AndroidDetectLeaksAssert.kt
TLDR
The file AndroidDetectLeaksAssert.kt
is a Kotlin file in the leakcanary
package. It provides the default implementation of the DetectLeaksAssert
interface and contains methods and properties related to leak detection and heap analysis in Android applications.
Methods
assertNoLeaks
This method is called to assert that there are no memory leaks in the application. It performs various checks and analysis to detect leaks. The method takes a tag
parameter which is used for identifying the assertion. The method also logs the total duration spent in leak detection.
Classes
AndroidDetectLeaksAssert
This class provides the default implementation of the DetectLeaksAssert
interface. It implements the assertNoLeaks
method and contains private helper methods for performing leak detection and analysis. The class also defines a companion object that contains constants and properties related to heap analysis.
package leakcanary
import android.os.SystemClock
import leakcanary.internal.InstrumentationHeapAnalyzer
import leakcanary.internal.InstrumentationHeapDumpFileProvider
import leakcanary.HeapAnalysisDecision.NoHeapAnalysis
import leakcanary.internal.RetryingHeapAnalyzer
import leakcanary.internal.friendly.checkNotMainThread
import leakcanary.internal.friendly.measureDurationMillis
import shark.HeapAnalysisFailure
import shark.HeapAnalysisSuccess
import shark.SharkLog
/**
* Default [DetectLeaksAssert] implementation. Uses public helpers so you should be able to
* create our own implementation if needed.
*
* Leak detection can be skipped by annotating tests with [SkipLeakDetection] which requires the
* [TestDescriptionHolder] test rule be applied and evaluating when [assertNoLeaks]
* is called.
*
* For improved leak detection, you should consider updating [LeakCanary.Config.leakingObjectFinder]
* to `FilteringLeakingObjectFinder(AndroidObjectInspectors.appLeakingObjectFilters)` when running
* in instrumentation tests. This changes leak detection from being incremental (based on
* [AppWatcher] to also scanning for all objects of known types in the heap).
*/
class AndroidDetectLeaksAssert(
private val detectLeaksInterceptor: DetectLeaksInterceptor = AndroidDetectLeaksInterceptor(),
private val heapAnalysisReporter: HeapAnalysisReporter = NoLeakAssertionFailedError.throwOnApplicationLeaks()
) : DetectLeaksAssert {
override fun assertNoLeaks(tag: String) {
val assertionStartUptimeMillis = SystemClock.uptimeMillis()
try {
runLeakChecks(tag, assertionStartUptimeMillis)
} finally {
val totalDurationMillis = SystemClock.uptimeMillis() - assertionStartUptimeMillis
totalVmDurationMillis += totalDurationMillis
SharkLog.d { "Spent $totalDurationMillis ms detecting leaks on $tag, VM total so far: $totalVmDurationMillis ms" }
}
}
private fun runLeakChecks(tag: String, assertionStartUptimeMillis: Long) {
if (TestDescriptionHolder.isEvaluating()) {
val testDescription = TestDescriptionHolder.testDescription
if (SkipLeakDetection.shouldSkipTest(testDescription, tag)) {
return
}
}
checkNotMainThread()
val waitForRetainedDurationMillis = measureDurationMillis {
val yesNo = detectLeaksInterceptor.waitUntilReadyForHeapAnalysis()
if (yesNo is NoHeapAnalysis) {
SharkLog.d { "Test can keep going: no heap dump performed (${yesNo.reason})" }
return
}
}
val heapDumpFile = InstrumentationHeapDumpFileProvider().newHeapDumpFile()
val config = LeakCanary.config
KeyedWeakReference.heapDumpUptimeMillis = SystemClock.uptimeMillis()
val heapDumpDurationMillis = measureDurationMillis {
config.heapDumper.dumpHeap(heapDumpFile)
}
val heapDumpUptimeMillis = KeyedWeakReference.heapDumpUptimeMillis
AppWatcher.objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
val heapAnalyzer = RetryingHeapAnalyzer(
InstrumentationHeapAnalyzer(
leakingObjectFinder = config.leakingObjectFinder,
referenceMatchers = config.referenceMatchers,
computeRetainedHeapSize = config.computeRetainedHeapSize,
metadataExtractor = config.metadataExtractor,
objectInspectors = config.objectInspectors,
proguardMapping = null
)
)
val analysisResult = heapAnalyzer.analyze(heapDumpFile)
val totalDurationMillis = SystemClock.uptimeMillis() - assertionStartUptimeMillis
val heapAnalysisWithExtraDetails = analysisResult.let {
when (it) {
is HeapAnalysisSuccess -> it.copy(
dumpDurationMillis = heapDumpDurationMillis,
metadata = it.metadata + mapOf(
ASSERTION_TAG to tag,
WAIT_FOR_RETAINED to waitForRetainedDurationMillis.toString(),
TOTAL_DURATION to totalDurationMillis.toString()
),
)
is HeapAnalysisFailure -> it.copy(dumpDurationMillis = heapDumpDurationMillis)
}
}
heapAnalysisReporter.reportHeapAnalysis(heapAnalysisWithExtraDetails)
}
companion object {
private const val ASSERTION_TAG = "assertionTag"
private const val WAIT_FOR_RETAINED = "waitForRetainedDurationMillis"
private const val TOTAL_DURATION = "totalDurationMillis"
private var totalVmDurationMillis = 0L
val HeapAnalysisSuccess.assertionTag: String?
get() = metadata[ASSERTION_TAG]
val HeapAnalysisSuccess.waitForRetainedDurationMillis: Int?
get() = metadata[WAIT_FOR_RETAINED]?.toInt()
val HeapAnalysisSuccess.totalDurationMillis: Int?
get() = metadata[TOTAL_DURATION]?.toInt()
}
}