[TOC]

概述

源码参考:https://github.com/square/leakcanary

官网地址:https://square.github.io/leakcanary/

我们先看一下使用方法:
添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1.0的最新版本
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

public class MyApp extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
//2.0之前LeakCanary是在单独的进程里来分析的
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}

2.0版本的使用方法

1
2
3
// 现在最新的版本是2.4的版本
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'

2.0与1.0的区别与联系

配合Gradle3.0引入的debugImplementation我们不需要添加no-op包来分离测试和生产环境了.

LeakCanary也不需要再单独的进程里来分析了,2.0之前的版本AS里可以看到LeakCanary是单独开了一个进程的

2.0不再需要我们手动执行初始化了, 我们看看2.0自动注册的实现.

2.0的使用更加简单方便

LeakCanary2.0原理

LeakCanary的原理很简单: 在Activity或Fragment被销毁后, 将他们的引用包装成一个WeakReference, 然后将这个WeakReference关联到一个ReferenceQueue.查看ReferenceQueue中是否含有Activity或Fragment的引用, 如果没有触发GC,再次查看,还是没有的话就说明没有回收成功, 可能发生了泄露. 这时候开始dump内存的信息,并分析泄露的引用链.

图片

LeakCanary主要是通过4个步骤自动检测并报告内存泄漏:

1、检查残留对象

2、Dumping堆内存

3、堆转储文件的分析

4、进行内存泄漏的分析

下面,我们来依次说一下这几个工作步骤:

1、检查残留对象

LeakCanary是hook了Android的生命周期,然后来自动检测Activity和Fragment何时被销毁并应该被垃圾收集器回收。

这些应该被销毁的对象将传递给ObjectWatcher,该对象持有对它们的弱引用。

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

  • destroyed Activity instances
  • destroyed Fragment instances
  • destroyed fragment View instances
  • cleared ViewModel instances

当然,您也可以自定义查看任何需要被回收对象,例如已经detached的View或者使用完毕的presenter

1
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")

如果等待5秒钟并运行垃圾收集后,仍未清除ObjectWatcher持有的弱引用,则该被监视对象被视为retained,并且有可能泄漏。 LeakCanary将此记录到Logcat:

1
2
3
4
5
6
7
D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
(Activity received Activity#onDestroy() callback)

... 5 seconds later ...

D LeakCanary: Scheduling check for retained objects because found new object
retained

LeakCanary在转储堆之前等待保留对象的数量达到阈值,并显示具有最新数量的通知。

2、Dumping the heap进行堆转储

当保留对象的数量达到一个阈值时,LeakCanary将Java堆转储到一个. hprof文件(一个堆转储)中,并存储到安卓文件系统中(参见LeakCanary在哪里存储堆转储?(https://square.github.io/leakcanary/faq/#where-does-leakcanary-store-heap-dumps))。转储堆会在短时间内冻结应用程序,在此期间,LeakCanary会显示以下的Toast:

toast

3、Analyzing the heap分析堆转储文件

LeakCanary使用Shark解析.hprof文件,并在该堆转储中找到可能泄漏的对象。

这里有必要说一下:LeakCanary1.0和2.0进行解析.hprof文件的工具是不同的。

对于每个可能泄漏的对象。LeakCanary会分析这个对应为什么不能被垃圾回收期回收的引用路径。我们将在下一部分中学习分析泄漏跟踪:修复内存泄漏。

hprof文件分析完成后,LeakCanary将显示带有摘要的通知,并在Logcat中打印结果。

我们可以看下面的图片:

LeakCanary已经分析出两处不同类型的泄漏,共泄漏4个对象。

LeakCanary为每个泄漏跟踪创建一个signature,并将具有相同signature的泄漏(即,由同一bug引起的泄漏)组合在一起。

我们可以可以点击通知栏来查看更详细的泄漏信息,当然也也可以点击应用的那个Leaks的App图标来查看。LeakCanary会为每个安装的应用添加启动器图标。

每行对应一组具有相同signature的泄漏。 LeakCanary在应用程序首次使用该签名触发泄漏时将行标记为“新建”。

源码解析

前面说2.0不再需要我们手动执行初始化了, 所以我们看看2.0自动注册的实现.

答案就在AppWatcherInstaller这个类上,它继承了ContentProvider。

ContentProvider的特点就是不用显示调用初始化,在执行完application的初始化后就会调用contentProvider的onCreate()方法。正是利用这一点,leakcanary把注册写在了这里面,有系统自动调用完成,对开发者完全无感知。

初始化

在AppWatcherInstaller的onCreate中调用了AppWatcher的manualInstall方法

1
2
3
4
5
6
7
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
}

AppWatcherInstaller 有两个实现类,一个是 MainProcess,当我们使用 leakcanary-android 模块时,会默认使用这个,表示在当前 App 进程中使用 LeakCanary。另外一个类为 LeakCanaryProcess,当使用 leakcanary-android-process 模块代替 leakcanary-android 模块时,则会使用这个类,我们可以看下 leakcanary-android-process 的 Manifest 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary">

<application>
<service
android:name="leakcanary.internal.HeapAnalyzerService"
android:exported="false"
android:process=":leakcanary" />

<provider
android:name="leakcanary.internal.AppWatcherInstaller$LeakCanaryProcess"
android:authorities="${applicationId}.leakcanary-process.installer"
android:process=":leakcanary"
android:exported="false"/>
</application>

</manifest>

AppWatcher#manualInstall()方法

1
2
3
fun manualInstall(application: Application) {
InternalAppWatcher.install(application)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 代码位于:leakcanary/leakcanary-object-watcher-android/src/main/java/leakcanary/internal/InternalAppWatcher.kt
fun install(application: Application) {
// 主线程判断
checkMainThread()
if (this::application.isInitialized) {
return
}
SharkLog.logger = DefaultCanaryLog()
InternalAppWatcher.application = application

val configProvider = { AppWatcher.config }

///Watcher 功能的入口位于 InternalAppWatcher.install() 方法中,
// 这个方法的调用时机则是我们上面说到的 AppWatcherInstaller.onCreate() 中。
// 这里主要做了两件事,首先是 Activity 和 Fragment 对象的注册观察,
// 这里我们以 ActivityDestroyWatcher 为例,Fragment 的处理也是类似的。
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

//在 InternalAppWatcher.install() 方法的最后,
// 还有一个 onAppWatcherInstalled 的调用,它是一个方法对象,
// 在 Kotlin 中一切皆对象,包括方法,它的赋值在 init 块中:
onAppWatcherInstalled(application)
}

InternalLeakCanary#invoke()

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
/**
* InternalLeakCanary的初始化
*/
override fun invoke(application: Application) {
_application = application

checkRunningInDebuggableBuild()
// 首先是 addOnObjectRetainedListener() 方法,这里会注册一个 OnObjectRetainedListener 事件,
// 也就是我们上面说到的在 moveToRetained() 方法中的回调事件。
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// AndroidHeapDumper 则是通过调用 Debug.dumpHprofData() 方法从虚拟机中 dump hprof 文件。
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
// GcTrigger 通过调用 Runtime.getRuntime().gc() 方法触发虚拟机进行 GC 操作。
val gcTrigger = GcTrigger.Default

val configProvider = { LeakCanary.config }

val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// HeapDumpTrigger 管理触发 Heap Dump 的逻辑,有两个地方会触发 Heap Dump:
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
addDynamicShortcut(application)

disableDumpHeapInTests()
}

内存泄漏的修复

文章参考:https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/