关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。
目录
- 一、导读
- 二、概览
- 三、使用
- 四、原理分析
- 4.1 自动初始化
- 4.1.1 初始化
- 4.2 LeakCananry自动检测步骤
- 4.2.1 检测泄漏
- 4.2.2 dump
- 4.2.3 shark分析
- 4.2.4 生成报告
- 五、 推荐阅读
一、导读
我们继续总结学习基础知识,温故知新。
本文主讲 LeakCanary 使用及原理。
截止本文写稿,目前LeakCanary已经更新到2.12的版本, 我们基于2.x的版本来查看源码。
二、概览
LeakCanary主要有两大作用,第一发现内存泄漏问题,第二根据内存的状态输出泄漏的堆栈。
LeakCanary 的核心原理是主要通过 Android 生命周期的 api 来监听 activities 和 fragments 什么时候被销毁,
被销毁的对象会被传递给一个 ObjectWatcher,它持有它们的弱引用,默认等待5秒后观察弱引用是否进入关联的引用队列,
是则说明未发生泄露,否则说明可能发生泄漏。
LeakCanary 是我们熟悉内存泄漏检测工具,它能够帮助开发者非常高效便捷地检测 Android 中常见的内存泄漏。
在各大厂自研的内存泄漏检测框架(如腾讯 Matrix 和快手 Koom)的帮助文档中,也会引述 LeakCanary 原理分析。
LeakCanary Github
LeakCanary 官网
在Java中有四大引用,具体可查看下面这篇文章
强引用:绝不回收
软引用:内存不足才回收
弱引用:GC 就回收
虚引用:等价于没有引用,只是用来标识下指向的对象是否被回收。
三、使用
To use LeakCanary, add the leakcanary-android dependency to your app’s build.gradle file:
dependencies {// debugImplementation because LeakCanary should only run in debug builds.debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'}
这样就可以了,LeakCanary会自动完成初始化,自动检测以下对象的泄漏:
Activity 、Fragment 、fragment View 、ViewModel 、Service等
我们也可以监听自己想要监听的任意对象,使用方式如下:
AppWatcher.objectWatcher.watch(object, "what you want to watcher")
四、原理分析
4.1 自动初始化
利用ContentProvider原理,ContentProvider的onCreate是在 Application的onCreate之前执行,
因此在App进程拉起时会自动执行 AppWatcherInstaller 的onCreate生命周期,利用Android这种机制就可以完成自动初始化。
我们也可以关闭自动注册,进行手动注册。只需要在资源文件里覆写 @bool/eak_canary_watcher_auto_install 布尔值来关闭自动初始化,
<application><providerandroid:name="leakcanary.internal.MainProcessAppWatcherInstaller"android:authorities="${applicationId}.leakcanary-installer"android:enabled="@bool/leak_canary_watcher_auto_install"android:exported="false"/>
</application><resources><bool name="leak_canary_watcher_auto_install">false</bool>
</resources>
手动调用 AppWatcher.manualInstall 。
4.1.1 初始化
在初始化时,需要做以下几个操作
- 注册前后台切换监听、前台 Activity 监听和 ObjectWatcher 的泄漏监听
- 注册Activity的生命周期监听、fragment的生命周期监听、service等监听
我们来看看源码,都有哪些监听
fun appDefaultWatchers(application: Application,reachabilityWatcher: ReachabilityWatcher = objectWatcher): List<InstallableWatcher> {return listOf(ActivityWatcher(application, reachabilityWatcher), // activityFragmentAndViewModelWatcher(application, reachabilityWatcher), // fragmentRootViewWatcher(reachabilityWatcher),ServiceWatcher(reachabilityWatcher))}
在这个方法中,添加了我们常用的4中监听:
- Fragment的生命周期期监听:同样,注册** FragmentManager.FragmentLifecycleCallbacks** ,但Fragment较为复杂,因为Fragment有三种,
即android.app.Fragment、androidx.fragment.app.Fragment、android.support.v4.app.Fragment,因此需要注册各自包下的FragmentManager.FragmentLifecycleCallbacks; - view
- service
- Activity的生命周期监听:注册 Application.ActivityLifecycleCallbacks
class ActivityWatcher(private val application: Application,private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {private val lifecycleCallbacks =object : Application.ActivityLifecycleCallbacks by noOpDelegate() {override fun onActivityDestroyed(activity: Activity) {reachabilityWatcher.expectWeaklyReachable(activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}override fun install() {application.registerActivityLifecycleCallbacks(lifecycleCallbacks)}override fun uninstall() {application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)}
}
4.2 LeakCananry自动检测步骤
-
检测可能泄漏的对象;
-
堆快照,生成hprof文件;
-
分析hprof文件;
-
对泄漏进行分类。
4.2.1 检测泄漏
当在初始化时各种监听注册好之后,就到对象的监听者ObjectWatcher上场了。
利用引用对象可感知对象垃圾回收的机制判定内存泄漏: 为无用对象包装弱引用,并在一段时间后(默认为五秒)观察弱引用是否如期进
入关联的引用队列,是则说明未发生泄漏,否则说明发生泄漏(无用对象被强引用持有,导致无法回收,即泄漏)
@Synchronized fun watch(watchedObject: Any,description: String) {if (!isEnabled()) {return}removeWeaklyReachableObjects()val key = UUID.randomUUID().toString()val watchUptimeMillis = clock.uptimeMillis()val reference =KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)SharkLog.d {"Watching " +(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +(if (description.isNotEmpty()) " ($description)" else "") +" with key $key"}watchedObjects[key] = referencecheckRetainedExecutor.execute {moveToRetained(key)}}
这里其实就用到了弱引用的第二个构造方法,我们注意上面源码中的KeyedWeakReference及queue,如果弱引用关联的的对象被回收,则会把这个弱引用加入到queue中,利用这个机制可以在后续判断对象是否被回收。
我们一起来看看WeakReference构造方法
public WeakReference(T referent) {super(referent);
}/*** 当GC回收对象时,将引用对象回收而将被引用对象放入ReferenceQueue*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);
}
对于检测的入口方法在private fun moveToRetained(key: String),当检测到泄漏后就进行dump,其步骤为:
1、移除不可达对象:移除** ReferenceQueue** 中记录的KeyedWeakReference 对象(引用着监听的对象实例);
2、主动触发GC:回收不可达的对象;
3、再次移除不可达对象:经过一次GC后可以进一步导致只有WeakReference持有的对象被回收;
4、判断是否还有剩余的监听对象存活,且存活的个数是否超过阈值;
5、若满足上面的条件,则抓取Hprof文件
调用的接口为onObjectRetained,可自行跟踪代码查看
fun interface OnObjectRetainedListener {/*** A watched object became retained.*/fun onObjectRetained()...
}
4.2.2 dump
Debug.dumpHprofData(path);
// 最终调用 Debug.dumpHprofData(heapDumpFile.absolutePath) configProvider().heapDumper.dumpHeap(heapDumpFile)
关于hprof文件的内容会比较多,可自行学习
4.2.3 shark分析
Leakcanary2.x版本开源了自己实现的hprof文件解析以及泄漏引用链查找的功能模块(命名为shark),之前使用的是HAHA库,但是存在一些问题。
private fun analyzeHeap(heapDumpFile: File,progressListener: OnAnalysisProgressListener,isCanceled: () -> Boolean
): HeapAnalysis {...// Shark 堆快照分析器val heapAnalyzer = HeapAnalyzer(progressListener)...// 构建对象图信息val sourceProvider = ConstantMemoryMetricsDualSourceProvider(ThrowingCancelableFileSourceProvider(heapDumpFile)val graph = sourceProvider.openHeapGraph(proguardMapping = proguardMappingReader?.readProguardMapping())...// 开始分析heapAnalyzer.analyze(heapDumpFile = heapDumpFile,graph = graph,leakingObjectFinder = config.leakingObjectFinder, // 默认是 KeyedWeakReferenceFinderreferenceMatchers = config.referenceMatchers, // 默认是 AndroidReferenceMatcherscomputeRetainedHeapSize = config.computeRetainedHeapSize, // 默认是 trueobjectInspectors = config.objectInspectors, // 默认是 AndroidObjectInspectorsmetadataExtractor = config.metadataExtractor // 默认是 AndroidMetadataExtractor)
}
4.2.4 生成报告
进过一系列的分析后,就会生成一份报告。
五、 推荐阅读
Java 专栏
SQL 专栏
数据结构与算法
Android学习专栏