美文网首页Android小技巧Android安卓
性能优化工具(九)-LeakCanary

性能优化工具(九)-LeakCanary

作者: Stan_Z | 来源:发表于2018-09-14 14:19 被阅读2319次
    一、简介

    使用MAT来分析内存问题,有一些门槛,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比才能找到问题原因。 为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary

    二、使用

    在app build.gradle 中加入引用:

    dependencies {
    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'
    }
    

    在 Application 中:

    public class LeakApplication extends Application {
        @Override public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {//1
          // This process is dedicated to LeakCanary for heap analysis.
          // You should not init your app in this process.
          return;
        }
        LeakCanary.install(this);
      }
    }
    

    如果当前的进程是用来给LeakCanary 进行堆分析的则return,否则会执行LeakCanary的install方法。这样我们就可以使用LeakCanary了,如果检测到某个Activity 有内存泄露,LeakCanary 就会给出提示。

    例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。改写Application,如下所示:

    public class MyApplication extends Application {
        private RefWatcher refWatcher;
        @Override
        public void onCreate() {
            super.onCreate();
            refWatcher= setupLeakCanary();
        }
    
        private RefWatcher setupLeakCanary() {
            if (LeakCanary.isInAnalyzerProcess(this)) {
                return RefWatcher.DISABLED;
            }
            return LeakCanary.install(this);
        }
    
        public static RefWatcher getRefWatcher(Context context) {
            MyApplication leakApplication = (MyApplication) context.getApplicationContext();
            return leakApplication.refWatcher;
        }
    }
    

    install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。

    最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。

    public class OtherActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            LeakThread leakThread = new LeakThread();
            leakThread.start();
        }
    
        class LeakThread extends Thread {
            @Override
            public void run() {
                try {
                    CommonUtils.getInstance(OtherActivity.this);
                    Thread.sleep(6 * 60 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            RefWatcher refWatcher = MyApplication.getRefWatcher(this);//1
            refWatcher.watch(this);
        }
    }
    

    MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。

    它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露,当前此例onDestroy加是多余的,这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。

    运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来。

    提示的notification:

    解决方法就是将LeakThread改为静态内部类,再次运行程序LeakThread就不会给出内存泄漏的提示了。

    使用小结
    如果只关注activity的内存泄漏,那么在Application中onCreate加入LeakCanary.install(this);就OK了,

    如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入:

    RefWatcher refWatcher = MyApplication.getRefWatcher(this);
         refWatcher.watch(this);
    
    三、使用踩坑

    在刚开始使用LeakCanary的时候,遇到了几个问题导致有内存泄漏发生时LeakCanary不发生通知,这里和大家分享一下。

    1、 你的应用需要有写SD权限,因为LeakCanary需要生成hprof文件,保存在SD卡里面,因此你的应用要先申请权限

    <!– SDCard中创建与删除文件权限 –>
    <uses-permission android:name=“android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>

    <!– 向SDCard写入数据权限 –>
    <uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE”/>

    2、 java.lang.NullPointerException: Attempt to invoke virtual method’boolean java.lang.String.equals(java.lang.Object)’ on a null object reference

    atcom.squareup.leakcanary.HeapAnalyzer.findLeakingReference(HeapAnalyzer.java:160)

    ….

    如果遇到这个问题,是LeakCanary的版本过低了,不适合Android6.0及以上的机型,我看网上大部分引用的还是基于1.3的版本,升级到1.5的就没问题了。

    四、原理介绍

    4.1 触发检测

    每次当Activity/Fragment执行完onDestroy生命周期,LeakCanary就会获取到这个Activity/Fragment,然后初始化RefWatcher对它进行分析,查看是否存在内存泄漏。

    4.2 判断是否存在内存泄漏

    首先尝试着从ReferenceQueue队列中获取待分析对象(软引用和弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用或弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用或弱引用加入到与之关联的引用队列中),如果不为空,那么说明正在被系统回收,如果直接就返回DONE,说明已经被系统回收了,如果没有被系统回收,可能存在内存泄漏,手动触发系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。

    4.3 分析内存泄漏

    确定有内存泄漏后,调用heapDumper.dumpHeap()生成.hprof文件目录。HAHA 是一个由 square 开源的 Android 堆分析库,分析 hprof 文件生成Snapshot对象。Snapshot用以查询对象的最短引用链。找到最短引用链后,定位问题,排查代码将会事半功倍。

    整体流程如下:

    i

    详细原理分析可以参考:LeakCanary原理分析

    总结:

    LeakCanary对于内存泄漏的检测非常有效,但也并不是所有的内存泄漏都能检测出来。

    <1>无法检测出Service中的内存泄漏问题

    <2>如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏。

    所以说LeakCanary针对Activity/Fragment的内存泄漏检测非常好用,但是对于以上检测不到的情况,还得配合Android Monitor + MAT 来分析。

    参考:
    http://liuwangshu.cn/application/performance/ram-6-leakcanary.html
    https://www.jianshu.com/p/3f1a1cc1e964

    相关文章

      网友评论

        本文标题:性能优化工具(九)-LeakCanary

        本文链接:https://www.haomeiwen.com/subject/qejogftx.html