1 LeakCanary介绍和使用
1.1 LeakCanary简介
在开始介绍LeakCanary源码之前,需要先介绍一些内存泄漏相关的知识。在java的运行时环境中,内存泄漏是一种程序异常,根本原因是程序持有了不再需要的对象引用。以Android应用程序来看,一个Activity实例中存在内部类(内部类会隐式持有外部类引用),在activity执行onDestroy生命周期方法之后,会导致内存出现泄漏。
LeakCanary是一个Android内存泄漏检测库。LeakCanary框架可以协助Android开发者迅速定位内存泄漏,减少开发过程中的卡顿、anr(Android Not Response)、oom(OutOfMemory)等问题,是现代Android开发中一个常用的工具。
1.2 LeakCanary使用方法
LeakCanary的项目地址为github.com/square/leak... ,它的集成方法非常的简单,首先依然是引入依赖:
kotlin
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
注意要使用debugImplementation,防止线上环境集成后影响用户的体验。其实整个LeakCanary的集成方法就是上面的那行依赖,是不是非常的简单,这就不得不提到LeakCanary使用ContentProvider初始化sdk的技巧了,接下来就看看LeakCanary这个启动的ContentProvider是怎么检测内存泄漏的。
2 LeakCanary源码分析
2.1 LeakCanary内存泄漏主流程分析
在1.2介绍LeakCanary的使用方法时提到LeakCanary启动是依赖ContentProvider初始化,其实这里有一个小知识点,ContentProvider的启动时机是早于Application的onCreate方法执行的,正是基于这样的机制才保证了在应用启动时完成LeakCanary的初始化。在LeakCanary中承担初始化的ContentProvider是leakcanary.internal.MainProcessAppWatcherInstaller。
- 接下来我们看看MainProcessAppWatcherInstaller中做了些什么:
kotlin
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
可以看到在MainProcessAppWatcherInstaller中用到了AppWatcher#manualInstall方法作为实际初始化LeakCanary的入口。
- AppWatcher是一个单例对象,里面有两个方法分别是manualInstall方法和appDefaultWatchers,接下来依次看看这两个方法分别做了些什么,先来看一下manualInstall方法:
kotlin
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
// a、加载LeakCanary
LeakCanaryDelegate.loadLeakCanary(application)
// b、注册监听器
watchersToInstall.forEach {
it.install()
}
// Only install after we're fully done with init.
installCause = RuntimeException("manualInstall() first called here")
}
上面注释了a、b两处,这两处是主要的流程,放在后面的主流程分析中再讲。先把看看函数的默认参数retainedDelayMillis,这个参数用于延迟检测内存泄漏的市场,在后面的内存泄漏检测时会用到。另外一个默认参数watchersToInstall是由AppWatcher#appDefaultWatchers方法返回,先看下appDefaultWatchers方法的实现:
kotlin
fun appDefaultWatchers(
application: Application,
deletableObjectReporter: DeletableObjectReporter = objectWatcher.asDeletableObjectReporter()
): List<InstallableWatcher> {
val resources = application.resources
val watchDismissedDialogs = resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
return listOf(
ActivityWatcher(application, deletableObjectReporter),
FragmentAndViewModelWatcher(application, deletableObjectReporter),
RootViewWatcher(deletableObjectReporter, WindowTypeFilter(watchDismissedDialogs)),
ServiceWatcher(deletableObjectReporter)
)
}
可以看到默认的内存泄漏检测主要是四类
- ActivityWatcher
- FragmentAndViewModelWatcher
- RootViewWatcher
- ServiceWatcher
这些对象都实现了InstallableWatcher接口,注释b处的监听注册就调用了install方法。
- 流程2在介绍manualInstall方法的时候,注释a处LeakCanaryDelegate#loadLeakCanary实际上是一个(Application) -> Unit的函数类型,LeakCanaryDelegate#loadLeakCanary的实现如下:
kotlin
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
这段代码其实写的很让人摸不着头脑,我们来一句一句解析一下。首先,leakCanaryListener实际上是InternalLeakCanary类对象,其中InternalLeakCanary是一个单例。leakCanaryListener.getDeclaredField("INSTANCE")的意思是获取InternalLeakCanary单例的INSTANCE属性。在介绍这个INSTANCE属性之前,需要介绍一个小的Kotlin知识点,在kotlin中使用object关键字直接实现的单例对象,在反编译成java代码后(反编译路径为Tools->Kotlin->Show Kotlin ByteCode->Decompile),会有一个INSTANCE单例属性,如下是笔者自己反编译的InternalLeakCanary类:
java
public final class InternalLeakCanary implements Function1, OnObjectRetainedListener {
@NotNull
public static final InternalLeakCanary INSTANCE = new InternalLeakCanary();
//...省略部分代码
}
那么现在leakCanaryListener.getDeclaredField("INSTANCE").get(null)这句话的意思也就很明显了,实际就是获取了InternalLeakCanary单例对象,另外看下InternalLeakCanary类单例实现了(Application) -> Unit接口,其实现为InternalLeakCanary#invoke方法(对应笔者反编译的java代码中的Function1实现)。
前面介绍了leakCanaryListener.getDeclaredField("INSTANCE").get(null)获取的是InternalLeakCanary对象,那么leakCanaryListener.getDeclaredField("INSTANCE") .get(null) as (Application) -> Unit就是获取InternalLeakCanary的invoke实现,对应到流程2中的注释a处的LeakCanaryDelegate.loadLeakCanary(application)这句话的意思就是调用InternalLeakCanary的类单例invoke方法。
- 流程3介绍了LeakCanaryDelegate.loadLeakCanary(application)这句话的意思是调用了InternalLeakCanary的invoke方法,接下我们看看这个方法的实现里做了些什么:
kotlin
override fun invoke(application: Application) {
_application = application
checkRunningInDebuggableBuild()
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val gcTrigger = GcTrigger.inProcess()
val configProvider = { LeakCanary.config }
// a、启动一个工作线程
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// b、构建HeapDumpTrigger
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger,
configProvider
)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
// c、往桌面添加shortcut(我们集成LeakCanary之后在桌面看到的LeakCanary图标)
LeakCanaryAndroidInternalUtils.addLeakActivityDynamicShortcut(application)
mainHandler.post {
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}
5、前面介绍的部分主要和LeakCanary的启动相关,现在介绍一下LeakCanary是如何进行内存检测的。前面流程2介绍AppWatcher#appDefaultWatchers方法的时候返回了四种类型实例,这里以ActivityWatcher实例为例,开启真正的LeakCanary泄漏检测流程。前面的流程1注释b处调用了ActivityWatcher的intsall方法,这里把方法实际用到的代码展示如下:
kotlin
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
// a、调用了默认的reporter的expectDeletionFor方法
deletableObjectReporter.expectDeletionFor(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
// 注册应用的Activity生命周期监听
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
- 可以看到流程5的注释a处install就是对application进行了Activity生命周期方法监听,然后在应用Activity的onDetroy时执行注释a处的方法,这里我们需要知道deletableObjectReporter的来源,才能知道expectDeletionFor做了什么。前面流程2介绍AppWatcher#appDefaultWatchers方法的时候,第二个参数deletableObjectReporter默认是来自于ObjectWatcher#asDeletableObjectReporter(实际方法实现在ReachabilityWatcher#asDeletableObjectReporter),现在看一下这个asDeletableObjectReporter方法的实现:
kotlin
fun asDeletableObjectReporter(): DeletableObjectReporter =
DeletableObjectReporter { target, reason ->
// a、实际执行的ObjectWatcher#expectWeaklyReachable方法
expectWeaklyReachable(target, reason)
// This exists for backward-compatibility purposes and as such is unable to return
// an accurate [TrackedObjectReachability] implementation.
object : TrackedObjectReachability {
override val isStronglyReachable: Boolean
get() = error("Use a non deprecated DeletableObjectReporter implementation instead")
override val isRetained: Boolean
get() = error("Use a non deprecated DeletableObjectReporter implementation instead")
}
}
- 可以看到流程6中的注释a处是DeletableObjectReporter#expectDeletionFor实际执行的流程,即leakcanary.ObjectWatcher#expectWeaklyReachable方法,那么我们跟进方法内看看
kotlin
override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
// a、获取RetainTrigger触发器,后续泄漏检测流程中会用到
val retainTrigger =
retainedObjectTracker.expectDeletionOnTriggerFor(watchedObject, description)
checkRetainedExecutor.execute {
// b、执行内存泄漏检测
retainTrigger.markRetainedIfStronglyReachable()
}
}
- 流程7的注释a处,除了生成了内存泄漏检测的对象外,还调用ReferenceQueueRetainedObjectTracker#removeWeaklyReachableObjects方法进行弱引用删除,然后对传入的watchedObject(这里实际是Activity实例)进行引用链注册(包装生成弱引用并存入watchedObjects变量map中),接着在注释b处调用markRetainedIfStronglyReachable方法检测弱引用删除剩余的强引用对象。我们跟进markRetainedIfStronglyReachable方法看会发现,其内部直接调用了leakcanary.ReferenceQueueRetainedObjectTracker#moveToRetained方法,moveToRetained方法实现如下:
kotlin
private fun moveToRetained(key: String) {
// a、删除弱引用对象
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptime().inWholeMilliseconds
// b、继续检测可能的内存泄漏
onObjectRetainedListener.onObjectRetained()
}
}
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 {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != nul
}
- 上面流程8中我列出了两个方法,先看removeWeaklyReachableObjects方法的实现,实际是从引用链队列queue(前面activit包装成KeyedWeakReference的时候传入WeakReference构造参数)中取出注册的弱引用对象并循环移除。然后在上面的流程8注释b处,当watchedObjects这个map中剩余的弱引用对象中还有这个对象的引用队列时,说明可能存在内存泄漏,接下来的方法调用链是leakcanary.internal.InternalLeakCanary#onObjectRetained-> leakcanary.internal.InternalLeakCanary#scheduleRetainedObjectCheck-> leakcanary.internal.HeapDumpTrigger#scheduleRetainedObjectCheck
那就看看scheduleRetainedObjectCheck方法的实现:
kotlin
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
// a、切后台执行执行剩余对象的检测
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
- 可以看到注释a处的checkRetainedObjects方法是重点,它的实现如下:
kotlin
private fun checkRetainedObjects() {
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
val config = configProvider()
if (iCanHasHeap is Nope) {
if (iCanHasHeap is NotifyingNope) {
// Before notifying that we can't dump heap, let's check if we still have retained object.
var retainedReferenceCount = retainedObjectTracker.retainedObjectCount
if (retainedReferenceCount > 0) {
// a、剩余的引用数大于0,手动触发GC
gcTrigger.runGc()
retainedReferenceCount = retainedObjectTracker.retainedObjectCount
}
val nopeReason = iCanHasHeap.reason()
// b、检查剩余的引用数量,默认的配置中数量大于5会dump堆栈(checkRetainedCount还会在满足条件的5s内执行scheduleRetainedObjectCheck方法)
val wouldDump = !checkRetainedCount(
retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
)
if (wouldDump) {
val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
// c、展示内存泄漏的通知
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = uppercaseReason
)
}
} else {
SharkLog.d {
application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
return
}
var retainedReferenceCount = retainedObjectTracker.retainedObjectCount
if (retainedReferenceCount > 0) {
// d、手动触发GC
gcTrigger.runGc()
retainedReferenceCount = retainedObjectTracker.retainedObjectCount
}
// e、数量小于5个则退出
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
// f、展示剩余的强引用数量通知栏
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
// g、当前在执行dump操作,根据上次dump时间延时执行GC检测
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
// h、执行dump堆栈
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
- 流程11的注释h处是最终进行堆栈的操作,其实现如下:
kotlin
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
val directoryProvider =
InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
// a、生成dump堆栈文件
val heapDumpFile = directoryProvider.newHeapDumpFile()
val durationMillis: Long
if (currentEventUniqueId == null) {
currentEventUniqueId = UUID.randomUUID().toString()
}
try {
InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId!!))
if (heapDumpFile == null) {
throw RuntimeException("Could not create heap dump file")
}
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
durationMillis = measureDurationMillis {
// b、调用android.os.Debug#dumpHprofData(java.lang.String)生成堆栈数据文件
configProvider().heapDumper.dumpHeap(heapDumpFile)
}
if (heapDumpFile.length() == 0L) {
throw RuntimeException("Dumped heap file is 0 byte length")
}
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
retainedObjectTracker.clearObjectsTrackedBefore(heapDumpUptimeMillis.milliseconds)
currentEventUniqueId = UUID.randomUUID().toString()
// c、通知LeakCanary默认监听器发送时间
InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
} catch (throwable: Throwable) {
InternalLeakCanary.sendEvent(HeapDumpFailed(currentEventUniqueId!!, throwable, retry))
if (retry) {
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
return
}
}
到这里整个LeakCanary启动的流程、内存泄漏检测和堆栈文件生成流程介绍就结束了,整个核心的部分就是WeakReference的引用链注册。我们以ActivityWatcher为例进行了内存泄漏的分析,其余的FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher也是类似的方法调用流程,我们就不逐一讲解,读者可以自行阅读源码理解。
2.2 LeakCanary主流程之外的细节
在流程12的注释c处,调用了leakcanary.internal.InternalLeakCanary#sendEvent方法,可以看到监听器如下:
kotlin
val eventListeners: List<EventListener> = listOf(
LogcatEventListener,
ToastEventListener,
LazyForwardingEventListener {
if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
},
when {
RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
RemoteWorkManagerHeapAnalyzer
WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
else -> BackgroundThreadHeapAnalyzer
}
)
可以看到其中有一个NotificationEventListener,真正分析的堆栈的监听有三个分别是RemoteWorkManagerHeapAnalyzer、WorkManagerHeapAnalyzer和BackgroundThreadHeapAnalyzer,以BackgroundThreadHeapAnalyzer为例:
kotlin
override fun onEvent(event: Event) {
if (event is HeapDump) {
heapAnalyzerThreadHandler.post {
// a、执行堆栈分析,获取事件实例
val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event ->
InternalLeakCanary.sendEvent(event)
}
// b、将事件实例发送给对应的监听器
InternalLeakCanary.sendEvent(doneEvent)
}
}
}
在收到事件后会执行leakcanary.internal.AndroidDebugHeapAnalyzer#runAnalysisBlocking方法,其实现如下:
kotlin
fun runAnalysisBlocking(
heapDumped: HeapDump,
isCanceled: () -> Boolean = { false },
progressEventListener: (HeapAnalysisProgress) -> Unit
): HeapAnalysisDone<*> {
val progressListener = OnAnalysisProgressListener { step ->
val percent = (step.ordinal * 1.0) / OnAnalysisProgressListener.Step.values().size
progressEventListener(HeapAnalysisProgress(heapDumped.uniqueId, step, percent))
}
val heapDumpFile = heapDumped.file
val heapDumpDurationMillis = heapDumped.durationMillis
val heapDumpReason = heapDumped.reason
val heapAnalysis = if (heapDumpFile.exists()) {
// a、分析堆栈
analyzeHeap(heapDumpFile, progressListener, isCanceled)
} else {
missingFileFailure(heapDumpFile)
}
val fullHeapAnalysis = when (heapAnalysis) {
is HeapAnalysisSuccess -> heapAnalysis.copy(
dumpDurationMillis = heapDumpDurationMillis,
metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
)
is HeapAnalysisFailure -> {
// b、生成异常结果
}
}
progressListener.onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
// c、内存泄漏写入db
val analysisDoneEvent = ScopedLeaksDb.writableDatabase(application) { db ->
val id = HeapAnalysisTable.insert(db, heapAnalysis)
when (fullHeapAnalysis) {
is HeapAnalysisSuccess -> {
// d、生成内存泄漏分析成功的数据
val showIntent = LeakActivity.createSuccessIntent(application, id)
val leakSignatures = fullHeapAnalysis.allLeaks.map { it.signature }.toSet()
val leakSignatureStatuses = LeakTable.retrieveLeakReadStatuses(db, leakSignatures)
val unreadLeakSignatures = leakSignatureStatuses.filter { (_, read) ->
!read
}.keys
// keys returns LinkedHashMap$LinkedKeySet which isn't Serializable
.toSet()
HeapAnalysisSucceeded(
heapDumped.uniqueId,
fullHeapAnalysis,
unreadLeakSignatures,
showIntent
)
}
is HeapAnalysisFailure -> {
val showIntent = LeakActivity.createFailureIntent(application, id)
HeapAnalysisFailed(heapDumped.uniqueId, fullHeapAnalysis, showIntent)
}
}
}
return analysisDoneEvent
}
上述代码中重点看注释a和d,注释a处的堆栈分析路径为leakcanary.internal.AndroidDebugHeapAnalyzer#analyzeHeap->shark.HeapAnalyzer#analyze,在这个链路中主要使用了shark库进行堆栈分析。注释d处为shark库执行的堆栈解析成功,生成实际的解析跳转intent,接下来看看leakcanary.internal.activity.LeakActivity.Companion#createSuccessIntent方法的实现:
kotlin
fun createSuccessIntent(context: Context, heapAnalysisId: Long): Intent {
val intent = createHomeIntent(context)
intent.putExtra("heapAnalysisId", heapAnalysisId)
intent.putExtra("success", true)
return intent
}
fun createHomeIntent(context: Context): Intent {
val intent = Intent(context, LeakActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
return intent
}
可以看到最终是跳转到LeakActivity,LeakActivity根据传入的heapAnalysisId进行引用链展示。至此整个内存泄漏的细节部分结束。
3. 小结
整个Android开发过程中内存泄漏是一个影响用户体验和上线质量的问题,LeakCanary通过基本零代码侵入的方法协助开发者检测代码中可能的异常内存泄漏。我们通过整个LeakCanary的工作流程介绍,了解到ContentProvider启动SDK的可能,其实我们在开发类似的sdk库的时候也可以做成这种无侵入的sdk启动方法。除了LeakCanary这种内存泄漏检测方案外,笔者了解到的快手的KOOM和字节的Liko等,也能够实现内存泄漏检测,甚至线上数据回流等功能。LeakCanary作为一款优秀的内存泄漏检测的库,值得每一位Android开发者去了解背后的实现原理,加油各位开发者。创作不易,一键三连走起!!!