1 LeakCanary 简介

LeakCanary 是 Square 公司为 Android 开发者提供的一个自动检测内存泄漏的工具,LeakCanary 本质上是一个基于 MAT 进行 Android 应用程序内存泄漏自动化检测的的开源工具,我们可以通过集成 LeakCanary 提供的 jar 包到自己的工程中,一旦检测到内存泄漏,LeakCanary 就会 dump Memory 信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题,极大地方便了Android应用程序的开发。

2 LeakCanary 原理

LeakCanary 是通过在 Application 的 registerActivityLifecycleCallbacks 方法实现对 Activity 销毁监听的,该方法主要用来统一管理所有 Activity 的生命周期。所有 Activity 在销毁时在其 OnDestory 方法中都会回调 ActivityLifecycleCallbacks 的 onActivityDestroyed 方法,而 LeakCanary 要做的就是在该方法中调用 RefWatcher.watch 方法实现对 Activity 进行内存泄漏监控。
那么,LeakCanary 是如何判断某个 Activity 可能会发生内存泄漏呢?答案是:WeakReference 和 ReferenceQueue,即 LeakCanary 利用了 Java 的 WeakReference 和 ReferenceQueue,通过将 Activity 包装到 WeakReference 中,被 WeakReference 包装过的 Activity 对象如果能够被回收,则说明引用可达,垃圾回收器就会将该 WeakReference 引用存放到 ReferenceQueue 中。假如我们要监视某个 Activity 对象,LeakCanary 就会去 ReferenceQueue 找这个对象的引用,如果找到了,说明该对象是引用可达的,能被 GC 回收,如果没有找到,说明该对象有可能发生了内存泄漏。最后,LeakCanary 会将 Java 堆转储到一个 .hprof 文件中,再使用 Shark(堆分析工具)析 .hprof 文件并定位堆转储中“滞留”的对象,并对每个"滞留"的对象找出 GC roots 的最短强引用路径,并确定是否是泄露,如果泄漏,建立导致泄露的引用链。最后,再将分析完毕的结果以通知的形式展现出来。

3 LeakCanary 接入

要使用 LeakCanary,请将 leakcanary-android 依赖项添加到应用程序 build.gradle 文件中:

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}

通过过滤 Logcat 中的 LeakCanary 标签来确认 LeakCanary 在启动时正在运行:

D/LeakCanary: LeakCanary is running and ready to detect memory leaks.

在 LeakCanary2.0 之前我们接入的时候需要在 Application.onCreate 方法中显式调用 LeakCanary.install(this); 开启 LeakCanary 的内存监控。
从 LeakCanary2.0 开始通过库里注册的 ContentProvier 自己开启 LeakCanary 的内存监控,无需用户手动再添加初始化代码。

LeakCanary 自动检测以下对象的泄漏:

  • 销毁的Activity实例
  • 销毁的Fragment实例
  • 销毁的片段View实例
  • 清除ViewModel实例

4 LeakCanary 使用

以下面的非静态内部类的泄漏场景为例,演示下 LeakCanary 的使用步骤。

public class MainActivity extends AppCompatActivity {
    //非静态内部类的静态实例引用
    public static InnerClass innerClass = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //保证非静态内部类的实例只有1个
        if (innerClass == null) {
            innerClass = new InnerClass();
        }
    }

    // 非静态内部类
    private class InnerClass {
        //...
    }
}

安装测试应用后,手机上会出现如下两个图标:
左侧的是测试应用的图标,右侧是自动安装的 LeakCanary 的图标。

LeakCanary_icon
打开测试应用,然后返回退出,人为制造内存泄漏。等待大概10秒,LeakCanary 就会检测到,并进行分析。在通知栏可以看到进度。

LeakCanary_dumping_heap
LeakCanary_analyzing_parsing
LeakCanary_analyzing_extracting
LeakCanary_analyzing_finding_paths
LeakCanary_analyzing_computing_native
LeakCanary_analyzing_computing
然后在分析完成后,然后在通知栏会收到通知。

LeakCanary_notification
点开通知,会打开 LeakCanary 的界面。

LeakCanary_leaked
每行对应一组具有相同签名的泄漏。LeakCanary 在应用程序第一次使用该签名触发泄漏时将一行标记为“New”。点击可以查看泄漏详情。

LeakCanary_leaked_detail
这里显示 com.example.leaksample.MainActivity 这个实例发生了泄漏,红色下划波浪线标记的是可能的原因。我们分析下原因:innerClass 是非静态内部类的静态实例,它的生命周期等于应用的生命周期,持有外部类 MainActivity 的引用,故 MainActivity 无法被 GC 回收,从而导致内存泄漏。解决方案就是将 InnerClass 改为静态内部类。

另外,在 Logcat 中也可以看到 LeakCanary 的相关信息。LeakCanary 将应用中发现的泄漏分为两类:应用程序泄漏和库泄漏。

====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
......
====================================
0 LIBRARY LEAK

如果有库泄漏,LeakCanary 会在其泄漏列表中将其标记为 Library Leak:

LeakCanary_library_leak
以上就是 LeakCanary 的使用说明, LeakCanary 只能定位到内存泄漏的大概范围,具体原因还是需要我们自己分析。内存泄露一般是代码设计存在缺陷导致的,只有多了解内存泄露的场景,才可以避免不必要的内存溢出和提高自己的编码水平。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐