前一阵子正好碰到了 App 里出现内存泄漏,记录一下完整的流程。

起因

在运行时检测 App 的内存泄漏有多种方式:比如手动的在 Android Studio 中调用 GC,然后观察内存抖动的情况;比如在 App 中加入 leakcanary 这样的 lib,当发生内存泄漏时直接输出 Heap Dump。不过我一直不希望在 App 中引入过多的 lib,所以我是通过 StrictMode 来监测 App 有没有发生内存泄漏。

StrictMode 是 Android 2.3 之后加入的一个类,用以检测程序中是否有违例情况发生的开发者工具。

在自定义的 Application 中的 onCreate() 方法中初始化 StrictMode 就可以了

public void onCreate() {
    super.onCreate();

    if (BuildConfig.DEBUG) {
        // 设置线程策略和 VM策略
        StrictMode.setThreadPolicy(new StrictMode
                                        .ThreadPolicy
                                        .Builder()
                                        .detectAll()
                                        .penaltyLog()
                                        .build());
        StrictMode.setVmPolicy(new StrictMode
                                    .VmPolicy.
                                    Builder().
                                    detectAll().
                                    penaltyLog().
                                    build());
    }
}

之后如果有内存泄漏、未关闭 closable 对象或在主线程中监测到耗时操作时,StrictMode 就会通过 log 输出来提醒我们。

利用 Android Studio 获取 Java Heap

发生内存泄漏的 log

之后,当某次发生内存泄漏时,StrictMode 就通过 log 提示我们。此处是我的 App 所遇到的情况,也就是存在多个 Activity 实例,并且无法释放。

获取 Java Heap

此时可以通过 Android Studio 自带的 Monitor 来获取此时应用的堆信息。

  1. 点击Initate GC按钮,强制 GC
  2. 点击Dump Java Heap按钮,生成 Heap 文件(*.hprof)

通常获取 Heap 文件之后会直接打开 *.hprof 文件,不过我们也可以在 Android Studio 左侧的Captures标签找到已经生成的 Heap 文件。

通过 Android Studio 分析 Memory Leak

  1. 打开 Heap 文件之后,首先打开右侧的Analyzer Tasks标签
  2. 点击perform action按钮
  3. Analysis Results里就可以看到具体有哪些 Acitvity 导致了内存泄漏
  4. Reference Tree里可以看到持有这个 Activity 引用的对象

如果是简单的情况的话,这里就可以排查出到底是那个引用导致的 Activity 无法释放,从而导致了内存泄漏。但是如果情况复杂一些的话,就需要用到 MAT 来分析了

利用 MAT 分析 Heap 文件

MAT 全称"Eclipse Memory Analyzer”,是一个强大 JAVA 堆转储文件分析工具,也就是分析 *.hprof 文件的工具。

转化为标准格式的文件

在使用 MAT 之前,我们需要先将 Android Studio 生成的 Heap 文件转换为标准格式。

筛选出泄漏对象

用 MAT 打开 Heap 文件之后点击生成 Histogram,可以列出内存中的对象及其个数、大小

由于我们已经通过 Android Studio我们已经知道泄漏的对象的类名。所以可以直接筛选出泄漏的对象。右键Merge Shortest Paths to GC Roots -> exclude all phantom/weak/soft etc. references。排除掉所有的虚/软/弱引用之后,就可以知道到底是那些对象持有泄漏的 Activity 的强引用从而导致内存泄漏了。

查明泄漏原因

这里可以看到是 Glide 引起的,此时就可以通过代码中 Glide 相关的代码来排查。

此处的例子,是一个匿名内部类BitmapTransformation持有了 Activity 的引用,而 Glide 是全局配置的。所以 Activity 对象无法释放,从而导致了内存泄漏

排查以外

内存泄漏通常是由于我们代码的不规范所导致的。比如长引用持有对象而不释放啊,closable 对象未及时关闭啊,或者一些非静态类持有对象啊。

除了在内存泄漏发生之后,及时的通过工具来排查原因,修改代码之外,最重要的还是要规范自己的代码,提高代码质量。当然,不忽略任何一个 lint 提示的警告大概是最方便快捷的方式了吧!