main

square/leakcanary

Last updated at: 29/12/2023 09:39

AndroidDetectLeaksInterceptor.kt

TLDR

The file AndroidDetectLeaksInterceptor.kt contains a class called AndroidDetectLeaksInterceptor that implements the DetectLeaksInterceptor interface. It provides a method waitUntilReadyForHeapAnalysis() which waits for certain conditions to be met before deciding whether or not to analyze the heap for memory leaks.

Classes

AndroidDetectLeaksInterceptor

This class implements the DetectLeaksInterceptor interface and is responsible for determining whether the heap should be analyzed for memory leaks. It provides a method called waitUntilReadyForHeapAnalysis() which waits for specific conditions to be met before making the decision.

Methods

waitUntilReadyForHeapAnalysis()

This method is called to determine whether the heap should be analyzed for memory leaks. It waits for various conditions to be met, including checking if there are watched objects, waiting for idle synchronization, triggering an explicit garbage collection, waiting for delayed UI posts to clear, and waiting for the retained delay period. If all conditions are met, the method returns AnalyzeHeap, indicating that the heap should be analyzed. If any of the conditions are not met, the method returns NoHeapAnalysis along with a reason explaining why the heap analysis is not necessary.

package leakcanary

import android.app.Instrumentation
import android.os.SystemClock
import androidx.test.platform.app.InstrumentationRegistry
import leakcanary.GcTrigger.Default
import leakcanary.HeapAnalysisDecision.NoHeapAnalysis
import leakcanary.HeapAnalysisDecision.AnalyzeHeap

class AndroidDetectLeaksInterceptor(
  private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
  private val objectWatcher: ObjectWatcher = AppWatcher.objectWatcher,
  private val retainedDelayMillis: Long = AppWatcher.retainedDelayMillis
) : DetectLeaksInterceptor {

  @Suppress("ReturnCount")
  override fun waitUntilReadyForHeapAnalysis(): HeapAnalysisDecision {
    val leakDetectionTime = SystemClock.uptimeMillis()

    if (!objectWatcher.hasWatchedObjects) {
      return NoHeapAnalysis("No watched objects.")
    }

    instrumentation.waitForIdleSync()
    if (!objectWatcher.hasWatchedObjects) {
      return NoHeapAnalysis("No watched objects after waiting for idle sync.")
    }

    Default.runGc()
    if (!objectWatcher.hasWatchedObjects) {
      return NoHeapAnalysis("No watched objects after triggering an explicit GC.")
    }

    // Waiting for any delayed UI post (e.g. scroll) to clear. This shouldn't be needed, but
    // Android simply has way too many delayed posts that aren't canceled when views are detached.
    SystemClock.sleep(2000)

    if (!objectWatcher.hasWatchedObjects) {
      return NoHeapAnalysis("No watched objects after delayed UI post is cleared.")
    }

    // Aaand we wait some more.
    // 4 seconds (2+2) is greater than the 3 seconds delay for
    // FINISH_TOKEN in android.widget.Filter
    SystemClock.sleep(2000)

    val endOfWatchDelay = retainedDelayMillis - (SystemClock.uptimeMillis() - leakDetectionTime)
    if (endOfWatchDelay > 0) {
      SystemClock.sleep(endOfWatchDelay)
    }

    Default.runGc()

    if (!objectWatcher.hasRetainedObjects) {
      return NoHeapAnalysis("No retained objects after waiting for retained delay.")
    }
    return AnalyzeHeap
  }
}