LeakCanary是Android开发中常用的用来检测内存泄漏的框架,它能够帮助开发者快速发现是否发生内存泄漏,并可视化的给予提示。
这里基于LeakCanary 2.3版本进行分析。
只需要在工程中进行简单的集成配置,就能自动对Activity、Fragment、fragment View进行自动检测。也可以通过ObjectWatcher#watch方法对指定对象进行检测。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
}
仅需添加一行依赖,就完成了接入工作,当buildTypes为debug时,运行APP就会自动启动LeakCanary。
LeakCanary的核心功能,大致可以分为两块:
检测泄漏功能的主要流程如下:
SDK自初始化
在1.X是通过手动调用方法初始化,2.X实现自动初始化。
自动对Android特定对象进行检测
例如自动监测Activity、Fragment、fragment View生命周期销毁。
检测判断对象是否发生内存泄漏
检查一个对象应该被回收,可能产生内存泄漏而没有被回收。
dump heap至hprof文件并解析文件,生成泄漏引用链
依赖另一个专门分析hprof的库来解析文件和生成分析结果。在1.X是依赖haha库,2.X改成依赖shark库。
在通知栏和Activity界面显示泄漏信息
把泄漏分析结果发送到通知栏和Activity界面中显示可能导致泄漏的对象引用链。
前面看到LeakCanary只需要添加一行依赖,不需要开发者手动调用它的方法,那么它是如何自己把自己启动起来呢?可以猜测通过注册静态广播监听系统广播事件或者注册ContentProvider。
查找LeakCanary开源库中的AndroidManifest.xml,可以发现没有符合的静态注册广播,而存在ContentProvider:
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
位于AndroidManifest.xml
在AppWatcherInstaller中会进行LeakCanary的初始化。
清单中注册的ContentProvider在应用启动的时候就会进行初始化:
[ActivityThread#handleBindApplication]
private void handleBindApplication(AppBindData data) {
// ···
Application app;
// ···
// 反射实例化Application,并会调用其attachBaseContext方法
app = data.info.makeApplication(data.restrictedBackupMode, null);
// ···
// 初始化注册的ContentProvider
installContentProviders(app, data.providers);
// ···
// 触发Application的onCreate回调
mInstrumentation.callApplicationOnCreate(app);
// ···
}
ContentProvider启动后就会回调onCreate方法。
AppWatcherInstaller继承ContentProvider,在其onCreate回调中,调用InternalAppWatcher#install方法进行初始化。
InternalAppWatcher负责SDK的初始化。
// 初始化ObjectWatcher,用于检测传入的对象是否未被及时回收
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { AppWatcher.config.enabled }
)
接着看它的init代码块:
init {
// 通过反射获取InternalLeakCanary实例
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#invoke(Application)方法
onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
}
接着看install方法:
fun install(application: Application) {
SharkLog.logger = DefaultCanaryLog()
// 检查是否在主线程,不在主线程则抛异常
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
val configProvider = { AppWatcher.config }
// Activity自动监测相关初始化
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
// Fragment自动监测相关初始化
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
// 在init代码块中,onAppWatcherInstalled被赋值为InternalLeakCanary#invoke
onAppWatcherInstalled(application)
}
该方法中进行了Activity和Fragment自动监测功能的初始化,最后初始化InternalLeakCanary。
接着看InternalLeakCanary的invoke方法:
override fun invoke(application: Application) {
this.application = application
// 检查是否在DEBUGGABLE启动,若不是则抛异常(除非开启设置)
checkRunningInDebuggableBuild()
// 添加OnObjectRetainedListener监听,单发生对象未及时回收时,会回调onObjectRetained
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// 用于dump heap
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
// 用于主动触发gc
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
// 开辟子线程,创建子线程通信handler
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// 用于调度dump heap
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
// 监听应用前台后台切换(通过Application#registerActivityLifecycleCallbacks监听)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
// 监听当前Resumed的Activity(通过Application#registerActivityLifecycleCallbacks监听)
registerResumedActivityListener(application)
// 初始化Shortcuts(查看泄漏信息的Activity的快捷方式)
addDynamicShortcut(application)
disableDumpHeapInTests()
}
该方法中初始化了核心功能模块的类和各种Activity生命周期监听。
LeakCanary通过注册ContentProvider,实现应用启动时自动初始化,完成核心功能模块的工具类的初始化和Activity、Fragment生命周期的监听注册。
LeakCanary在初始化时调用ActivityDestroyWatcher#install:
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
// 向Application注册ActivityLifecycleCallbacks
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
该方法中就是注册Activity生命周期监听。
接着看这个ActivityLifecycleCallbacks的实现:
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
// watchActivities配置是否支持检测Activity,默认是true
if (configProvider().watchActivities) {
// 通过ObjectWatcher#watch进行检测
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
当Activity销毁时,触发onActivityDestroyed回调,在这里调用ObjectWatcher#watch传入Activity实例和一个描述字符串,进行内存泄漏检查检查。
在初始化完Activity的自动监测后,接着调用FragmentDestroyWatcher#install进行Fragment的监测初始化:
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
) {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
if (SDK_INT >= O) {
// 添加对android.app.Fragment的支持
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
}
// 如果使用了AndroidX库,添加对androidx.fragment.app.Fragment的支持
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
// 如果使用support库,添加对support.v4.app.Fragment的支持
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生命周期监听
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
// 在onActivityCreated回调中,依次对各Watcher进行初始化
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
})
}
该方法中会将不同库的Fragment的Watcher处理类收集到集合中保存,并注册Activity生命周期监听,当触发onActivityCreated回调后,对各Watcher进行初始化。
调用AndroidOFragmentDestroyWatcher#invoke方法传入activity,向该activity的FragmentManager注册FragmentLifecycleCallbacks。
当触发onFragmentViewDestroyed回调时,通过ObjectWatcher#watch检查fragment.mView对象(即Fragment#onCreateView创建的View)。
当触发onFragmentDestroyed回调时,检查fragment对象是否泄漏。
在getWatcherIfAvailable方法中会通过反射创建AndroidXFragmentDestroyWatcher实例,通过supportFragmentManager注册LifecycleCallbacks监听Fragment生命周期。
同android Fragment的监听一样,在onFragmentViewDestroyed检测View,在onFragmentDestroyed检测Fragment。
有一点不同的是,AndroidXFragmentDestroyWatcher在onFragmentCreated回调中,还会初始化ViewModel的监听。当ViewModel执行clear触发onCleared回调时,会检测ViewModel是否泄漏。
反射创建AndroidSupportFragmentDestroyWatcher进行监听,同样通过注册LifecycleCallbacks监听Fragment生命周期,在销毁时检测View和Fragment。
LeakCanary自动监测Activity和Fragment都是通过注册Lifecycle监听生命周期回调,在销毁时检测对象是否被及时回收。正常情况下,生命周期销毁后,对象仍没有被释放,则可能产生了泄漏。
检测一个对象是否泄漏,是LeakCanary的核心功能,是所有流程中的关键流程。LeakCanary中调用ObjectWatcher#watch检测一个对象是否期望应该被释放,但仍然被持有,从而判定其产生泄漏。
在查看watch方法前,先看ObjectWatcher中的两个成员:
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
KeyedWeakReference继承自WeakReference,增加了几个成员,其中有一个key,持有UUID,可通过它定位自身在watchedObjects中的位置。
接下来进入watch方法:
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
// 首先清除watchedObjects和queue中缓存
removeWeaklyReachableObjects()
// 生成UUID作为key
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// 创建KeyedWeakReference,传入key、待检测对象和queue
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集合中
watchedObjects[key] = reference
checkRetainedExecutor.execute {
// 执行检测
moveToRetained(key)
}
}
该方法首先清除watchedObjects和queue中缓存,之后生成key和KeyedWeakReference,缓存至watchedObjects。
注意,KeyedWeakReference不仅保存了待检测对象和key,还保存了引用队列。当Reference的引用队列不为空时,当它包装的对象被回收时,会将自身加入引用队列中。
进入removeWeaklyReachableObjects方法看看如何清理缓存:
private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
// queue中元素不断移出队列
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
// 通过持有的key,从watchedObjects移除对应的KeyedWeakReference
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
回到ObjectWatcher#watch中,方法最后通过checkRetainedExecutor提交任务执行,通过它提交的任务会延迟5s执行:
private val checkRetainedExecutor = Executor {
// 通过主线程handler发送延迟5s的消息(watchDurationMillis默认是5s)
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
当延迟5s后将会在主线程执行moveToRetained方法,接下来进入这个方法:
@Synchronized private fun moveToRetained(key: String) {
// 清理queue和watchedObjects缓存
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
// 判断取出的元素不为空,若不为空则可能存在泄漏
if (retainedRef != null) {
// 记录当前时间
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 回调OnObjectRetainedListener接口,在InternalLeakCanary初始化时注册
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
该方法中首先清理queue和watchedObjects缓存,若对象已经被回收,则对应的KeyedWeakReference会加入queue中,从而也会将watchedObjects中对应元素移除。若在清理后仍能从watchedObjects中查找到元素,说明对象未被回收,可能发生泄漏。
若对象仍未被回收,则回调执行InternalLeakCanary#onObjectRetained方法,在该方法中又会调用HeapDumpTrigger#onObjectRetained方法:
fun onObjectRetained() {
scheduleRetainedObjectCheck(
reason = "found new object retained",
rescheduling = false
)
}
private fun scheduleRetainedObjectCheck(
reason: String,
rescheduling: Boolean,
delayMillis: Long = 0L
) {
// 省略避免发送handler消息的判断 ···
backgroundHandler.postDelayed({
checkScheduledAt = 0
// 检查对象是否仍被持有
checkRetainedObjects(reason)
}, delayMillis)
}
5s后对象仍未被回收,则通过handler切换到子线程再次检查,进入HeapDumpTrigger#checkRetainedObjects方法:
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
if (!config.dumpHeap) {
SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
return
}
// 先再清理一次集合缓存,然后获取watchedObjects中经过初步检测后,仍存在的元素个数
var retainedReferenceCount = objectWatcher.retainedObjectCount
// 判断是否存在泄漏对象个数
if (retainedReferenceCount > 0) {
// 通过Runtime.getRuntime().gc()立即触发GC,然后sleep 100ms
gcTrigger.runGc()
// GC后再次查询未被回收的对象个数
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 判断泄漏对象个数是否达到阈值(阈值默认5个)
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
// 省略调试模式判断部分 ···
// 省略两次dump时间间隔判断部分 ···
SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
// cancel通知
dismissRetainedCountNotification()
// 执行dump heap
dumpHeap(retainedReferenceCount, retry = true)
}
该方法中会通过Runtime.getRuntime().gc()立即触发一次GC,然后等100ms再次检查对象是否被释放。若仍存在对象未被释放,则判断个数是否达到阈值(5个)、是否不处于调试中、距离前一次dump是否超过60s,都满足的话就会进行dump heap。
LeakCanary检查一个期望不再被引用的对象是否泄漏是通过WeakReference和ReferenceQueue配合使用。
当判定存在内存泄漏后,会调用HeapDumpTrigger#dumpHeap方法:
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
// 生成hprof文件
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
// 省略dump失败的重试和提醒部分 ···
}
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
// 清除watchedObjects集合中早于heapDumpUptimeMillis的元素
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// 发送文件到HeapAnalyzerService解析
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
该方法中将通过AndroidHeapDumper#dumpHeap生成hprof文件:
override fun dumpHeap(): File? {
// 创建目标存储文件(有读写权限会存在sdcard,否则存在app目录下,文件命名规则yyyy-MM-dd_HH-mm-ss_SSS.hprof)
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
// ···
return try {
// dump heap
Debug.dumpHprofData(heapDumpFile.absolutePath)
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
null
} else {
// 写入成功返回hprof文件
heapDumpFile
}
} catch (e: Exception) {
SharkLog.d(e) { "Could not dump heap" }
// Abort heap dump
null
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
该方法中调用系统Debug#dumpHprofData方法进行堆转储,保存在yyyy-MM-dd_HH-mm-ss_SSS.hprof命名规则的文件中,最后成功写入返回文件。
关于hprof协议格式可以参考hprof manual、堆转储HPROF协议。
hprof文件中分为header和records两部分,其中records由多个Record组成。
而Record又由4部分组成:
- TAG:Record类型
- TIME:时间戳
- LENGTH: BODY的字节长度
- BODY:存储的数据,例如trace、object、class、thread等信息
解析hprof文件主要就是根据TAG,创建对应的集合保存信息在内存中。
当生成hprof文件后,便会启动HeapAnalyzerService进行解析。HeapAnalyzerService继承自IntentService,在它的onHandleIntent方法中又会调用onHandleIntentInForeground方法:
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
// 解析并将结果保存在HeapAnalysis中
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
// 将解析结果回调出去
config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
}
该方法中从intent取出hprof文件后,调用analyzeHeap方法进行解析,结果将会保存在HeapAnalysis中,最后通过接口回调出去。
在analyzeHeap方法中,会创建HeapAnalyzer,执行真正的解析操作。(若apk有混淆,还可以应用混淆mapping文件)。
看HeapAnalyzer#analyze方法:
fun analyze(
heapDumpFile: File,
leakingObjectFinder: LeakingObjectFinder,
referenceMatchers: List<ReferenceMatcher> = emptyList(),
computeRetainedHeapSize: Boolean = false,
objectInspectors: List<ObjectInspector> = emptyList(),
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
proguardMapping: ProguardMapping? = null
): HeapAnalysis {
val analysisStartNanoTime = System.nanoTime()
// ···
return try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
// 打开文件、读取文件头
Hprof.open(heapDumpFile)
.use { hprof ->
// 生成hprof文件中Record关系图,用HprofHeapGraph对象保存
val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
val helpers =
FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
// 构建从GC Roots到检测对象的最短引用路径,并返回结果
helpers.analyzeGraph(
metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
)
}
} catch (exception: Throwable) {
// 异常,返回失败结果
HeapAnalysisFailure(
heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
HeapAnalysisException(exception)
)
}
}
该方法中首先打开hprof文件,读取文件头信息。之后解析文件,创建HprofHeapGraph保存指定TAG的Record信息到gcRoots(GC Roots信息)、objects(内存中对象信息)、classes(类信息)、instances(实例信息)集合中。
之后匹配待检测对象和HprofHeapGraph.objects找到对应对象ID。然后从GC Roots开始通过BFS查找到检测对象的调用路径,期间会根据LeakCanaryCore自带的白名单剔除名单中的类。最后从众多路径中找到一条最短引用路径,将结果保存在HeapAnalysisSuccess中返回。
HeapAnalysisSuccess中有applicationLeaks和libraryLeaks两个集合,分别保存应用代码中的泄漏路径和依赖库代码中的泄漏路径。
首先调用系统Debug#dumpHprofData方法生成hprof文件,之后通过shark库进行解析,构建GC Roots到检测未被释放对象的最短引用路径。
回到HeapAnalyzerService#onHandleIntentInForeground中,当执行完analyzeHeap方法,拿到结果后(成功返回HeapAnalysisSuccess,失败返回HeapAnalysisFailure),传给onHeapAnalyzedListener接口的onHeapAnalyzed回调方法。
onHeapAnalyzedListener的默认实现类是DefaultOnHeapAnalyzedListener,看它的onHeapAnalyzed方法:
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) {
showToast(heapAnalysis)
printIntentInfo()
} else {
// 通知栏提示
showNotification(screenToShow, contentTitle)
}
}
该方法中根据解析结果创建对应的Screen,Screen包含解析结果和渲染View的逻辑。之后创建跳转LeakActivity的PendingIntent,显示通知栏提示。当点击通知栏时打开LeakActivity,通过Screen来渲染结果视图。
LeakCanaryCore的核心原理就是通过WeakReference和ReferenceQueue的组合,来检测期望被回收的对象。当期望被回收的对象没有被释放,就会dump heap生成hprof文件,借由shark库进行解析,构建泄漏对象最短引用路径。最后将结果在通知栏和Activity进行渲染显示。