https://www.cnblogs.com/zhaoyanjun/p/5981386.html
https://blog.youkuaiyun.com/p106786860/article/details/55060794
https://www.jianshu.com/p/3138f9c351e8
##Android 如何有效的解决内存泄漏的问题##
前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题。在网上找了很多资料,有很多都是互相抄的,没有实际的作用。
本文的内存泄漏检测工具是:LeakCanary github地址:https://github.com/square/leakcanary
什么是内存泄漏?
- 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并不是指物理上的内存消失,这里的内存泄漏是值由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费。
怎样会导致内存泄漏?
- 资源对象没关闭造成的内存泄漏,如查询数据库后没有关闭游标cursor
- 构造Adapter时,没有使用 convertView 重用
- Bitmap对象不在使用时调用recycle()释放内存
- 对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放
内存泄漏有什么危害?
-
内存泄漏对于app没有直接的危害,即使app有发生内存泄漏的情况,也不一定会引起app崩溃,但是会增加app内存的占用。内存得不到释放,慢慢的会造成app内存溢出。所以我们解决内存泄漏的目的就是防止app发生内存溢出。
1、新建线程引起的Activity内存泄漏
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
运行上面的代码后,点击finish按钮,过一会儿发生了内存泄漏的问题。
为什么Activity6会发生内存泄漏?
进入Activity6 界面,然后点击finish按钮,Activity6销毁,但是Activity6里面的线程还在运行,匿名内部类Runnable对象引用了Activity6的实例,导致Activity6所占用的内存不能被GC及时回收。
如何改进?
Runnable改为静态非匿名内部类即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
2、Activity添加监听器造成Activity内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这个是在开发中经常会犯的错误,NastyManager.getInstance() 是一个单例,当我们通过 addListener(this) 将 Activity 作为 Listener 和 NastyManager 绑定起来的时候,不好的事情就发生了。
如何改进?
想要修复这样的 Bug,其实相当简单,就是在你的 Acitivity 被销毁的时候,将他和 NastyManager 取消掉绑定就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
3、Handler 匿名内部类造成内存溢出?
先看着一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
这段代码运行起来后,立即点击 finish 按钮,通过检测,发现 HandlerActivity 出现了内存泄漏。当Activity finish后,延时消息会继续存在主线程消息队列中8秒钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。Handler 是个很常用也很有用的类,异步,线程安全等等。如果有下面这样的代码,会发生什么呢? handler.postDeslayed ,假设 delay 时间是几个小时… 这意味着什么?意味着只要 handler 的消息还没有被处理结束,它就一直存活着,包含它的 Activity 就跟着活着。我们来想办法修复它,修复的方案是 WeakReference ,也就是所谓的弱引用。垃圾回收器在回收的时候,是会忽视掉弱引用的,所以包含它的 Activity 会被正常清理掉。
如何避免
- 使用静态内部类
- 使用弱引用
修改后代码是这样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
这个Handler已经使用了静态内部类,并且使用了弱引用。但是这个并没有完全解决 HandlerActivity 内存泄漏的问题,罪魁祸首是线程创建的方式出了问题,就像本文的第一个例子一样。改进的方式,是把Runnable类写成静态内部类。
最终完整的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
等等,还没完呢?
上面这个代码已经有效的解决了Handler,Runnable 引用Activity实例从而导致内存泄漏的问题,但是这不够。因为内存泄漏的核心原因就是这个某个对象应该被系统回收内存的时候,却被其他对象引用,造成该内存无法回收。所以我们在写代码的时候,要始终绷着这个弦。再回到上面这个问题,当当前Activity调用finish销毁的时候,在这个Activity里面所有线程是不是应该在OnDestory()方法里,取消线程。当然是否取消异步任务,要看项目具体的需求,比如在Activity销毁的时候,启动一个线程,异步写log日志到本地磁盘,针对这个需求却需要在OnDestory()方法里开启线程。所以根据当前环境做出选择才是正解。
所以我们还可以修改代码为:在onDestroy() 里面移除所有的callback 和 Message 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
|
4、AsyncTask造成内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
为什么?
上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。
怎么解决?
- 自定义静态AsyncTask类
- AsyncTask的周期和Activity周期保持一致。也就是在Activity生命周期结束时要将AsyncTask cancel掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
5、Timer Tasks 造成内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
为什么?
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。
怎么解决?
- 在适当的时机进行Cancel。
- TimerTask用静态内部类
注意:在网上看到一些资料说,解决TimerTask内存泄漏可以使用在适当的时机进行Cancel。经过测试,证明单单使用在适当的时机进行Cancel , 还是有内存泄漏的问题。所以一定要用静态内部类配合使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
##LeakCanary—内存泄露检测神器##
一、LeakCanary简介
LeakCanary是Square公司开源的一个检测内存的泄露的函数库,可以方便地和你的项目进行集成,在Debug版本中监控Activity、Fragment等的内存泄露;
LeakCanary集成到项目中之后,在检测到内存泄露时,会发送消息到系统通知栏。点击后打开名称DisplayLeakActivity的页面,并显示泄露的跟踪信息,Logcat上面也会有对应的日志输出。同时如果跟踪信息不足以定位时,DisplayLeakActivity还为开发者默认保存了最近7个dump文件到App的目录中,可以使用MAT等工具对dump文件进行进一步的分析;
二、内存泄漏简介
在了解了LeakCanary的接入方式后,我们肯定着急想见识见识LeakCanary的威力。在跟大家演示LeakCanary检测和处理构成之前,大家应该明应该对内存泄露有基本的了解和认识;
1.为什么会产生内存泄漏?
当一个对象不需要使用本该回收时,有另外一个正在使用的对象持有它的引用,从而导致它不能回收停留在堆内存中,这就产生了内存泄漏;
2.内存泄露对程序产生的影响?
内存泄漏是造成应用程序OOM的主要原因之一。Android系统为每个应用程序分配有限的内存,当应用中内存泄漏较多时,就难免会导致应用所需要的内存超出系统分配限额,从而导致OOM应用Crash;
3.Android常见的内存泄露?
相信内存泄露对大家都早有耳闻,但是它不像一些Java异常情况,会立即造成程序的Crash,却有让大家比较“陌生”。下面我们就列举出日常开发中常见的内存泄露类型,让大家对内存泄露的认识不仅仅停留在“有所耳闻 ”的层面;
单例造成:由于单例静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象(如Context)已经不使用了,而单例对象还持有对象的引用造成这个对象不能正常被回收;
非静态内部类创建静态实例造成:在Acitivity内存创建一个非静态内部类单例,避免每次启动资源重新创建。但是因为非静态内部类默认持有外部类(Activity)的引用,并且使用该类创建静态实例。造成该实例和应用生命周期一样长,导致静态实例持有引用的Activity和资源不能正常回收;
Handler造成:子线程执行网络任务,使用Handler处理子线程发送消息。由于handler对象是非静态匿名内部类的对象,持有外部类(Activity)的引用。在Handler-Message中Looper线程不断轮询处理消息,当Activity退出还有未处理或者正在处理的消息时,消息队列中的消息持有handler对象引用,handler又持有Activity,导致Activity的内存和资源不能及时回收;
线程造成:匿名内部类Runnalbe和AsyncTask对象执行异步任务,对当前Activity隐式引用。当Activity销毁之前,任务还没有执行完,将导致Activity的内存和资源不能及时回收;
资源未关闭造成的内存泄露:对于使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄露;
三、LeakCanary接入
下面我们还是以QProject项目进行演示如何在项目中接入LeakCanary,项目目录如下:
1.添加LeakCanary依赖
在主项目main模块的build.gradle文件中添加LeakCanary相关依赖;
/main/build.gradle文件
apply plugin: 'com.android.application'
android {
... ...
}
dependencies {
... ...
//添加leakcanary相关的依赖
//在release和test版本中,使用的是LeakCanary的no-op版本,也就是没有实际代码和操作的Wrapper版本,只包含LeakCanary和RefWatcher类的空实现,这样不会对生成的APK包体积和应用性能造成影响
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
... ...
compile project(':test')
}
2.初始化LeakCanary
在主项目main模块的QApplication的onCreate()方法中初始化LeakCanary;
/main/src/main/java/com/qproject/main/QApplication.java文件
public class QAplication extends Application{
@Override
public void onCreate() {
super.onCreate();
... ...
//初始化LeakCanary
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
OK,到这里我们就完成了一个项目的LeankCanary的简单接入;
提示1:集成LeakCanary后,构建和安装apk的时候报错如下:
Error:Error converting bytecode to dex:
Cause: com.android.dex.DexException: Multiple dex files define Lcom/squareup/leakcanary/LeakCanary;
Error:Execution failed for task ':main:transformClassesWithDexForDebug'.
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: java.lang.UnsupportedOperationException
处理1:添加依赖debugCompile 'com.squareup.haha:haha:2.0.3',修改依赖的版本为1.4-beta2;
四、LeakCanary检测
通过对一些常见的内存泄露的学习,我们已经对内存泄露有所见闻了。那么下面我们通过LeakCanary工具,让大家感受下它在你的日常开发中的真实存在。我们以常见内存—单例造成的内存泄露为例进行实践;
1.单例内存泄露模拟
test/src/main/com/qproject/test/TestManager.java
public class TestManager {
//单例静态特性使得单例的生命周期和应用的生命周期一样长
private static TestManager instance;
private Context context;
/**
* 传入的Context的生命周期很重要:
* 如果传入的是Application的Context,则生命周期和单例生命周期一样长;
* 如果传入的是Activity的Context,由于该Context和Activity的生命周期一样长,当Activity退出的时候它的内存不会被回收,因为单例对象持有它的引用;
*/
private TestManager(Context context) {
this.context = context;
}
public static TestManager getInstance(Context context) {
if (instance == null) {
instance = new TestManager(context);
}
return instance;
}
}
test/src/main/com/qproject/test/leakcanary/LeakCanaryActivity.java
public class LeakCanaryActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leakcanary);
//获取单例对象,退出Activity即可模拟出内存泄露
TestManager testManager = TestManager.getInstance(this);
}
}
2.检测消息通知
运行App到LeakCanaryActivit页面并退出,在检测到内存泄露的时候,会发送消息到系统通知栏;
3.查看检测详情
点击通知消息,打开名为DisplayLeakActivity的页面,并显示泄漏的跟踪信息;
4.查看LogCat日志
除了以上泄漏信息的显示,Logcat上面也会有对应的日志输出;
//内存泄露对象com.qproject.test.leakcanary.LeakCanaryActivity
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * com.qproject.test.leakcanary.LeakCanaryActivity has leaked:
//static com.qproject.test.TestManager.instance的com.qproject.test.TestManager.context引用了回收的内存对象
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * GC ROOT static com.qproject.test.TestManager.instance
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * references com.qproject.test.TestManager.context
//内存泄露对象大小,Reference Key,Device和Android Version等信息
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * leaks com.qproject.test.leakcanary.LeakCanaryActivity instance
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Retaining: 46 KB.
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Reference Key: 3d74d294-70dc-4447-a9a2-64e656ea86b8
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Device: Genymotion Android PREVIEW - Google Nexus 5X - 7.0.0 - API 24 - 1080x1920 vbox86p
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Android Version: 7.0 API: 24 LeakCanary: 1.5 00f37f5
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Durations: watch=5038ms, gc=137ms, heap dump=2390ms, analysis=27325ms
//内存泄露对象详细信息
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Details:
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Class com.qproject.test.TestManager
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | static instance = com.qproject.test.TestManager@316184816 (0x12d898f0)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | static $classOverhead = byte[308]@316175745 (0x12d87581)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Instance of com.qproject.test.TestManager
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | static instance = com.qproject.test.TestManager@316184816 (0x12d898f0)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | static $classOverhead = byte[308]@316175745 (0x12d87581)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | context = com.qproject.test.leakcanary.LeakCanaryActivity@315059712 (0x12c76e00)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | shadow$_klass_ = com.qproject.test.TestManager
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | shadow$_monitor_ = 0
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Instance of com.qproject.test.leakcanary.LeakCanaryActivity
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | static $classOverhead = byte[2228]@316203009 (0x12d8e001)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mDelegate = android.support.v7.app.AppCompatDelegateImplN@315842128 (0x12d35e50)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mEatKeyUpEvent = false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mResources = null
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mThemeId = 2131230884
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mCreated = true
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mFragments = android.support.v4.app.FragmentController@316183584 (0x12d89420)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mHandler = android.support.v4.app.FragmentActivity$1@316163360 (0x12d84520)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mNextCandidateRequestIndex = 0
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mOptionsMenuInvalidated = false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@316172368 (0x12d86850)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mReallyStopped = true
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mRequestedPermissionsFromFragment = false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mResumed = false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mRetaining = false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mStopped = true
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mStartedActivityFromFragment = false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mStartedIntentSenderFromFragment = false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mExtraDataMap = android.support.v4.util.SimpleArrayMap@316171864 (0x12d86658)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: | mActionBar = null
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: | mActionModeTypeStarting = 0
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: | mActivityInfo = android.content.pm.ActivityInfo@315841984 (0x12d35dc0)
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: | mActivityTransitionState = android.app.ActivityTransitionState@316207336 (0x12d8f0e8)
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: | mApplication = com.qproject.main.QAplication@314916416 (0x12c53e40)
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: | mCalled = true
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: | mChangeCanvasToTranslucent = false
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: | mChangingConfigurations = false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mComponent = android.content.ComponentName@315998320 (0x12d5c070)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mConfigChangeFlags = 0
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mCurrentConfig = android.content.res.Configuration@316178888 (0x12d881c8)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mDecor = null
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mDefaultKeyMode = 0
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mDefaultKeySsb = null
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mDestroyed = true
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mDoReportFullyDrawn = false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mEatKeyUpEvent = false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mEmbeddedID = null
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mEnableDefaultActionBarUp = false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mEnterTransitionListener = android.app.SharedElementCallback$1@1887062680 (0x707a4a98)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mExitTransitionListener = android.app.SharedElementCallback$1@1887062680 (0x707a4a98)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mFinished = true
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mFragments = android.app.FragmentController@316183536 (0x12d893f0)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: | mHandler = android.os.Handler@316163296 (0x12d844e0)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: | mHasCurrentPermissionsRequest = false
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: | mIdent = 20356640
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: | mInstanceTracker = android.os.StrictMode$InstanceTracker@316183552 (0x12d89400)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: | mInstrumentation = android.app.Instrumentation@314950632 (0x12c5c3e8)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: | mIntent = android.content.Intent@316215416 (0x12d91078)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: | mLastNonConfigurationInstances = null
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: | mMainThread = android.app.ActivityThread@314966272 (0x12c60100)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mManagedCursors = java.util.ArrayList@316171816 (0x12d86628)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mManagedDialogs = null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mMenuInflater = null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mParent = null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mReferrer = java.lang.String@316215864 (0x12d91238)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mResultCode = 0
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mResultData = null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mResumed = false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mSearchEvent = null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mSearchManager = null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mStartedActivity = false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mStopped = true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mTaskDescription = android.app.ActivityManager$TaskDescription@316163328 (0x12d84500)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mTemporaryPause = false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mTitle = java.lang.String@315129824 (0x12c87fe0)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mTitleColor = 0
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mTitleReady = true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mToken = android.os.BinderProxy@315867328 (0x12d3c0c0)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mTranslucentCallback = null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mUiThread = java.lang.Thread@1959751680 (0x74cf7000)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mVisibleBehind = false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mVisibleFromClient = true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mVisibleFromServer = true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: | mVoiceInteractor = null
12-25 07:50:51.716 4941-5795/com.qproject.main D/LeakCanary: | mWindow = com.android.internal.policy.PhoneWindow@315116864 (0x12c84d40)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mWindowAdded = true
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mWindowManager = android.view.WindowManagerImpl@316172152 (0x12d86778)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mInflater = com.android.internal.policy.PhoneLayoutInflater@316010352 (0x12d5ef70)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mOverrideConfiguration = null
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mResources = android.content.res.Resources@316235992 (0x12d960d8)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mTheme = android.content.res.Resources$Theme@316183728 (0x12d894b0)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mThemeResource = 2131230884
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | mBase = android.app.ContextImpl@316155392 (0x12d82600)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | shadow$_klass_ = com.qproject.test.leakcanary.LeakCanaryActivity
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | shadow$_monitor_ = 1316364430
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: * Excluded Refs:
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:FinalizerWatchdogDaemon (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:main (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:LeakCanary-Heap-Dump (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.WeakReference (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.SoftReference (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.PhantomReference (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.Finalizer (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.FinalizerReference (always)
5.获取dump日志文件
如果觉得跟踪信息不足以定位时,DisplayLeakActivity还为开发者默认保存了最近7个dump文件到APP的目录中,可以使用MAT等工具对dump文件进行进一步的分析;
vbox86p:/data/data/com.qproject.main/files/leakcanary # ls
2016-12-25_07-50-51_718.hprof 2016-12-25_07-50-51_718.hprof.result
D:\>adb pull ./data/data/com.qproject.main/files/leakcanary/2016-12-25_07-50-51_ 718.hprof
[100%] ./data/data/com.qproject.main/f...akcanary/2016-12-25_07-50-51_718.hprof
Android Studio->View->Tool Windows->Captures,打开Captures窗口,将pull获取的hprof文件剪切到Capture中文件的目录下,双击打开即可;
具体的分析过程,这里就不重点讲述,大家去查询MAT相关的资料;
五、检测其他对象
如果想监听其他的对象(例如Fragment ),可以通过RefWatcher的实例来实现;
六、LeakCanary原理
1.RefWatcher.watch()函数会为被监控的对象创建一个KeyedWeakReference弱引用对象,是WeakReference对的子类,增加了键值对信息,后面会根据指定的键key找到弱引用对象;
2.在后台线程AndroidWatchExecutor中,检查KeyedWeakReference弱引用是否已经被清楚。如果还存在,则触发一次垃圾回收之后。垃圾回收之后,如果弱引用对象依然存在,说明发生了内存泄露;
---------------------
作者:1024工场
来源:优快云
原文:https://blog.youkuaiyun.com/p106786860/article/details/55060794
版权声明:本文为博主原创文章,转载请附上博文链接!
##在Android开发中怎样使用Application类##
---- 在Android开发中怎样使用Application类
自己独立开发项目才发现以前对Application类并不是十分了解,现在开始直接搭建一个新项目的框架才重新踩过这个坑。
1. Context类型
Android是用Java语言来编写和,然而Android却需要一个完整的Android工程环境,在这个环境下,我们熟悉的Activity、Service、ContentPrivoder等系统组件才能有自己各自的上下文环境,也就是Context。Context是维持Android应用的核心功能类,是整个Android系统交互的核心。
context继承图
Context的继承结构从图中可以看到,直系子类有两个,一个是ContextWrapper,一个是ContextImpl。那么从名字上就可以看出,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。
在这里我们至少看到了几个所比较熟悉的面孔,Activity、Service、还有Application。由此,其实我们就已经可以得出结论了,Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的。
一种比较特殊的情况是Dialog要求在Activity的Context中弹出,而一些全局的例如网络终端的Dialog需要什么特殊权限。
2. Application类在项目开发中的使用
首先在项目目录下一个Java类继承Application类,实现是onCreate()方法。这个类可以做APP的全局初始化工作,比如图片加载框架的全局配置信息设置。
public class AndroidApplication extends Application {
private static AndroidApplication instance;
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static AndroidApplication getInstance(){
return instance;
}
}
然后千万不要忘了在Android项目的Manifest文件中指定Application的实现类,不然系统会创建一个默认的Application类。
<application
android:name=".AndroidApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
3. Application类或Context类的误用情况
1. 不能用Application缓存数据!!!
因为Application会因为进入background后内存不足被系统干掉,进入后系统会重现创建一个Application类,而导致缓存在Application类里的数据全部初始化而丢失。
2. 错误的获取全局Context对象的方式
public class AnddroidApplication extends Application {
private static AnddroidApplication app;
public static AnddroidApplication getInstance() {
if (app == null) {
app = new AnddroidApplication();
}
return app;
}
}
上面这种方式如果是单纯的Java工程可能没有问题,但是在Android里这样说大错特错的。因为Application是系统组件,系统组件实例是要由系统去创建的,如果我们这里直接创建一个,不过是简单的Java对象而已,不具备任何Context能力,也无法进行任何Context操作。标准的写法就本文的第一段示意代码那样。
3. 在控件的构造方法中获取Context或者做其他视图操作
写过Android的同学应该知道自己或者看别人dome都很少或者基本看不到在控件构造函数内进行初始化,获取参数等这些操作吧!是的,这样做是很容易出难以发现的Bug的。具体原因是在ContextWrapper类的源码中,他有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,之后mBase对象就有值了。而我们又知道,所有Context的方法都是调用这个mBase对象的同名方法,那么也就是说如果在mBase对象还没赋值的情况下就去调用Context中的任何一个方法时,就会出现空指针异常,上面的代码就是这种情况。Application中方法的执行顺序如下图所示:
Application方法执行顺序
作者:蝉翅的空响
链接:https://www.jianshu.com/p/3138f9c351e8
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。