LeakCanary
文章目录
一、内容
LeakCanary 是在 Android 项目中,用于检测内存泄露,优化性能的工具。
1. 使用方法
本文使用版本为 2.5 版本,相比于 2.0 之前的版本,2.0 之后的版本在使用上简洁了很多,只需要在 dependencies 添加 LeakCanary 依赖项即可。
bash
dependencies {
debugImplementation ("com.squareup.leakcanary:leakcanary-android:2.5")
}
之后运行需要检测的项目,就可在控制台看到 LeakCanary 在启动时正在运行的打印信息:
java
D/LeakCanary: LeakCanary is running and ready to detect memory leaks.
运行测试应用后桌面会出现两个图标:左侧的是测试应用的图标,右侧是自动安装的 LeakCanary 的图标。
2. 工作原理
LeakCanary 本质上是一个基于 MAT 进行 Android 应用程序内存泄漏自动化检测的的开源工具,我们可以通过集成 LeakCanary 提供的 jar 包到自己的工程中,一旦检测到内存泄漏,LeakCanary 就会 dump Memory 信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题。
LeakCanary 是使用了 ContentProvider 进行初始化,即在打包的过程中来自不同 module 的 ContentProvider最后都会 merge 到一个文件中,启动 app 的时候 ContentProvider 是自动安装,利用它安装比 Application 的 OnCreate 还早来实现对 Activity 整个生命周期的监听。
3.工作流程
1)检测保留对象
LeakCanary 自动检测四种对象的泄露:1. 销毁的 Activity 实例;2. 销毁的 Fragment 实例;3. 销毁的片段 View 实例;4. 清除 ViewModel 实例。
LeakCanary 基于 ObjectWatcher Android 的 library,它与 Android 的生命周期挂钩,当 Activity 和 Fragment 被销毁并且应该被做为垃圾回收时自动检测。应当被销毁的对象传递给 ObjectWatcher,ObjectWatcher 持有这些被销毁对象的弱引用,如果弱引用在等待 5 秒钟后运行垃圾收集器后发现被销毁对象仍未被清除,那么被观察者就认为这些对象在生命周期结束后仍保留,并存在潜在的泄露,LeakCanary 就会在 Logcat 中输出日志。
java
09-26 13:07:18.785 23731 23731 D LeakCanary: LeakCanary is running and ready to detect memory leaks.
09-26 13:07:20.368 23731 23753 D LeakCanary: Setting up flushing for Thread[LeakCanary-Heap-Dump,5,main]
09-26 13:07:20.369 23731 23753 D LeakCanary: Setting up flushing for Thread[queued-work-looper,5,main]
09-26 13:07:20.369 23731 23753 D LeakCanary: Setting up flushing for Thread[InsetsAnimations,5,main]
09-26 13:07:21.062 23731 23731 D LeakCanary: Watching instance of androidx.fragment.app.FragmentManagerViewModel (androidx.fragment.app.FragmentManagerViewModel received ViewModel#onCleared() callback) with key f676ad97-f211-4a87-a7f8-55d501e03bc7
09-26 13:07:21.062 23731 23731 D LeakCanary: Watching instance of androidx.lifecycle.SavedStateHandlesVM (androidx.lifecycle.SavedStateHandlesVM received ViewModel#onCleared() callback) with key 4438d66f-e78c-47c0-a494-644154121e99
09-26 13:07:21.062 23731 23731 D LeakCanary: Watching instance of leakcanary.internal.ViewModelClearedWatcher (leakcanary.internal.ViewModelClearedWatcher received ViewModel#onCleared() callback) with key 25e62c20-6dcd-4a0d-9c63-68b0745eeadf
09-26 13:07:21.063 23731 23731 D LeakCanary: Watching instance of androidx.lifecycle.ReportFragment (androidx.lifecycle.ReportFragment received Fragment#onDestroy() callback) with key 76e52b86-4f2f-47e9-ab40-63c2dd2eac8a
09-26 13:07:21.064 23731 23731 D LeakCanary: Watching instance of com.lambda.binder.activity.MyClientActivity (com.lambda.binder.activity.MyClientActivity received Activity#onDestroy() callback) with key ecfabc8d-7c5b-4c83-b910-d9c0401587b8
09-26 13:07:26.273 23731 23754 D LeakCanary: Found 4 objects retained, dumping heap now (app is invisible)
09-26 13:07:26.304 23731 23754 D LeakCanary: Removing 1 heap dumps
09-26 13:07:29.900 23731 23777 D LeakCanary: Analysis in progress, working on: PARSING_HEAP_DUMP
09-26 13:07:31.353 23731 23777 D LeakCanary: Analysis in progress, working on: EXTRACTING_METADATA
09-26 13:07:31.405 23731 23777 D LeakCanary: Analysis in progress, working on: FINDING_RETAINED_OBJECTS
09-26 13:07:31.555 23731 23777 D LeakCanary: Analysis in progress, working on: FINDING_PATHS_TO_RETAINED_OBJECTS
09-26 13:07:32.896 23731 23753 D LeakCanary: Setting up flushing for Thread[IntentService[HeapAnalyzerService],5,main]
09-26 13:07:35.002 23731 23777 D LeakCanary: Analysis in progress, working on: FINDING_DOMINATORS
09-26 13:07:35.084 23731 23777 D LeakCanary: Found 4 retained objects
09-26 13:07:35.085 23731 23777 D LeakCanary: Found 4 paths to retained objects, down to 1 after removing duplicated paths
09-26 13:07:35.085 23731 23777 D LeakCanary: Analysis in progress, working on: INSPECTING_OBJECTS
09-26 13:07:35.099 23731 23777 D LeakCanary: Analysis in progress, working on: COMPUTING_NATIVE_RETAINED_SIZE
09-26 13:07:35.355 23731 23777 D LeakCanary: Analysis in progress, working on: COMPUTING_RETAINED_SIZE
09-26 13:07:35.543 23731 23777 D LeakCanary: Analysis in progress, working on: BUILDING_LEAK_TRACES
09-26 13:07:35.554 23731 23777 D LeakCanary: Analysis in progress, working on: REPORTING_HEAP_ANALYSIS
09-26 13:07:35.577 23731 23777 D LeakCanary:
09-26 13:07:35.577 23731 23777 D LeakCanary: ====================================
09-26 13:07:35.577 23731 23777 D LeakCanary: HEAP ANALYSIS RESULT
09-26 13:07:35.577 23731 23777 D LeakCanary: ====================================
09-26 13:07:35.577 23731 23777 D LeakCanary: 1 APPLICATION LEAKS
09-26 13:07:35.577 23731 23777 D LeakCanary:
09-26 13:07:35.577 23731 23777 D LeakCanary: References underlined with "~~~" are likely causes.
09-26 13:07:35.577 23731 23777 D LeakCanary: Learn more at https://squ.re/leaks.
09-26 13:07:35.577 23731 23777 D LeakCanary:
09-26 13:07:35.577 23731 23777 D LeakCanary: 169456 bytes retained by leaking objects
09-26 13:07:35.577 23731 23777 D LeakCanary: Signature: 37f15b5a51deab884265aa64df7c089bb978ef5
09-26 13:07:35.577 23731 23777 D LeakCanary: ┬───
09-26 13:07:35.577 23731 23777 D LeakCanary: │ GC Root: System class
09-26 13:07:35.577 23731 23777 D LeakCanary: │
09-26 13:07:35.577 23731 23777 D LeakCanary: ├─ android.provider.FontsContract class
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Leaking: NO (Application↓ is not leaking and a class is never leaking)
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ↓ static FontsContract.sContext
09-26 13:07:35.577 23731 23777 D LeakCanary: ├─ android.app.Application instance
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Leaking: NO (Application is a singleton)
09-26 13:07:35.577 23731 23777 D LeakCanary: │ mBase instance of android.app.ContextImpl, not wrapping known Android context
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ↓ Application.mLoadedApk
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ~~~~~~~~~~
09-26 13:07:35.577 23731 23777 D LeakCanary: ├─ android.app.LoadedApk instance
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Leaking: UNKNOWN
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Retaining 1152 bytes in 23 objects
09-26 13:07:35.577 23731 23777 D LeakCanary: │ mApplication instance of android.app.Application
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ↓ LoadedApk.mReceivers
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ~~~~~~~~~~
09-26 13:07:35.577 23731 23777 D LeakCanary: ├─ android.util.ArrayMap instance
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Leaking: UNKNOWN
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Retaining 689 bytes in 11 objects
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ↓ ArrayMap.mArray
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ~~~~~~
09-26 13:07:35.577 23731 23777 D LeakCanary: ├─ java.lang.Object[] array
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Leaking: UNKNOWN
09-26 13:07:35.577 23731 23777 D LeakCanary: │ Retaining 648 bytes in 9 objects
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ↓ Object[].[0]
09-26 13:07:35.577 23731 23777 D LeakCanary: │ ~~~
09-26 13:07:35.577 23731 23777 D LeakCanary: ╰→ com.lambda.binder.activity.MyClientActivity instance
09-26 13:07:35.577 23731 23777 D LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.lambda.binder.activity.MyClientActivity received
09-26 13:07:35.577 23731 23777 D LeakCanary: Activity#onDestroy() callback and Activity#mDestroyed is true)
09-26 13:07:35.577 23731 23777 D LeakCanary: Retaining 169456 bytes in 3671 objects
09-26 13:07:35.577 23731 23777 D LeakCanary: key = ecfabc8d-7c5b-4c83-b910-d9c0401587b8
09-26 13:07:35.577 23731 23777 D LeakCanary: watchDurationMillis = 5227
09-26 13:07:35.577 23731 23777 D LeakCanary: retainedDurationMillis = 191
09-26 13:07:35.577 23731 23777 D LeakCanary: mApplication instance of android.app.Application
09-26 13:07:35.577 23731 23777 D LeakCanary: mBase instance of androidx.appcompat.view.ContextThemeWrapper, not wrapping known Android context
09-26 13:07:35.577 23731 23777 D LeakCanary: ====================================
09-26 13:07:35.577 23731 23777 D LeakCanary: 0 LIBRARY LEAKS
09-26 13:07:35.577 23731 23777 D LeakCanary:
09-26 13:07:35.577 23731 23777 D LeakCanary: A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
09-26 13:07:35.577 23731 23777 D LeakCanary: See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
09-26 13:07:35.577 23731 23777 D LeakCanary: ====================================
09-26 13:07:35.577 23731 23777 D LeakCanary: METADATA
09-26 13:07:35.577 23731 23777 D LeakCanary:
09-26 13:07:35.577 23731 23777 D LeakCanary: Please include this in bug reports and Stack Overflow questions.
09-26 13:07:35.577 23731 23777 D LeakCanary:
09-26 13:07:35.577 23731 23777 D LeakCanary: Build.VERSION.SDK_INT: 30
09-26 13:07:35.577 23731 23777 D LeakCanary: Build.MANUFACTURER: Letv
09-26 13:07:35.577 23731 23777 D LeakCanary: LeakCanary version: 2.5
09-26 13:07:35.577 23731 23777 D LeakCanary: App process name: com.lambda.binder
09-26 13:07:35.577 23731 23777 D LeakCanary: Stats: LruCache[maxSize=3000,hits=3074,misses=61336,hitRate=4%]
09-26 13:07:35.577 23731 23777 D LeakCanary: RandomAccess[bytes=2981240,reads=61336,travel=25232087720,range=20535346,size=26457885]
09-26 13:07:35.577 23731 23777 D LeakCanary: Analysis duration: 5652 ms
09-26 13:07:35.577 23731 23777 D LeakCanary: Heap dump file path: /storage/emulated/0/Download/leakcanary-com.lambda.binder/2024-09-26_13-07-26_360.hprof
09-26 13:07:35.577 23731 23777 D LeakCanary: Heap dump timestamp: 1727327255551
09-26 13:07:35.577 23731 23777 D LeakCanary: Heap dump duration: 3485 ms
09-26 13:07:35.577 23731 23777 D LeakCanary: ====================================
并且在手机上也会弹出提示:
2)转储堆
在未被正常清除的对象达到一定数量后,LeakCanary 会将其转储到 Android 文件系统上的 .hprof 文件中。这个数量当应用程序的状态是可见的时候,默认阀值为 5;当应用程序是不可见时,默认阀值为 1。转储堆会使应用程序停止运行一小段时间,并弹出提示。
3)分析堆
LeakCanary 是使用 shark 来解析 .hprof 文件并定位 Java 堆中保留的对象,并对于每个被保留的对象,LeakCanary 会找出阻止该对象被回收的引用链,即泄露路径。确定泄露路径后,LeakCanary 使用它对 Android 框架的了解来找出在泄露路径上是谁泄露了。分析完成之后,LeakCanary 会显示带有摘要的通知,
并将结果打印在 Logcat 中。LeakCanary 会为每个泄露跟踪创建一个签名,并将具有相同签名的泄露(即由相同错误引起的泄露)组合在一起。
点击进去就可看到详细的泄露路径,每个路径中的每个节点都对应着一个 Java 对象。
在泄露路径的顶部是 GC Root,它是一些总是可达的特殊对象。接下来就注意看 Leaking 的状态,分为 YES、NO、UNKNOWN 三种,NO 就是没泄露,UNKNOWN 表示这里可能出现了内存泄漏,YES 表示此处存在泄露。一般推断内存泄露是从最后一个没有泄漏的节点(Leaking:NO)到第一个泄漏的节点(Leaking:YES)之间的引用。
4)分类泄露
LeakCanary 将泄露分为两类:应用程序泄露和库泄露。库泄露是由于第三方库造成,带有 LibraryLeak 标签;应用程序泄露一般是由于程序员异常操作造成。