LeakCanary 2.0原理分析
Posted android小菜比
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeakCanary 2.0原理分析相关的知识,希望对你有一定的参考价值。
LeakCanary 2.0原理
背景:
android应用基于Java(kotlin)实现,因此它也将Java的优缺点继承了过来,典型的极就是内存回收问题,JVM在GC上让开发者尽量不需要关注垃圾对象的回收,但是如果开发真的不去关注内存问题就有可能造成应用的内存泄漏和OOM。比如错误使用handler、thread等都可能会带来OOM。应用程序在申请一定的内存后,内存又没有及时得到释放后就很容易发生OOM而导致crash,因此在实际开发过程中一方面要避免出现错误、另一方面要及时通过第三方工具(LeakCanary)排查问题。
简介
LeakCanary本质上就是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具,通过集成工具代码到自己的Android工程当中就能够在程序调试开发过程中得到及时发现和定位内存泄漏问题。
LeakCanary示例图:
Github地址:仓库地址
用法
(1)1.6.1版本
引入LeakCanary到自己的项目中
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
项目的自定义Application 类中添加,进行初始化任务。
private fun initLeakCanary()
if (LeakCanary.isInAnalyzerProcess(this))
return
LeakCanary.install(this)
(2)2.1版本
引入LeakCanary到自己的项目中,但是在2.0版本以后是非侵入式的,集成LeakCanary只需要引入库即可,内部是通过实现ContentProvider类完成初始化任务的。
PS:现在越来越多的库使用这种方式完成初始化任务,简化了第三方接入库的开发成本,但是也会导致业务方一旦想做启动优化等优化项就会显得不那么友好。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.1'
原理分析
前提:
继续阅读本文之前,请先熟悉:ReferenceQueue
在2.1的版本中代码是完全基于kotlin实现的,对于不熟悉kotlin的开发者可能不太友好。
问题:
Android 应用的完整生命周期由其组件的生命周期组成,如果想要分析应用是否存在内存泄漏只需要在组件比如在Activity或者Fragment 执行 onDestory() 方法之后,对Activity或者Fragment等对象的引用情况进行分析即可,如果在Activity执行onDestory之后,依旧有其他对象持有activity的引用,导致activity对象得不到及时释放,那么就有可能造成内存泄漏。
解决方案:
熟悉WeakReference的我们知道,WeakReference 创建时,如果指定一个ReferenceQueue 对象,在垃圾回收器检测到被引用对象的可到达性更改后,垃圾回收器会将已注册的引用对象添加到ReferenceQueue队列中,等待ReferenceQueue处理。但是如果当 GC 过后引用对象仍然不被加入 ReferenceQueue中,就有可能存在内存泄漏问题。
所以我们可以通过监听 Activity.onDestroy() 回调之后,通过弱引用(WeakReference)对象、ReferenceQueue和 GC来观测Activity引用的内存泄露情况,如果发现了未被回收的Activity对象,在找到该Activity对象是否被其他对象所引用,如果被其他对象引用,就进行 heap dump生成完整的内存引用链(最短引用链),并通过notification等方式展示出来。
LeakCanary启动
在2.0的版本中不需要业务方在application完成初始化任务,LeakCanary2.0利用了ContentProvider 在 Application 被创建之前被加载的原理,在ContentProvider的onCreate完成了初始化任务。
(1)ContentProvider的配置
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:exported="false"
android:authorities="com.jandroid.myapplication.leakcanary-installer" />
(2)初始化任务
在onCreate方法中调用了InternalAppWatcher.install(application)进行初始化
override fun onCreate(): Boolean
val application = context!!.applicationContext as Application
InternalAppWatcher.install(application)
return true
LeakCanary初始化
LeakCanary通过InternalAppWatcher.install方法完成了主要的初始化任务。
(1)通过Looper检查是否在主线程中,否则抛出异常。
(2)通过ActivityDestroyWatcher完成监听 Activity.onDestroy()的操作。
(3)通过FragmentDestroyWatcher完成监听 Fragment.onDestroy()的操作。
(4)通过InternalLeakCanary初始化检测内存泄露过程中需要用到的对象。
fun install(application: Application)
SharkLog.logger = DefaultCanaryLog()
SharkLog.d "Installing AppWatcher"
checkMainThread()
if (this::application.isInitialized)
return
InternalAppWatcher.application = application
val configProvider = AppWatcher.config
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
onAppWatcherInstalled(application)
其中onAppWatcherInstalled变量的初始化操作放在kotlin的init代码块里,对应的是InternalLeakCanary类的对象,然后调用InternalLeakCanary类的 **override fun invoke(application: Application)**完成检测内存泄露过程中需要用到的对象的初始化。
init
val internalLeakCanary = try
val leakCanaryListener = Class.forName("leakcanary.internal. 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
")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null)
catch (ignored: Throwable)
NoLeakCanary
@kotlin.Suppress("UNCHECKED_CAST")
onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
ActivityDestroyWatcher.install()
(1)在install方法中创建了ActivityDestroyWatcher类对象activityDestroyWatcher
(2)注册了 Activity 生命周期监听类,在监听到 onDestroy() 时,调用 ObjectWatcher.watch() 方法监测 Activity的引用。
(3)其中objectWatcher在InternalAppWatcher类中创建。
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
)
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate()
override fun onActivityDestroyed(activity: Activity)
if (configProvider().watchActivities)
objectWatcher.watch(
activity, "$activity::class.java.name received Activity#onDestroy() callback"
)
companion object
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
)
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
FragmentDestroyWatcher.install()
(1)FragmentDestroyWatcher为静态类,install方法为静态方法
(2)如果SDK是android 8.0及以上,则使用AndroidOFragmentDestroyWatcher监听Fragment的生命周期。
(3)通过getWatcherIfAvailable方法加载support fragment的生命周期监听类AndroidSupportFragmentDestroyWatcher。
(4)通过getWatcherIfAvailable方法加载android x的生命周期监听类AndroidXFragmentDestroyWatcher。
(5)通过监听activity的onCreate方法,在activity方法之后,在调用Fragment监听类的invoke方法,完成Fragment监听类的初始化任务。这里监听activity是因为Fragment需要依附于activity,当activity创建的时候,可能会有Fragment对象的创建。
总结一下,其实fragment有三个监听类,分别是AndroidOFragmentDestroyWatcher、AndroidSupportFragmentDestroyWatcher、AndroidXFragmentDestroyWatcher。但是这三个类的本质实现没有差别。
internal object FragmentDestroyWatcher
private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidXFragmentDestroyWatcher"
private const val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME = "android.support.v4.app.Fragment"
private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidSupportFragmentDestroyWatcher"
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
)
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
if (SDK_INT >= O)
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let
fragmentDestroyWatchers.add(it)
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let
fragmentDestroyWatchers.add(it)
if (fragmentDestroyWatchers.size == 0)
return
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate()
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
)
for (watcher in fragmentDestroyWatchers)
watcher(activity)
)
private fun getWatcherIfAvailable(
fragmentClassName: String,
watcherClassName: String,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
): ((Activity) -> Unit)?
return if (classAvailable(fragmentClassName) &&
classAvailable(watcherClassName)
)
val watcherConstructor = Class.forName(watcherClassName)
.getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit
else
null
private fun classAvailable(className: String): Boolean
return try
Class.forName(className)
true
catch (e: Throwable)
// e is typically expected to be a ClassNotFoundException
// Unfortunately, prior to version 25.0.2 of the support library the
// FragmentManager.FragmentLifecycleCallbacks class was a non static inner class.
// Our AndroidSupportFragmentDestroyWatcher class is compiled against the static version of
// the FragmentManager.FragmentLifecycleCallbacks class, leading to the
// AndroidSupportFragmentDestroyWatcher class being rejected and a NoClassDefFoundError being
// thrown here. So we're just covering our butts here and catching everything, and assuming
// any throwable means "can't use this". See https://github.com/square/leakcanary/issues/1662
false
AndroidSupportFragmentDestroyWatcher
(1)本质上是通过监听Fragment的view来实现fragment的监听任务。
(2)注册了 fragment 生命周期监听类,在监听到 onDestroy() 时,调用 ObjectWatcher.watch() 方法监测 fragment的引用。
internal class AndroidSupportFragmentDestroyWatcher(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : (Activity) -> Unit
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks()
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)"
)
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
)
if (configProvider().watchFragments)
objectWatcher.watch(
fragment, "$fragment::class.java.name received Fragment#onDestroy() callback"
)
override fun invoke(activity: Activity)
if (activity is FragmentActivity)
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
InternalLeakCanary
(1)初始化一些检测内存泄露过程中需要的对象。
(2)addOnObjectRetainedListener设置可能存在内存泄漏的回调。
(3)通过AndroidHeapDumper进行内存泄漏之后进行 heap dump 任务。
(4)通过GcTrigger 手动调用 GC 再次确认内存泄露。
(5)启动内存泄漏检查的线程。
(6)通过registerVisibilityListener监听应用程序的可见性。
override fun invoke(application: Application)
this.application = application
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
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)
addDynamicShortcut(application)
disableDumpHeapInTests()
监听ObjectWatcher
通过上面的分子,我们知道无论是activity还是frgment都是通过ObjectWatcher的watch方法来完成监听任务的。
(1)removeWeaklyReachableObjects移除若可达的对象(即将被GC的对象),也就是不会存在内存泄漏的对象。
(2)创建watchedObject引用的弱引用对象,并关联引用队列 ReferenceQueue中
(3)watchedObjects是LinkedHashMap对象,用于存储WeakReference对象。通常是指尚未确定是否存在内存泄露的对象引用。
(4)在主线程(mainHandler)中执行moveToRetained方法。(5s 后监测对象是否可达)。
@Synchronized fun watch(
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 val checkRetainedExecutor = Executor
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
moveToRetained
(1)removeWeaklyReachableObjects方法主要是移除watchedObjects队列中 中将被 GC 的引用。
(2)如果当前引用retainedRef未被移除,仍在 watchedObjects队列中,说明仍未被 GC,说明可能会存在内存泄露,需要在次做判断。
(3)在InternalLeakCanary类中我们调用了addOnObjectRetainedListener了传入了OnObjectRetainedListener类对象。通过调用onObjectRetained方法,又回到了InternalLeakCanary类中。
(4)removeWeaklyReachableObjects方法中,移除了 watchedObjects 队列中的会被 GC 的引用对象,其他的就是可能内存泄露的对象。
@Synchronized private fun moveToRetained(key: String)
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null)
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach it.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 != null)
onObjectRetained
通过上面分析,最终又会回调到InternalLeakCanary类的onObjectRetained方法。
(1)判断heapDumpTrigger是否完成了初始化。调用了HeapDumpTrigger的onObjectRetained方法。
(2)在HeapDumpTrigger的onObjectRetained方法调用了scheduleRetainedObjectCheck。
override fun onObjectRetained()
if (this::heapDumpTrigger.isInitialized)
heapDumpTrigger.onObjectRetained()
scheduleRetainedObjectCheck
(1)backgroundHandler的handle对象对应的子线程名称为:private const val LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump"
(2)通过调用checkRetainedObjects完成可能存在内存泄漏对象的检测任务。
private fun scheduleRetainedObjectCheck(
reason: String,
rescheduling: Boolean,
delayMillis: Long = 0L
)
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0)
val scheduledIn = checkCurrentlyScheduledAt - SystemClock.uptimeMillis()
SharkLog.d "Ignoring request to check for retained objects ($reason), already scheduled in $scheduledInms"
return
else
val verb = if (rescheduling) "Rescheduling" else "Scheduling"
val delay = if (delayMillis > 0) " in $delayMillisms" else ""
SharkLog.d "$verb check for retained objects$delay because $reason"
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed(
checkScheduledAt = 0
checkRetainedObjects(reason)
, delayMillis)
checkRetainedObjects
(1)retainedReferenceCount获取到objectWatcher可能存在内存泄漏的对象个数
(2)如果retainedReferenceCount的数量大于0,则手动执行GC,再次更新retainedReferenceCount数值。
(3)当前retainedReferenceCount泄露实例个数小于 5 时,不进行 heap dump。
(4)如果两次 heap dump的间隔小于60s,则不在进行 heap dump处理,只做showRetainedCountNotification通知栏通知并在次发起scheduleRetainedObjectCheck检测。
(5)启动前台服务(ForegroundService) HeapAnalyzerService 来heap dump 文件处理。
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
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0)
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_debugger_attached
)
)
scheduleRetainedObjectCheck(
reason = "debugger is attached",
rescheduling = true,
delayMillis = WAIT_FOR_DEBUG_MILLIS
)
return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(
reason = "previous heap dump was $elapsedSinceLastDumpMillisms ago (< $WAIT_BETWEEN_HEAP_DUMPS_MILLISms)",
rescheduling = true,
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
SharkLog.d "Check for retained objects found $retainedReferenceCount objects, dumping the heap"
dismissRetainedCountNotification()
dumpHeap(retainedReferenceCount, retry = true)
GcTrigger
(1)可能存在被观察的引用将要变得弱可达,但是还未入队引用队列。这时候应该主动调用一次 GC,可能可以避免一次 heap dump。
涞源:https://blog.csdn.net/u012551350/article/details/97079164
object Default : GcTrigger
override fun runGc()
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
checkRetainedCount
(1)如果不存在内存泄漏的对象,则不进行heap dump
(2)如果存在内存泄漏的对象个数小于 val retainedVisibleThreshold: Int = 5,,则不进行heap dump
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int
): Boolean
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
lastDisplayedRetainedObjectCount = retainedKeysCount
if (retainedKeysCount == 0)
SharkLog.d "Check for retained object found no objects remaining"
if (countChanged)
showNoMoreRetainedObjectNotification()
return true
if (retainedKeysCount < retainedVisibleThreshold)
if (applicationVisible || applicationInvisibleLessThanWatchPeriod)
showRetainedCountNotification(
objectCount = retainedKeysCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
)
)
scheduleRetainedObjectCheck(
reason = "found only $retainedKeysCount retained objects (< $retainedVisibleThreshold while app visible)",
rescheduling = true,
delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
return false
dumpHeap
(1)showRetainedCountNotification显示泄漏个数的通知栏
(2)启动一个前台服务 HeapAnalyzerService 来分析 heap dump 文件。
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean
)
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null)
if (retry)
SharkLog.d "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms"
scheduleRetainedObjectCheck(
reason = "failed to dump heap",
rescheduling = true,
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
)
)
return
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
dump
待更新
以上是关于LeakCanary 2.0原理分析的主要内容,如果未能解决你的问题,请参考以下文章