前言
本文是針對 LeakCanary Version 2.7 (2021-03-26) 版本的源碼進行的分析纵势。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
1. 基礎知識
- java 引用之弱引用 WeakReference
弱引用, 當一個對象僅僅被weak reference(弱引用)指向, 而沒有任何其他strong reference(強引用)指向的時候, 如果這時GC運行, 那么這個對象就會被回收,不論當前的內存空間是否足夠,這個對象都會被回收。
構造方法:
- public WeakReference(T referent)
- public WeakReference(T referent, ReferenceQueue<? super T> q)
主要說一下第二構造方法的第二個參數 ReferenceQueue。當WeakReference引用的對象referent被回收后,會把這個WeakReference或其子類的對象澜术,放入隊列ReferenceQueue中。敲黑板猬腰,LeakCanary 原理中的重要一環(huán)鸟废。
- gc
gc 的可達性分析算法和引用計數法;
Java中可以作為GC Roots的對象:
1姑荷、虛擬機棧(javaStack)(棧幀中的局部變量區(qū)盒延,也叫做局部變量表)中引用的對象缩擂。
2、方法區(qū)中的類靜態(tài)屬性引用的對象兰英。
3撇叁、方法區(qū)中常量引用的對象。
4畦贸、本地方法棧中JNI(Native方法)引用的對象陨闹。
- ContentProvider
ContentProvider初始化(onCreate)是在Application生命周期方法attachBaseContext之后、onCreate之前薄坏。
2. 源碼分析
2.1 流程概述
為了腦中有個框架趋厉,便于理解。我將源碼的分析過程分成三個步驟胶坠,接下來會依次按照這三個步驟去進行分析君账。圖中涉及的類是調用的說明,不存在繼承關系沈善。
2.1.1 注冊觀察對象
注冊流程就是LeakCanary的初始化過程乡数,我們首先分析入口類 AppWatcherInstaller
- AppWatcherInstaller
/**
* Content providers are loaded before the application class is created. [AppWatcherInstaller] is
* used to install [leakcanary.AppWatcher] on application start.
*/
internal sealed class AppWatcherInstaller : ContentProvider() {
/**
* [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
*/
internal class MainProcess : AppWatcherInstaller()
···
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
···
根據源碼可知,AppWatcherInstaller是一個ContentProvider闻牡,它在app啟動的時候就會自動調用onCreate中的代碼净赴。
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher>
<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>
</manifest>
AppWatcherInstaller 在 manifest 中注冊源碼如上。接著我們分析 onCreate 中的關鍵代碼 AppWatcher.manualInstall(application)
- AppWatcher
/**
* The entry point API for using [ObjectWatcher] in an Android app. [AppWatcher.objectWatcher] is
* in charge of detecting retained objects, and [AppWatcher] is auto configured on app start to
* pass it activity and fragment instances. Call [ObjectWatcher.watch] on [objectWatcher] to
* watch any other object that you expect to be unreachable.
*/
object AppWatcher {
···
/**
* The [ObjectWatcher] used by AppWatcher to detect retained objects.
* Only set when [isInstalled] is true.
*/
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
···
@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"
}
installCause = RuntimeException("manualInstall() first called here")
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
LeakCanaryDelegate.loadLeakCanary(application)
watchersToInstall.forEach {
it.install()
}
}
/**
* Creates a new list of default app [InstallableWatcher], created with the passed in
* [reachabilityWatcher] (which defaults to [objectWatcher]). Once installed,
* these watchers will pass in to [reachabilityWatcher] objects that they expect to become
* weakly reachable.
*
* The passed in [reachabilityWatcher] should probably delegate to [objectWatcher] but can
* be used to filter out specific instances.
*/
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
···
}
manualInstall 方法有三個參數罩润,application 會在 AppWatcherInstaller 中傳參玖翅, retainedDelayMillis 和watchersToInstall會走默認值。這里我們著重看下watchersToInstall 這個參數割以,這個參數的默認值是appDefaultWatchers方法返回的一個InstallableWatcher列表金度。列表中有ActivityWatcher、FragmentAndViewModelWatcher严沥、RootViewWatcher和ServiceWatcher四個對象猜极。四個對象構造方法都有一個公共參數reachabilityWatcher,用objectWatcher進行了賦值消玄。這個列表在源碼中遍歷魔吐,并調用 item 的 install()方法。我們用ActivityWatcher 看看 install() 中干了什么莱找。
/**
* Expects activities to become weakly reachable soon after they receive the [Activity.onDestroy]
* callback.
*/
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)
}
}
ActivityWatcher.install() 注冊了Activity 的生命周期回調,在 onActivityDestroyed 回調的時候調用了reachabilityWatcher.expectWeaklyReachable()方法嗜桌。其實就是調用了AppWatcher中的objectWatcher對象方法奥溺。接著繼續(xù)看ObjectWatcher
- ObjectWatcher
class ObjectWatcher constructor(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
/**
* Calls to [watch] will be ignored when [isEnabled] returns false
*/
private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
/**
* References passed to [watch].
*/
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
···
@Synchronized override fun expectWeaklyReachable(
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] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
···
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 != null)
}
···
}
ObjectWatcher 中有兩個成員變量,watchedObjects 和 queue 骨宠。根據上面對弱引用的了解浮定,queue 存儲的是已經回收引用對象的 reference相满。前面 activity的ondestory生命周期回調引發(fā)了expectWeaklyReachable 方法的調用,會首先走removeWeaklyReachableObjects方法桦卒,去移除watchedObjects 中已經被回收引用對象的reference立美,保證watchedObjects中的reference所引用的對象沒有被回收。接著為當前 watchedObject 生成一個key 值方灾,然后再放入watchedObjects中建蹄。objectWatcher是在AppWatcher中創(chuàng)建的,checkRetainedExecutor 是一個線程池裕偿,并通過mainHandler延時5秒執(zhí)行了一個 runnable洞慎。這個runnable 執(zhí)行會調用ObjectWatcher 的 moveToRetained方法。到這里先停一下嘿棘,前面選了ActivityWatcher 源碼梳理到這里劲腿,最終確定了LeakCanary 將已經執(zhí)行了 onDestory生命周期的activity,通過 KeyedWeakReference 保存在了ObjectWatcher的watchedObjects這個map里鸟妙。和ActivityWatcher一樣焦人,前面其他 三個watcher觀測的對象按照一定的策略也會保存在這里。
到這里就梳理了LeakCanary的Watcher注冊流程重父,ContentProvider的子類AppWatchInstaller是一切開始的地方花椭。也分析了觀察的可能泄漏的對象在一定的時機會被放在ObjectWatcher的watchedObjects中,比如前面的onActivityDestroyed 回調時機坪郭。ObjectWatcher是一個核心類个从,判斷是否有內存泄漏,它是重要的一環(huán)歪沃。
2.1.2 觸發(fā)內存檢查
LeakCanary 中觸發(fā)內存檢查的時機可以分為兩種:
- 觀測對象可能要銷毀的時機
-- activity的onActivityDestroyed
-- service的onServiceDestroyed
-- rootview的onViewDetachedFromWindow
-- fragment的onFragmentViewDestroyed - 未熄屏的前提下嗦锐,整個應用app對用戶不可見的時候
怎么得出的這個結論,我們接著上一小節(jié)繼續(xù)分析沪曙。前面我們分析到了ObjectWatcher的moveToRetained()方法被調用奕污,往下看moveToRetained這個方法。
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
// 判斷應用對象是否被回收
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 沒有回收液走,回調監(jiān)聽
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
分析代碼碳默,最終回調了OnObjectRetainedListener監(jiān)聽,所以要看哪里添加了監(jiān)聽缘眶。添加監(jiān)聽的地方是 InternalLeakCanary 類的 invoke 方法中添加嘱根,代碼如下
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
private const val DYNAMIC_SHORTCUT_ID = "com.squareup.leakcanary.dynamic_shortcut"
private lateinit var heapDumpTrigger: HeapDumpTrigger
···
override fun invoke(application: Application) {
_application = application
checkRunningInDebuggableBuild()
//添加回調的地方
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
// 這里注冊應用的可見性回調
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
// 這里添加小鳥的快捷圖標
addDynamicShortcut(application)
// We post so that the log happens after Application.onCreate()
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
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()
)
}
}
}
}
}
···
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
···
}
分析代碼,InternalLeakCanary 實現了 OnObjectRetainedListener 巷懈,它就是那個監(jiān)聽该抒。找到重載方法onObjectRetained() ,往下看最終是調用了heapDumpTrigger.scheduleRetainedObjectCheck()顶燕。敲黑板凑保,分析到這里得停一下冈爹,因為不知道是誰調用了invoke 方法,才添加了監(jiān)聽欧引∑瞪耍看代碼 InternalLeakCanary還是一個 Function1,參數為Application芝此,返回值為Unit憋肖,所以它重載了invoke()。沒有直接調用執(zhí)行InternalLeakCanary.invoke的地方癌蓖,那是在哪里調用的瞬哼。其實在 AppWatcher. manualInstall() 分析的時候有一行代碼,這行代碼通過代理的方式執(zhí)行了InternalLeakCanary 這個Function1租副。
LeakCanaryDelegate.loadLeakCanary(application)
internal object LeakCanaryDelegate {
@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
}
}
object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun invoke(application: Application) {
}
override fun onObjectRetained() {
}
}
}
invoke方法除了添加監(jiān)聽坐慰,還創(chuàng)建了HeapDumpTrigger對象;添加了一個 application.registerVisibilityListener 的一個回調用僧,這個回調就是這一節(jié)最開始說的應用不可見引起內存檢查的地方结胀,一會兒再看這塊的邏輯;invoke 中執(zhí)行addDynamicShortcut添加了一個動態(tài)快捷方式责循,敲黑板糟港,桌面上小鳥的由來。這些就是invoke做了的事院仿〗崭В回到剛才的分析heapDumpTrigger.scheduleRetainedObjectCheck() ,看這個方法做了哪些事情歹垫。
internal class HeapDumpTrigger(
private val application: Application,
private val backgroundHandler: Handler,
private val objectWatcher: ObjectWatcher,
private val gcTrigger: GcTrigger,
private val heapDumper: HeapDumper,
private val configProvider: () -> Config
) {
···
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 = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
val nopeReason = iCanHasHeap.reason()
val wouldDump = !checkRetainedCount(
retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
)
if (wouldDump) {
val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = uppercaseReason
)
}
} else {
SharkLog.d {
application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
return
}
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
if (retry) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
} else {
SharkLog.d { "Failed to dump heap, will not automatically retry" }
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
private fun saveResourceIdNamesToMemory() {
val resources = application.resources
AndroidResourceIdNames.saveToMemory(
getResourceTypeName = { id ->
try {
resources.getResourceTypeName(id)
} catch (e: NotFoundException) {
null
}
},
getResourceEntryName = { id ->
try {
resources.getResourceEntryName(id)
} catch (e: NotFoundException) {
null
}
})
}
fun onDumpHeapReceived(forceDump: Boolean) {
backgroundHandler.post {
dismissNoRetainedOnTapNotification()
gcTrigger.runGc()
val retainedReferenceCount = objectWatcher.retainedObjectCount
if (!forceDump && retainedReferenceCount == 0) {
SharkLog.d { "Ignoring user request to dump heap: no retained objects remaining after GC" }
@Suppress("DEPRECATION")
val builder = Notification.Builder(application)
.setContentTitle(
application.getString(R.string.leak_canary_notification_no_retained_object_title)
)
.setContentText(
application.getString(
R.string.leak_canary_notification_no_retained_object_content
)
)
.setAutoCancel(true)
.setContentIntent(NotificationReceiver.pendingIntent(application, CANCEL_NOTIFICATION))
val notification =
Notifications.buildNotification(application, builder, LEAKCANARY_LOW)
notificationManager.notify(
R.id.leak_canary_notification_no_retained_object_on_tap, notification
)
backgroundHandler.postDelayed(
scheduleDismissNoRetainedOnTapNotification,
DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS
)
lastDisplayedRetainedObjectCount = 0
return@post
}
SharkLog.d { "Dumping the heap because user requested it" }
dumpHeap(retainedReferenceCount, retry = false, "user request")
}
}
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int,
nopeReason: String? = null
): Boolean {
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
lastDisplayedRetainedObjectCount = retainedKeysCount
// 沒有泄漏的對象
if (retainedKeysCount == 0) {
if (countChanged) {
SharkLog.d { "All retained objects have been garbage collected" }
onRetainInstanceListener.onEvent(NoMoreObjects)
showNoMoreRetainedObjectNotification()
}
return true
}
val applicationVisible = applicationVisible
val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod
if (countChanged) {
val whatsNext = if (applicationVisible) {
if (retainedKeysCount < retainedVisibleThreshold) {
"not dumping heap yet (app is visible & < $retainedVisibleThreshold threshold)"
} else {
if (nopeReason != null) {
"would dump heap now (app is visible & >=$retainedVisibleThreshold threshold) but $nopeReason"
} else {
"dumping heap now (app is visible & >=$retainedVisibleThreshold threshold)"
}
}
} else if (applicationInvisibleLessThanWatchPeriod) {
val wait =
AppWatcher.config.watchDurationMillis - (SystemClock.uptimeMillis() - applicationInvisibleAt)
if (nopeReason != null) {
"would dump heap in $wait ms (app just became invisible) but $nopeReason"
} else {
"dumping heap in $wait ms (app just became invisible)"
}
} else {
if (nopeReason != null) {
"would dump heap now (app is invisible) but $nopeReason"
} else {
"dumping heap now (app is invisible)"
}
}
SharkLog.d {
val s = if (retainedKeysCount > 1) "s" else ""
"Found $retainedKeysCount object$s retained, $whatsNext"
}
}
// 內存顯露的watchObject 少于5個剥汤,會先顯示一個通知暫不走堆棧分析,等待2秒再走一次判斷
if (retainedKeysCount < retainedVisibleThreshold) {
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
if (countChanged) {
onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
}
showRetainedCountNotification(
objectCount = retainedKeysCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
return false
}
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
// 前一次 checkRetainedObjects 還沒執(zhí)行
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
···
}
scheduleRetainedObjectCheck 會走到 checkRetainedCount 方法排惨,該方法中會通過objectWatcher.retainedObjectCount 去判斷是否有可能泄露的對象吭敢。count > 0 則會通過 gcTrigger.runGc() 進行一次 gc操作,然后再獲取count進行判斷暮芭,如果count仍然>0則調用dumpHeap 方法進行堆棧信息分析鹿驼,調用到 HeapAnalyzerService.runAnalysis()方法。到這里就是判定已經有內存泄漏辕宏,往下就是對堆棧進行分析了畜晰。關于觸發(fā)內存檢查的時機,上面我們已經分析完了觀測對象可能要銷毀的時機中的activity的onActivityDestroyed瑞筐,因為我們是選取ActivityWatcher 進行了分析舷蟀,其他三種就不在這里分析贅述了。
那再看看第二種時機,未鎖屏的前提下野宜,整個應用app對用戶不可見的時候。上面其實已經分析到了魔策,在InternalLeakCanary的invoke方法添加了applicationVisible的回調匈子。
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
看下registerVisibilityListener這個拓展方法
internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
val visibilityTracker = VisibilityTracker(listener)
registerActivityLifecycleCallbacks(visibilityTracker)
registerReceiver(visibilityTracker, IntentFilter().apply {
addAction(ACTION_SCREEN_ON)
addAction(ACTION_SCREEN_OFF)
})
}
可見性這塊邏輯就是通過注冊廣播接收者接收系統(tǒng)屏幕息屏與否的廣播,注冊ActivityLifecycle 來判斷當前是否有用戶可見的activity闯袒。這塊邏輯是在VisibilityTracker 中處理的虎敦,可見性變化就會調用InternalLeakCanary的invoke中的可見性回調,接著就會調用 heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)政敢,進入這個方法
/**
** 未鎖屏的前提下其徙,整個應用app對用戶不可見的時候引發(fā)內存檢查
**/
fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
if (applicationVisible) {
applicationInvisibleAt = -1L
} else {
applicationInvisibleAt = SystemClock.uptimeMillis()
// Scheduling for after watchDuration so that any destroyed activity has time to become
// watch and be part of this analysis.
scheduleRetainedObjectCheck(
delayMillis = AppWatcher.config.watchDurationMillis
)
}
}
分析可知當 applicationVisible 為false 的時候,也就是不可見的時候喷户,回調用到scheduleRetainedObjectCheck -> checkRetainedObjects -> dumpHeap ->HeapAnalyzerService.runAnalysis() 唾那。應用可見性這個時機也就走到堆棧分析這一步了。
2.1.3 分析內存堆棧
上一小節(jié)分析到 HeapAnalyzerService.runAnalysis()
fun runAnalysis(
context: Context,
heapDumpFile: File,
heapDumpDurationMillis: Long? = null,
heapDumpReason: String = "Unknown"
)
該方法有個 heapDumpFile 參數褪尝,這個參數就是進行堆棧分析的文件闹获。這個文件怎么來的,追溯參數對象創(chuàng)建的地方河哑,是 AndroidHeapDumper.dumpHeap()方法避诽。文件的創(chuàng)建以及數據的寫入就是通過下面一段代碼
override fun dumpHeap(): DumpHeapResult {
// 創(chuàng)建空文件
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
···
return try {
val durationMillis = measureDurationMillis {
// 將 dump hprof 數據寫入到空文件中
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
NoHeapDump
} else {
HeapDump(file = heapDumpFile, durationMillis = durationMillis)
}
} catch (e: Exception) {
SharkLog.d(e) { "Could not dump heap" }
// Abort heap dump
NoHeapDump
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
看源碼可知,要分析的堆棧信息是通過android sdk 中的 Debug.dumpHprofData()方法來獲取堆棧信息的璃谨。有文件之后沙庐,接著看HeapAnalyzerService.runAnalysis() 的實現
internal class HeapAnalyzerService : ForegroundService(
HeapAnalyzerService::class.java.simpleName,
R.string.leak_canary_notification_analysing,
R.id.leak_canary_notification_analyzing_heap
), OnAnalysisProgressListener {
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 heapDumpReason = intent.getStringExtra(HEAPDUMP_REASON_EXTRA)
val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, -1)
val config = LeakCanary.config
// 分析堆棧,拿到分析結果
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
val fullHeapAnalysis = when (heapAnalysis) {
is HeapAnalysisSuccess -> heapAnalysis.copy(
dumpDurationMillis = heapDumpDurationMillis,
metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
)
is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
// 將分析結果回調
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
···
companion object {
private const val HEAPDUMP_FILE_EXTRA = "HEAPDUMP_FILE_EXTRA"
private const val HEAPDUMP_DURATION_MILLIS_EXTRA = "HEAPDUMP_DURATION_MILLIS_EXTRA"
private const val HEAPDUMP_REASON_EXTRA = "HEAPDUMP_REASON_EXTRA"
private const val PROGUARD_MAPPING_FILE_NAME = "leakCanaryObfuscationMapping.txt"
fun runAnalysis(
context: Context,
heapDumpFile: File,
heapDumpDurationMillis: Long? = null,
heapDumpReason: String = "Unknown"
) {
val intent = Intent(context, HeapAnalyzerService::class.java)
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
heapDumpDurationMillis?.let {
intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
}
startForegroundService(context, intent)
}
private fun startForegroundService(
context: Context,
intent: Intent
) {
if (SDK_INT >= 26) {
context.startForegroundService(intent)
} else {
// Pre-O behavior.
context.startService(intent)
}
}
}
}
代碼中開啟了HeapAnalyzerService這個前臺服務佳吞,服務開啟會調用 onHandleIntentInForeground 中的邏輯拱雏。onHandleIntentInForeground會調用 analyzeHeap 拿到分析結果,然后將分析結果通過config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)進行回調給DefaultOnHeapAnalyzedListener的onHeapAnalyzed方法
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }
// 將分析結果存入數據庫容达,用于在activity中查看
val db = LeaksDbHelper(application).writableDatabase
val id = HeapAnalysisTable.insert(db, heapAnalysis)
db.releaseReference()
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)
}
}
private fun showNotification(
screenToShow: Screen,
contentTitle: String
) {
val pendingIntent = LeakActivity.createPendingIntent(
application, arrayListOf(HeapDumpsScreen(), screenToShow)
)
val contentText = application.getString(R.string.leak_canary_notification_message)
Notifications.showNotification(
application, contentTitle, contentText, pendingIntent,
R.id.leak_canary_notification_analysis_result,
LEAKCANARY_MAX
)
}
該回調中將分析結果存入數據庫古涧,展示一條通知,點擊通知回調轉到pendingIntent 的目標頁面 LeakActivity 展示數據庫中的信息花盐。關于如何得到分析hprof文件結果的羡滑,其實就是 HeapAnalyzerService 的 analyzeHeap() 方法返回值,一直查看調用算芯,實質調用的是 FindLeakInput 的 analyzeGraph 拓展方法
private fun FindLeakInput.analyzeGraph(
metadataExtractor: MetadataExtractor,
leakingObjectFinder: LeakingObjectFinder,
heapDumpFile: File,
analysisStartNanoTime: Long
): HeapAnalysisSuccess {
listener.onAnalysisProgress(EXTRACTING_METADATA)
val metadata = metadataExtractor.extractMetadata(graph)
val retainedClearedWeakRefCount = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph)
.filter { it.isRetained && !it.hasReferent }.count()
// This should rarely happens, as we generally remove all cleared weak refs right before a heap
// dump.
val metadataWithCount = if (retainedClearedWeakRefCount > 0) {
metadata + ("Count of retained yet cleared" to "$retainedClearedWeakRefCount KeyedWeakReference instances")
} else {
metadata
}
listener.onAnalysisProgress(FINDING_RETAINED_OBJECTS)
// 查找到泄露對象id
val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
// 查找泄露對象并分成應用泄露柒昏、庫泄露和其他泄露
val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)
return HeapAnalysisSuccess(
heapDumpFile = heapDumpFile,
createdAtTimeMillis = System.currentTimeMillis(),
analysisDurationMillis = since(analysisStartNanoTime),
metadata = metadataWithCount,
applicationLeaks = applicationLeaks,
libraryLeaks = libraryLeaks,
unreachableObjects = unreachableObjects
)
}
到這里就把LeakCanary核心流程梳理結束了,了解了LeakCanary 是如何檢測內存泄漏熙揍、提示以及展示的职祷。