leakcanary-object-watcher-android/src/main/java/leakcanary/internal/FragmentDestroyWatcher.kt
/** * Internal class used to watch for fragments leaks. */ 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" fun install( application: Application, objectWatcher: ObjectWatcher, configProvider: () -> AppWatcher.Config ) { val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>() // 如果SDK大于等于O,则添加android.app.Fragment的观察者 if (SDK_INT >= O) { fragmentDestroyWatchers.add( AndroidOFragmentDestroyWatcher(objectWatcher, configProvider) ) } // 通过反射判定androidx.fragment.app.Fragment以及其观察者是否存在 if (classAvailable(ANDROIDX_FRAGMENT_CLASS_NAME) && classAvailable(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME) ) { // 反射实例化androidx Fragment的观察者并添加到里面list里面 val watcherConstructor = Class.forName(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME) .getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java) @kotlin.Suppress("UNCHECKED_CAST") fragmentDestroyWatchers.add( watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit ) } // 如果watcher为空,则不需要进行观察了 if (fragmentDestroyWatchers.size == 0) { return } // 对每个Activity里面的所有的Fragment进行观察 application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { for (watcher in fragmentDestroyWatchers) { watcher(activity) } } }) } private fun classAvailable(className: String): Boolean { return try { Class.forName(className) true } catch (e: ClassNotFoundException) { false } } }
AndroidOFragmentDestroyWatcher
、AndroidXFragmentDestroyWatcher
两者的源码非常类似,只是针对的Fragment不同而调用的API不同而已,下面以AndroidXFragmentDestroyWatcher
为例看看里面是如何实现的。
leakcanary-object-watcher-android-androidx/src/main/java/leakcanary/internal/AndroidXFragmentDestroyWatcher.kt
internal class AndroidXFragmentDestroyWatcher( 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) } } override fun onFragmentDestroyed( fm: FragmentManager, fragment: Fragment ) { if (configProvider().watchFragments) { objectWatcher.watch(fragment) } } } override fun invoke(activity: Activity) { if (activity is FragmentActivity) { val supportFragmentManager = activity.supportFragmentManager supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true) } } }
实现就是向Activity的FragmentManager注册FragmentLifecycleCallbacks
,这样在Fragment调用onDestroyView
和onDestory
之后就能观察Fragment的View或者Fragment本身了。
内存泄漏判定
现在我们来看看ObjectWatcher.watch(Any)
方法,在上面一节中我们看到,Activity、Fragment的View、Fragment都是由该方法进行观察的,所以最后还是统一回到了这里。
leakcanary-object-watcher/src/main/java/leakcanary/ObjectWatcher.kt
/** * Identical to [watch] with an empty string reference name. */ @Synchronized fun watch(watchedObject: Any) { watch(watchedObject, "") } /** * Watches the provided [watchedObject]. * * @param name A logical identifier for the watched object. */ @Synchronized fun watch( watchedObject: Any, name: String ) { if (!isEnabled()) { return } // 将ReferenceQueue中出现的弱引用移除 // 这是一个出现频率很高的方法,也是内存泄漏检测的关键点之一 removeWeaklyReachableObjects() val key = UUID.randomUUID() .toString() // 记下观测开始的时间 val watchUptimeMillis = clock.uptimeMillis() // 这里创建了一个自定义的弱引用,且调用了基类的WeakReference<Any>(referent, referenceQueue)构造器 // 这样的话,弱引用被回收之前会出现在ReferenceQueue中 val reference = KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue) SharkLog.d { "Watching " + (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") + (if (name.isNotEmpty()) " named $name" else "") + " with key $key" } // 将key-reference保存到map中 watchedObjects[key] = reference // 主线程5秒之后执行moveToRetained(key)方法 checkRetainedExecutor.execute { moveToRetained(key) } }
上面这段代码便是LeakCanary的关键代码之一:1. 将要观测的对象使用WeakReference保存起来,并在构造时传入一个ReferenceQueue,这样待观测的对象在被回收之前,会出现在ReferenceQueue中。2. 5秒钟之后再检查一下是否出现在了引用队列中,若出现了,则没有泄露。
为什么会是5S,这里猜测与Android GC有关。在Activity.H中,收到GC_WHEN_IDLE
消息时会进行Looper.myQueue().addIdleHandler(mGcIdler)
,而mGcIdler
最后会触发doGcIfNeeded
操作,在该方法中会判断上次GC与现在时间的差值,而这个值就是MIN_TIME_BETWEEN_GCS = 5*1000
。
回到上面的代码,需要了解两个方法removeWeaklyReachableObjects()
与moveToRetained(key)
。前者比较简单,就会将引用队列中出现的对象从map中移除,因为它们没有发生内存泄漏。但是注意一下注释,这里强调了一点:弱引用入队列发生在终结函数或者GC发生之前。
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) }
然后我们接着看重头戏moveToRetained
方法:
@Synchronized private fun moveToRetained(key: String) { removeWeaklyReachableObjects() val retainedRef = watchedObjects[key] if (retainedRef != null) { retainedRef.retainedUptimeMillis = clock.uptimeMillis() onObjectRetainedListeners.forEach { it.onObjectRetained() } } }
5秒钟到了,还是先将引用队列中出现的对象从map中移除,因为它们没有内存泄漏。然后判断key还在不在map中,如果在的话,说明可能发生了内存泄漏。此时记下内存泄漏发生的时间,即更新retainedUptimeMillis
字段,然后通知所有的对象,内存泄漏发生了。
我们回忆一下,此处的onObjectRetainedListeners
只有一个,就是我们在Activity、Fragment的观测者安装完毕后,通知了InternalLeakCanary
,而InternalLeakCanary
添加了一个监听器,就是它自己。所以我们看看InternalLeakCanary.onObjectRetained()
方法:
leakcanary-android-core/src/main/java/leakcanary/internal/InternalLeakCanary.kt
override fun onObjectRetained() { if (this::heapDumpTrigger.isInitialized) { heapDumpTrigger.onObjectRetained() } }
跟踪一下HeapDumpTrigger.onObjectRetained()
方法:
leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt
fun onObjectRetained() { scheduleRetainedObjectCheck("found new object retained") } private fun scheduleRetainedObjectCheck(reason: String) { if (checkScheduled) { SharkLog.d { "Already scheduled retained check, ignoring ($reason)" } return } checkScheduled = true backgroundHandler.post { checkScheduled = false checkRetainedObjects(reason) } } private fun checkRetainedObjects(reason: String) { val config = configProvider() // A tick will be rescheduled when this is turned back on. if (!config.dumpHeap) { SharkLog.d { "No checking for retained object: LeakCanary.Config.dumpHeap is false" } return } SharkLog.d { "Checking retained object because $reason" } var retainedReferenceCount = objectWatcher.retainedObjectCount if (retainedReferenceCount > 0) { gcTrigger.runGc() retainedReferenceCount = objectWatcher.retainedObjectCount } if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) { showRetainedCountWithDebuggerAttached(retainedReferenceCount) scheduleRetainedObjectCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS) SharkLog.d { "Not checking for leaks while the debugger is attached, will retry in $WAIT_FOR_DEBUG_MILLIS ms" } return } SharkLog.d { "Found $retainedReferenceCount retained references, dumping the heap" } val heapDumpUptimeMillis = SystemClock.uptimeMillis() KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis dismissRetainedCountNotification() val heapDumpFile = heapDumper.dumpHeap() if (heapDumpFile == null) { SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" } scheduleRetainedObjectCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS) showRetainedCountWithHeapDumpFailed(retainedReferenceCount) return } lastDisplayedRetainedObjectCount = 0 objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) HeapAnalyzerService.runAnalysis(application, heapDumpFile) }
上面的代码就是对于内存泄漏判定的代码了,首先进入onObjectRetained
方法,该方法会调用scheduleRetainedObjectCheck
方法。此方法也就是在后台线程中执行checkRetainedObjects
方法来检查泄漏的对象:1. 首先获取泄漏对象的个数,如果大于0,则GC一次之后再次获取 2. 如果此时泄漏对象的个数大于等于5个config.retainedVisibleThreshold
,则继续执行下面的代码,准备dump heap 3. 如果config里面配置的“调试时不允许dump heap”为false(默认值)且正在调试,则20s之后再试 4. 否则可以开始dump heap:此时会先记下dump发生的时间,取消内存泄漏通知,dump heap,清除所有观测事件小于等于dump发生时间的对象(因为这些对象已经处理完毕了),最后运行HeapAnalyzerService开始分析heap。
第26行的代码是如何获取泄露对象的个数的呢?我们想一下,在前面的代码中,主线程5秒之后执行了一段检测的代码,在这里面将所有泄露的对象都记下了当时的时间,存在retainedUptimeMillis
字段里面。那么我们遍历所有元素,统计一下该字段不为默认值(-1)的个数即可:
leakcanary-object-watcher/src/main/java/leakcanary/ObjectWatcher.kt
/** * Returns the number of retained objects, ie the number of watched objects that aren't weakly * reachable, and have been watched for long enough to be considered retained. */ val retainedObjectCount: Int @Synchronized get() { removeWeaklyReachableObjects() return watchedObjects.count { it.value.retainedUptimeMillis != -1L } }
第29行,如果有内存泄漏的话,会调用gcTrigger.runGc()
方法,这里的gcTrigger
我们提到过,是GcTrigger.Default
:
leakcanary/leakcanary-object-watcher/src/main/java/leakcanary/GcTrigger.kt
/** * Default implementation of [GcTrigger]. */ 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() } private fun enqueueReferences() { // Hack. We don't have a programmatic way to wait for the reference queue daemon to move // references to the appropriate queues. try { Thread.sleep(100) } catch (e: InterruptedException) { throw AssertionError() } } }
在上面注释中提到,System.gc()
并不会每次都会执行GC,Runtime.gc()
更有可能执行GC。
执行一次GC操作之后,下面粗暴的等待100ms,这样有足够的时间可以让弱引用移动到合适的引用队列里面。这就是GcTrigger.Default
所干的事情。
GCTrigger触发GC之后,再次判断一下发生内存泄漏的对象的个数,如果仍然还有,那么肯定是泄漏无疑了,实锤!!
随后调用checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)
方法,判断泄漏对象的个数是否达到了阈值,如果达到了则直接dump heap;否则发出一个内存泄漏的通知。
我们看一下这个方法:
private fun checkRetainedCount( retainedKeysCount: Int, retainedVisibleThreshold: Int ): Boolean { // lastDisplayedRetainedObjectCount默认值为0,此处我们肯定有内存泄漏,因此countChanged为true val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount // 保存下当前的内存泄漏对象的个数 lastDisplayedRetainedObjectCount = retainedKeysCount // 如果内存泄漏个数为0,则说明已经处理了所有的内存泄漏 if (retainedKeysCount == 0) { SharkLog.d { "No retained objects" } if (countChanged) { showNoMoreRetainedObjectNotification() } return true } // 如果泄漏个数小于5个 if (retainedKeysCount < retainedVisibleThreshold) { if (applicationVisible || applicationInvisibleLessThanWatchPeriod) { SharkLog.d { "Found $retainedKeysCount retained objects, which is less than the visible threshold of $retainedVisibleThreshold" } // 展示一个内存泄漏发生的通知 showRetainedCountBelowThresholdNotification(retainedKeysCount, retainedVisibleThreshold) // 2秒钟之后再次执行检查泄漏对象的方法,看看泄漏个数是否有变化 scheduleRetainedObjectCheck( "Showing retained objects notification", WAIT_FOR_OBJECT_THRESHOLD_MILLIS ) return true } } // 如果泄漏个数大于等于5个,返回false,则返回后checkRetainedObjects方法会继续执行 // 此时就会dump heap return false }
至此,我们已经知道了内存泄漏是如何判定的,正如第3节开头所述:
LeakCanary 2.0 beta 3 检测内存泄漏原理:
在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。
随后,会向主线程的抛出一个5秒后执行的Runnable,用于检测内存泄漏。
这段代码首先会将引用队列中出现的对象从观察对象数组中移除,然后再判断要观察的此对象是否存在。若不存在,则说明没有内存泄漏,结束。否则,就说明可能出现了内存泄漏,会调用Runtime.getRuntime().gc()
进行GC,等待100ms后再次根据引用队列判断,若仍然出现在引用队列中,那么说明有内存泄漏,此时根据内存泄漏的个数弹出通知或者开始dump hprof。
LeakCanary2往后就是如何生成hprof文件以及如何解析了