LeakCanary内部用到了Refercence及ReferenceQueue来实现对对象是否被回收的监听。这是LeakCanary的核心逻辑,因此在讲解LeakCanary之前,我们先来简单了解一下Refercence及ReferenceQueue。
1、Refercence及ReferenceQueue
1.1、基本概念
Reference即引用,是一个泛型抽象类。Android中的SoftReference(软引用)、WeakReference(弱引用)、PhantomReference(虚引用)都是继承自Reference。来看下Reference的几个主要成员变量。
代码语言:java复制public abstract class Reference<T> {
// 引用对象,被回收时置null
volatile T referent;
//保存即将被回收的reference对象
final ReferenceQueue<? super T> queue;
//在Enqueued状态下即引用加入队列时,指向下一个待处理Reference对象,默认为null
Reference queueNext;
//在Pending状态下,待入列引用,默认为null
Reference<?> pendingNext;
}
Reference有四种状态:Active、Pending、Enqueued、Inactive。声明的时候默认Active状态。
ReferenceQueue则是一个单向链表实现的队列数据结构,存储的是Reference对象。包含了入列enqueue、出列poll和移除remove操作。
1.2、对象回收监听
Reference配合ReferenceQueue就可以实现对象回收监听了,先通过一个示例来看看是怎么实现的。
代码语言:java复制//创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();
//创建弱引用,并关联引用队列queue
WeakReference reference = new WeakReference(new Object(),queue);
System.out.println(reference);
System.gc();
//当reference被成功回收后,可以从queue中获取到该引用
System.out.println(queue.remove());
示例中的对象当然是可以正常回收的,所以回收后可以在关联的引用队列queue中获取到该引用。反之,若某个应该被回收的对象,GC结束后在queue中未找到该引用,则表明该引用存在内存泄漏风险,这也就是LeakCanary的基本原理了。
2、LeakCanary基本原理
为了更好的对LeakCanary源码进行分部解析,我们先对LeakCanary实现内存泄漏的整体过程做一个概括。后面在分部对整个流程的源码进行解析。
- 初始化。
- 添加相关监听对象销毁监听。LeakCanary会默认监听Activity、Fragment、Fragment的View、ViewModel是否回收。
- 收到销毁回调后,根据要回收对象创建KeyedWeakReference和ReferenceQueue,并关联。
- 延迟5秒检查相关对象是否被回收。
- 如果没有被回收就通过dump heap获取hprof文件。
- 通过Shark库解析hprof文件,获取泄漏对象,被计算泄漏对象到GC roots的最短路径。
- 合并多个泄漏路径并输出分析结果。
- 将结果展示到可视化界面。
3、LeakCanary源码解析
在2.0之后的版本只需要在build.gradle引入项目就完事了
代码语言:java复制debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
3.1、初始化
2.0之前的版本接入过程除了在build.gradle中引入项目外,还需要调用LeakCanary.install(this),来进行初始化工作。一般都是在Application的onCreate()方法中调用。
在2.0之后的版本只需要在build.gradle引入项目就完事了。那么问题来了:2.0之后的版本初始化工作是在哪里完成的呢?
找了许久,终于在项目工程:leakcanary-object-watcher-android
的manifest文件中发现了秘密:
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
这里注册了一个继承自ContentProvider的AppWatcherInstaller。我们知道在app启动时,会先调用注册的ContentProvider的onCreate完成初始化,在AppWatcherInstaller.onCreate中果然找到了熟悉的install方法:
代码语言:java复制override fun onCreate(): Boolean {
//获取application
val application = context!!.applicationContext as Application
//具体的初始化方法
AppWatcher.manualInstall(application)
return true
}
调用链:AppWatcher.manualInstall–>InternalAppWatcher.install。具体的初始化逻辑是在InternalAppWatcher,来看源码:
代码语言:java复制fun install(application: Application) {
//确保在主线程,否则抛出UnsupportedOperationException异常
checkMainThread()
//判断application是否初始化,application是lateinit修饰的延迟初始化变量,
if (this::application.isInitialized) {
return
}
//leakcanary日志初始化
SharkLog.logger = DefaultCanaryLog()
InternalAppWatcher.application = application
//日志配置初始化
val configProvider = { AppWatcher.config }
//Activity内存泄漏监听器初始化
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
//Fragment内存泄漏监听器初始化
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
//注册内存泄漏事件回调
onAppWatcherInstalled(application)
}
ContentProvider的核心方法CURD在AppWatcherInstaller都是空实现,只用到了onCreate。需要注意的是ContentProvider.onCreate调用时机介于Application的attachBaseContext和onCreate之间,所以不能依赖之后初始化的其他SDK。
在初始过程中,分别创建了针对Activity及Fragment的监听器。
3.2、Activity 回收监听
代码语言:java复制companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
//实例化ActivityDestroyWatcher
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
//注册ActivityLifecycle监听
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
registerActivityLifecycleCallbacks是Android Application的一个方法,注册了该方法,可以通过回调获取app中每一个Activity的生命周期变化。再来看看ActivityDestroyWatcher对生命周期回调的处理:
代码语言:java复制private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
//通过objectWatcher监听改activity是否被销毁回收
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
ActivityLifecycleCallbacks生命周期回调有那么多,为什么只用重写其中一个?关键在于by noOpDelegate(),通过类委托机制将其他回调实现都交给noOpDelegate,而noOpDelegate是一个空实现的动态代理。新姿势get 1,在遇到只需要实现接口的部分方法时,就可以这么玩了,其他方法实现都委托给空实现代理类就好了。
3.3、Fragment 回收监听
来看一下FragmentDestroyWatcher.install的实现:
代码语言:java复制fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
) {
//fragmentDestroyWatchers列表,支持不同Fragment实例的检测;
//这里的watcher都继承自(Activity)->Unit表示方法类型/函数类型,
//参数为Activity,返回值为空;因为是方法类型所以需要重写invoke方法
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
//Android O后构建AndroidOFragmentDestroyWatcher
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
}
//如果Class.for(className)能找到androidx.fragment.app.Fragment和
//leakcanary.internal.AndroidXFragmentDestroyWatcher则添加AndroidXFragmentDestroyWatcher则添加
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
//如果Class.for(className)能找到android.support.v4.app.Fragment和
//leakcanary.internal.AndroidSupportFragmentDestroyWatcher则添加AndroidSupportFragmentDestroyWatcher
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
if (fragmentDestroyWatchers.size == 0) {
return
}
//注册Activity生命周期回调,在Activity的onActivityCreated()方法中遍历这些watcher方法类型,实际调用的是对应的invoke方法
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
//实际调用的是对应的invoke方法
watcher(activity)
}
}
})
}
如果系统是Android O以后版本,使用AndroidOFragmentDestroyWatcher,如果app使用的是androidx中的fragment,则添加对应的AndroidXFragmentDestroyWatcher,如果使用support库中的fragment,则添加AndroidSupportFragmentDestroyWatcher。最终在invoke方法中使用对应的fragmentManager注册Fragment的生命周期回调,在onFragmentViewDestroyed()和onFragmentDestroyed()方法中使用ObjectWatcher来检测fragment。下面以AndroidXFragmentDestroyWatcher为例:
先看一下AndroidXFragmentDestroyWatcher的invoke方法实现:
代码语言:java复制override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
//取得对应的FragmentManager,注册生命周期回调
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
//添加了ViewModelStoreOwner为Activity的ViewModelClearedWatcher监测
ViewModelClearedWatcher.install(activity, objectWatcher, configProvider)
}
}
Fragment 回收监听
LeakCanary在onFragmentDestroyed回调里面来处理检查Fragment是否正常被回收的检测逻辑。
代码语言:java复制override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
Fragment的View 回收监听
LeakCanary在onFragmentViewDestroyed回调里面来处理检查Fragment的View是否正常被回收的检测逻辑。
代码语言:java复制override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback "
"(references to its views should be cleared to prevent leaks)"
)
}
}
ViewModel 回收监听
从AndroidXFragmentDestroyWatcher的invoke实现可以发现,LeakCanary增加了对ViewModel的检测
ViewModelClearedWatcher继承自ViewModel,里面使用viewModelMap来存储ViewModelStoreOwner中的ViewModel,并使用伴生对象来初始化自己,关联到ViewModelStoreOwner;在onCleared()方法中使用ObjectWatcher来监测。
代码语言:java复制override fun onCleared() {
if (viewModelMap != null && configProvider().watchViewModels) {
viewModelMap.values.forEach { viewModel ->
objectWatcher.watch(
viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}
}
4、对象是否回收检测
从上面分析可知,LeakCanary最后都是通过ObjectWatcher.watch()来监测Activity、Fragment、Fragment的View、ViewModel。我们这里以Activity为例进行分析,Fragment除了生命周期监听方式不同外后面的流程都是一样的。
ObjectWatcher.watch() 监听目标对象是否正常回收
来看一下ObjectWatcher.watch()的实现:
代码语言:java复制@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
//@1.清空queue,即移除之前已回收的引用
removeWeaklyReachableObjects()
//生成随机的key值,用来作为保存由检测对象创建的KeyedWeakReference的key
val key = UUID.randomUUID().toString()
//记录当前时间
val watchUptimeMillis = clock.uptimeMillis()
//将当前Activity对象封装成KeyedWeakReference,并关联引用队列queue
//KeyedWeakReference继承自WeakReference,封装了用于监听对象的辅助信息
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"
}
//将弱引用reference存入监听列表watchedObjects
watchedObjects[key] = reference
//@2.进行一次后台检查任务,判断引用对象是否未被回收
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
@1:清空queue,即移除之前已回收的引用。
这个方法很重要,第一次调用是清除之前的已回收对象,后面还会再次调用该方法判断引用是否正常回收。
这里涉及到的两个重要变量:
- queue 即引用队列ReferenceQueue
- watchedObjects 所有监听Reference对象的map,key为引用对象对应的UUID,value为Reference对象
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference?
do {
//遍历引用队列
ref = queue.poll() as KeyedWeakReference?
//将引用队列中的Reference对象从监听列表watchedObjects中移除
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
@2:进行一次后台检查任务moveToRetained,5秒后判断引用对象是否未被回收。
该任务是延迟5s后执行的。
代码语言:java复制private val checkRetainedExecutor = Executor {
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
moveToRetained() 执行检测目标是否被回收
来看下moveToRetained方法实现:
代码语言:java复制@Synchronized private fun moveToRetained(key: String) {
//遍历引用队列,并将引用队列中的引用从监听列表watchedObjects中移除
removeWeaklyReachableObjects()
//若对象未能成功移除,则表明引用对象可能存在内存泄漏。还能获取到则表明为移除成功。
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//@3.onObjectRetainedListeners内存泄漏事件回调
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
在这里理一下moveToRetained的处理逻辑:
- 正常情况:Activity对象被GC回收掉进入引用队列queue,通过removeWeaklyReachableObjects方法遍历queue获取该引用对象后,将其从监听列表watchedObjects中移除。所以watchedObjects[key]也就无法获取到引用对象了。
- 异常情况:Activity对象onDestroy后未能被GC回收掉,所以在引用队列queue中也就找不到该对象,也就是说监听列表watchedObjects中该对象没有被删掉。通过watchedObjects[key]可以拿到该引用对象,即可以判断该引用对象存在内存泄漏问题。
@3:onObjectRetainedListeners内存泄漏事件回调
发现内存泄漏对象后会调用onObjectRetainedListeners监听回调,进行后续处理。那么这个onObjectRetainedListeners是在哪里实现的呢?
onObjectRetainedListeners 目标对象未被回收监听回调的实现
在前面InternalAppWatcher.install初始化时,InternalAppWatcher的初始化方法onAppWatcherInstalled()中初始化了该监听。
代码语言:java复制init {
val internalLeakCanary = try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null)
} catch (ignored: Throwable) {
NoLeakCanary
}
@kotlin.Suppress("UNCHECKED_CAST")
onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
}
我们发现这里通过反射获取InternalLeakCanary.INSTANCE单列对象,这个类位于另一个包leakcanary-android-core,所以用了反射。由于InternalLeakCanary是一个函数对象,onAppWatcherInstalled()对应的调用方法为invoke()来完成监听注册。
代码语言:java复制override fun invoke(application: Application) {
_application = application
//检查是否debug构建模式
checkRunningInDebuggableBuild()
//注册监听
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
//创建AndroidHeapDumper对象,用于虚拟机dump hprof产生内存快照文件
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
//GcTrigger通过Runtime.getRuntime().gc()触发GC
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
//创建子线程及对应looper
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
//创建HeapDumpTrigger,后面会调用HeapDumpTrigger的一些列方法,比如dumpHeap()来创建hprof文件
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
//注册应用可见监听
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
addDynamicShortcut(application)
disableDumpHeapInTests()
}
checkRetainedObjects() 收到目标对象未被回收后的处理
当ObjectWatcher中moveToRetained发现未回收对象后,通过回调onObjectRetained()处理时,调用的就是这里注册的HeapDumpTrigger.onObjectRetained()。处理调用链较长,直接看关键方法:
–>onObjectRetained–>scheduleRetainedObjectCheck–>checkRetainedObjects
代码语言:java复制private fun checkRetainedObjects(reason: String) {
...//代码省略
//监听器中未回收对象个数
var retainedReferenceCount = objectWatcher.retainedObjectCount
//执行一次GC,再更新未回收对象个数
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
//若对象个数未达到阈值5,返回
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
...//代码省略,60s内只会执行一次
//核心方法,获取内存快照
dumpHeap(retainedReferenceCount, retry = true)
}
dumpHeap() 获取内存快照,生成hprof文件
来看下dumpHeap方法实现:
代码语言:java复制private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean
) {
...//代码省略
//获取当前内存快照hprof文件
val heapDumpFile = heapDumper.dumpHeap()
...//省略hprof获取失败处理
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
//清理之前注册的监听
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
//开启hprof分析Service,解析hprof文件生成报告
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
5、hprof文件解析
在上面讲到的内存泄漏回调处理中,生成了hprof文件,并开启一个服务来解析该文件。在Service的onHandleIntentInForeground回调方法中进行hprof文件解析
代码语言:java复制override fun onHandleIntentInForeground(intent: Intent?) {
if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
return
}
// Since we're running in the main process we should be careful not to impact it.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
val config = LeakCanary.config
//解析hporf文件,返回heapAnalysis
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
//@6:解析完成回调,这个回调函数的实现类是DefaultOnHeapAnalyzedListener
config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
}
调用链:HeapAnalyzerService.analyzeHeap–>HeapAnalyzer.analyze。该方法实现了解析hprof文件找到内存泄漏对象,并计算对象到GC roots的最短路径,输出报告。
代码语言:java复制fun analyze(.../*参数省略*/): HeapAnalysis {
...//代码省略
return try {
//PARSING_HEAP_DUMP解析状态回调
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
//开始解析hprof文件
Hprof.open(heapDumpFile)
.use { hprof ->
//从文件中解析获取对象关系图结构graph
//并获取图中的所有GC roots根节点
val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
//创建FindLeakInput对象
val helpers =
FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
//@4.查找内存泄漏对象
helpers.analyzeGraph(
metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
)
}
} catch (exception: Throwable) {
...//省略解析异常处理
}
@4:查找内存泄漏对象
代码语言:java复制private fun FindLeakInput.analyzeGraph(.../*参数省略*/): HeapAnalysisSuccess {
...//代码省略
//通过过滤graph中的KeyedWeakReference类型对象来
//找到对应的内存泄漏对象
val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
//@5.计算内存泄漏对象到GC roots的路径
val (applicationLeaks, libraryLeaks) = findLeaks(leakingObjectIds)
//输出最终hprof分析结果
return HeapAnalysisSuccess(
heapDumpFile = heapDumpFile,
createdAtTimeMillis = System.currentTimeMillis(),
analysisDurationMillis = since(analysisStartNanoTime),
metadata = metadata,
applicationLeaks = applicationLeaks,
libraryLeaks = libraryLeaks
)
}
@5:计算内存泄漏对象到GC roots的路径
代码语言:java复制private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
val pathFinder = PathFinder(graph, listener, referenceMatchers)
//计算并获取目标对象到GC roots的最短路径
val pathFindingResults =
pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
SharkLog.d { "Found ${leakingObjectIds.size} retained objects" }
//将这些内存泄漏对象的最短路径合并成树结构返回。
return buildLeakTraces(pathFindingResults)
}
最终在可视化界面中将hprof分析结果HeapAnalysisSuccess展示出来。
@6:将内存泄漏信息通知用户
onHeapAnalyzedListener.onHeapAnalyzed的实现类是DefaultOnHeapAnalyzedListener,来看下具体实现:
代码语言:java复制override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
SharkLog.d { "$heapAnalysis" }
val id = LeaksDbHelper(application).writableDatabase.use { db ->
HeapAnalysisTable.insert(db, heapAnalysis)
}
val (contentTitle, screenToShow) = when (heapAnalysis) {
is HeapAnalysisFailure -> application.getString(
R.string.leak_canary_analysis_failed
) to HeapAnalysisFailureScreen(id)
is HeapAnalysisSuccess -> {
val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
val leakTypeCount = heapAnalysis.applicationLeaks.size heapAnalysis.libraryLeaks.size
application.getString(
R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
) to HeapDumpScreen(id)
}
}
if (InternalLeakCanary.formFactor == TV) {
//toast形式
showToast(heapAnalysis)
printIntentInfo()
} else {
//通知的形式
showNotification(screenToShow, contentTitle)
}
}
6、总结
最后来总结下LeakCanary内存泄漏分析过程吧(Activity):
(1)注册监听Activity生命周期onDestroy事件
(2)在Activity onDestroy事件回调中创建KeyedWeakReference对象,并关联ReferenceQueue
(3)延时5秒检查目标对象是否回收
(4)未回收则开启服务,dump heap获取内存快照hprof文件
(5)解析hprof文件根据KeyedWeakReference类型过滤找到内存泄漏对象
(6)计算对象到GC roots的最短路径,并合并所有最短路径为一棵树
(7)输出分析结果,并根据分析结果展示到可视化页面
除了这些外,LeakCanary中代码风格同样值得学习,包括巧用ContentProvider初始化,kolint类委托进行选择性方法实现等。