-
什么是内存泄漏
内存泄漏并不是内存空间部分被移除,而是指本应该被回收的内存空间,目前无法回收(被一个长生命周期的对象所引用),从而相对本应持有空闲的内存空间就减少了,实际内存中整体空间大小并没有改变。当出现一定数量内存泄漏,程序运行所需要的内存大小大于了所能提供的最大内存,之后将会出现内存溢出(Out Of Memory)的情况。
-
内存泄漏场景
-
单例持有Activity实例
-
持有Activity的context
public class SingleClass { private Context mContext; private volatile static SingleClass mInstance; public SingleClass(Context context) { mContext = context; } private static SingleClass getInstance(Context context) { if (mInstance != null) { synchronized (SingleClass.class) { if (mInstance != null) { mInstance = new SingleClass(context); } } } return mInstance; } }避免方法:将Activity的context换成Application的context,我们知道Activity、Application、Service都是间接的继承自Context的,而且Application的生命周期是最长(随着应用的销毁而onDestroy)
-
通过内部类间接持有Activity实例(容易被忽略)
public class SingleClass { private CharViewCallback mCharViewCallback //接口 private volatile static SingleClass mInstance; public SingleClass(Context context) { mContext = context; } private static SingleClass getInstance(Context context) { if (mInstance != null) { synchronized (SingleClass.class) { if (mInstance != null) { mInstance = new SingleClass(context); } } } return mInstance; } private static void init(CharViewCallback charViewCallback) { this.mCharViewCallback = charViewCallback; } }public class TestMainActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SingleClass.getInstance.init(new CharViewCallback() { @Override public void build() { Log.d("CharViewCallback","you ara F-Man"); } }); } }我们都知道在Java中非静态内部类或者匿名内部类都是默认持有外部类的引用的,然而此时单例类持有了该匿名内部类,因此单例类也就持有了外部类(Activity),因此同样是会导致内存泄漏(长生命周期对象持有短生命周期对象引用,导致该Activity虽然被destroy了,但是GC无法对它进行回收,内存空间无法被释放)。
-
-
Handler、ViewBinding在Activity、Fragment中的使用,都可能发生内存泄漏
-
viewBinding:AndroidStudio 4.0推出,替换findViewById初始化控件
// 避免内存泄漏:在Activity、Fragment销毁时将ActivityBinding、FragmentBinding置为null override fun onDestroy() { super.onDestroy() mBinding = null } -
Handler:线程通信的桥梁
大家不要被搞混了!Handler内存泄漏并不是Handler持有了Activity或Fragment的引用,这只是前提条件,当Activity被销毁时,Handler仍然还在进行消息传递(可能是进行着一个比较耗时的任务),那么就会导致内存泄漏。我们要知道消息传递最终是要落地于数据或ui的变化的,但是这些操作只有在主线程上执行了我们才能直观的看出变化。
// NO.1 override fun onDestroy() { super.onDestroy() handler.removeCallbacksAndMessages(null) }// NO.2 静态内部类天然不持有外部类引用 private static class MyHandler extends Handler { // 弱引用解决静态内部类访问外部类内部类,避免强引用 private WeakReference<AlaSmallGiftViewHolder> mSmallGiftViewHolder; public MyHandler(AlaSmallGiftViewHolder smallGiftViewHolder) { mSmallGiftViewHolder = new WeakReference<>(smallGiftViewHolder); } @Override public void handleMessage(Message message) { AlaSmallGiftViewHolder alaSmallGiftViewHolder = mSmallGiftViewHolder.get(); switch (message.what) { case SHOW_GIFT_FLAG: alaSmallGiftViewHolder.showSmallGiftView(); break; case UPDATE_GIFT_VIEW: alaSmallGiftViewHolder.updateSmallGiftViewCountData(); break; case HIDE_GIFT_VIEW: alaSmallGiftViewHolder.hideSmallGiftView(); break; default: break; } } }
-
以上是我本人目前开发中所遇到的内存泄漏比较频繁出现的例子,并不是所有内存泄漏的场景。
-
-
安装(version 2.X)
dependencies { implementation 'com.squareup.leakcanary:plumber-android:2.4' //发行版本调用 debugImplementation 'com.squareup.leakcanary:plumber-android:2.4' // 调试版本调用 }在2.X版本开始,我们不需要在Application中LeakCanary.install(this)
-
提醒
-
从版本2.0开始LeakCanary代码库从Java迁移到了Kotlin
-
从2.0 Beta 1开始,Shark成为LeakCanary 2新的堆分析器,相交于AndroidStudio堆分析结果占用内存空间更少,并且可以在任何Java VM中运行
-
重要API变更:
AppWatcher替换了LeakSentry,
ObjectWatcher替换了RefWatcher,
AndroidReferenceMatchers替换了AndroidExcludedRefs,
OnHeeapAnalyzedListener替换了AnalysisResultListener,
AndroidObjectInspectors替换了AndroidLeakTraceInspectors
-
主要区别:当应用程序处于前台状态时,LeakCanary 2不会在每个保留的实例上触发。相反,它将等到应用程序进入后台或达到前台的5个保留实例的阀值。
-
-
-
如何使用Leaks排查内存泄漏?
在单个内存泄漏的引用链中,我们应该关注的红色引用链,导致内存泄漏的地方就在其中某一处,这时你需要查看该红色引用链相关代码,找到能与内存泄漏场景相似的代码,修改、调试、发布。
-
LeakCanary如何运作?
-
检测残留物(Detecting retained objects)
LeakCanary对Activity、Fragment、View、ViewModel的destroy会自动检测残留对象
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")如果在GC完5秒以后,所观察的对象还没有被回收,那么判定该对象发生了内存泄漏,并将此记录到Logcat中,依次这样检测,直至残留对象达到规定的阀值,或者应用返回至后台(不可见状态)。
-
转储堆(Heap Dump)
当残留对象的数量达到阀值时,LeakCanary将Java堆转储到存储在Android文件系统上的.hprof文件中。转储堆会使应用程序冻结一小段时间,在此期间LeakCanary会显示相应的Toast信息。
-
分析堆(Analyzing Heap Dump)
.hprof使用Shark解析文件,并在该堆转储中找到保留的对象,并得到导致该对象无法被回收的引用链
在LeakCanary 2中会对所有泄漏链进行分组(group)
-
泄漏分类(Categorizing leaks)
LeakCanary将在应用程序中发现的泄漏分为两类:应用程序泄漏和库泄漏(Library Leak标签)。
-