Initialize the

LeakCanary Initialization for the latest version of LeakCanary does not need to be done by calling the install method. It is done through the ContentProvider.

The ContentProvider onCreate method is called in the handleBindApplication and precedes the onCreate of the Application.

Let’s look at the ContentProvider implemented by LeakCanary

override fun onCreate(): Boolean { val application = context!! .applicationContext as Application AppWatcher.manualInstall(application) return true }Copy the code

AppWatcher. ManualInstall (application) is called in the onCreate method for initialization

fun manualInstall( application: Application, retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) {checkMainThread(); // Requires AppWatcher.objectWatcher to be set LeakCanaryDelegate.loadLeakCanary(application) watchersToInstall.forEach { it.install() } }Copy the code

You can see that the method has three parameter contexts, task latency, and listeners to install

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}
Copy the code

The default listener includes listening on activities, fragments, RootView, and Services

Activity

LifeCycle is implemented, the Activity triggers a check in the onActivityDestroyed callback,

Fragment

Added fragments supported by higher versions, Androidx and Android.support. v4

RootView

Top-level views, including Toast, trigger checks when the View’s onDetachFromWindow method is called back

Service

The Callback of the Handler in ActivityThread receives the message, and when the STOP_SERVICE message is received, the proxy is added to record the Service that is about to end its life cycle. A leak check is triggered when a serviceDoneExecuting call is detected (typically at the end of each lifecycle call of a Service)

Checking for Memory leaks

@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 watchedObjects[key] = reference checkRetainedExecutor.execute { moveToRetained(key) } }Copy the code

Generates a key for each listened object, generates a weak reference record in watchedObjects, and starts a task to dump memory information

@Synchronized private fun moveToRetained(key: String) { removeWeaklyReachableObjects() val retainedRef = watchedObjects[key] if (retainedRef ! = null) { retainedRef.retainedUptimeMillis = clock.uptimeMillis() onObjectRetainedListeners.forEach { it.onObjectRetained() } } }Copy the code

Can see multiple calls removeWeaklyReachableObjects method, when a weak reference, be recycled will be added to the queue by traversing the queue, deleting records of observation object, when after the delete, watchedObjects still exist in the records, there had been a memory leak

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) }Copy the code

Dump memory information

Execute the checkRetainedObjects by submitting the task to HeadDumpTrigger

private fun checkRetainedObjects() { val iCanHasHeap = HeapDumpControl.iCanHasHeap() val config = configProvider() // 1. Can't trigger dump if (iCanHasHeap is Nope) {if (iCanHasHeap is NotifyingNope) // can't trigger dump if (iCanHasHeap is Nope) {if (iCanHasHeap is NotifyingNope) Check whether you need to send a notification indicating the number of leaks return} // 2. Gc is triggered to get the number of remaining objects and determine whether // 3 needs to be dumped. If dump occurs too quickly, the number of update leaks does not trigger dump // 4. Remove memory leak number notification, Beginning dump dismissRetainedCountNotification (val) visibility = the if (applicationVisible) "visible" else "not visible" dumpHeap( retainedReferenceCount = retainedReferenceCount, retry = true, reason = "$retainedReferenceCount retained objects, app is $visibility" ) }Copy the code

The checkRetainedCount method is called several times during the above process

private fun checkRetainedCount( retainedKeysCount: Int, retainedVisibleThreshold: Int, nopeReason: String? = null ): Boolean {/ / 1. If the object of leakage have been recycled, call showNoMoreRetainedObjectNotification, Inform the user val applicationVisible = applicationVisible val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod // 2. If the current number of leaks changes, the system generates logs. // 3. Check whether the current number of leaks exceeds the threshold. The default value is 5. If (retainedKeysCount < retainedVisibleThreshold) {// 3.1 If the application stays in the foreground or changes to the background for less than watchDuration, the default value is 5s. And delay the attempt to dump, return true if (applicationVisible || applicationInvisibleLessThanWatchPeriod) { if (countChanged) { onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount)) } showRetainedCountNotification( objectCount = retainedKeysCount, contentText = application.getString( R.string.leak_canary_notification_retained_visible, RetainedVisibleThreshold)) / / 3.2 will be submitted to the HandlerThread task execution scheduleRetainedObjectCheck (delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS ) return true } } return false }Copy the code

The checkRetainedCount method returns true and does not dump

dump

private fun dumpHeap( retainedReferenceCount: Int, retry: Boolean, reason: String ) { saveResourceIdNamesToMemory() val heapDumpUptimeMillis = SystemClock.uptimeMillis() AndroidHeadDumper KeyedWeakReference. HeapDumpUptimeMillis = heapDumpUptimeMillis / / used for dump the when (val heapDumpResult = heapdumper.dumpheap ()) {NoHeapDump -> {// 1.1. } is HeapDump -> {// 2 After the dump is complete, clear the observed objects that have completed the dump, and run HeapAnalyzerService to analyze logs}}}Copy the code

Dump using AndroidHeapDumper

override fun dumpHeap(): DumpHeapResult { val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ? : Return NoHeapDump // toast, LeakCanary is dumping memory to investigate leaks. // Sending notifications that are being dumped return try {// Call the VMDebug dumpHprofData method to dump val durationMillis = measureDurationMillis { 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) } }Copy the code

Analyze the Hprof file with HeapAnalyzerService, which inherits from IntentService