1 package leakcanary 2 3 import android.os.SystemClock 4 import leakcanary.internal.InstrumentationHeapAnalyzer 5 import leakcanary.internal.InstrumentationHeapDumpFileProvider 6 import leakcanary.HeapAnalysisDecision.NoHeapAnalysis 7 import leakcanary.internal.RetryingHeapAnalyzer 8 import leakcanary.internal.friendly.checkNotMainThread 9 import leakcanary.internal.friendly.measureDurationMillis 10 import shark.HeapAnalysisFailure 11 import shark.HeapAnalysisSuccess 12 import shark.SharkLog 13 14 /** 15 * Default [DetectLeaksAssert] implementation. Uses public helpers so you should be able to 16 * create our own implementation if needed. 17 * 18 * Leak detection can be skipped by annotating tests with [SkipLeakDetection] which requires the 19 * [TestDescriptionHolder] test rule be applied and evaluating when [assertNoLeaks] 20 * is called. 21 * 22 * For improved leak detection, you should consider updating [LeakCanary.Config.leakingObjectFinder] 23 * to `FilteringLeakingObjectFinder(AndroidObjectInspectors.appLeakingObjectFilters)` when running 24 * in instrumentation tests. This changes leak detection from being incremental (based on 25 * [AppWatcher] to also scanning for all objects of known types in the heap). 26 */ 27 class AndroidDetectLeaksAssert( 28 private val detectLeaksInterceptor: DetectLeaksInterceptor = AndroidDetectLeaksInterceptor(), 29 private val heapAnalysisReporter: HeapAnalysisReporter = NoLeakAssertionFailedError.throwOnApplicationLeaks() 30 ) : DetectLeaksAssert { assertNoLeaksnull31 override fun assertNoLeaks(tag: String) { 32 val assertionStartUptimeMillis = SystemClock.uptimeMillis() 33 try { 34 runLeakChecks(tag, assertionStartUptimeMillis) 35 } finally { 36 val totalDurationMillis = SystemClock.uptimeMillis() - assertionStartUptimeMillis 37 totalVmDurationMillis += totalDurationMillis 38 SharkLog.d { "Spent $totalDurationMillis ms detecting leaks on $tag, VM total so far: $totalVmDurationMillis ms" } 39 } 40 } 41 runLeakChecksnull42 private fun runLeakChecks(tag: String, assertionStartUptimeMillis: Long) { 43 if (TestDescriptionHolder.isEvaluating()) { 44 val testDescription = TestDescriptionHolder.testDescription 45 if (SkipLeakDetection.shouldSkipTest(testDescription, tag)) { 46 return 47 } 48 } 49 checkNotMainThread() 50 51 val waitForRetainedDurationMillis = measureDurationMillis { 52 val yesNo = detectLeaksInterceptor.waitUntilReadyForHeapAnalysis() 53 if (yesNo is NoHeapAnalysis) { 54 SharkLog.d { "Test can keep going: no heap dump performed (${yesNo.reason})" } 55 return 56 } 57 } 58 59 val heapDumpFile = InstrumentationHeapDumpFileProvider().newHeapDumpFile() 60 61 val config = LeakCanary.config 62 63 KeyedWeakReference.heapDumpUptimeMillis = SystemClock.uptimeMillis() 64 val heapDumpDurationMillis = measureDurationMillis { 65 config.heapDumper.dumpHeap(heapDumpFile) 66 } 67 val heapDumpUptimeMillis = KeyedWeakReference.heapDumpUptimeMillis 68 AppWatcher.objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) 69 70 val heapAnalyzer = RetryingHeapAnalyzer( 71 InstrumentationHeapAnalyzer( 72 leakingObjectFinder = config.leakingObjectFinder, 73 referenceMatchers = config.referenceMatchers, 74 computeRetainedHeapSize = config.computeRetainedHeapSize, 75 metadataExtractor = config.metadataExtractor, 76 objectInspectors = config.objectInspectors, 77 proguardMapping = null 78 ) 79 ) 80 val analysisResult = heapAnalyzer.analyze(heapDumpFile) 81 val totalDurationMillis = SystemClock.uptimeMillis() - assertionStartUptimeMillis 82 val heapAnalysisWithExtraDetails = analysisResult.let { 83 when (it) { 84 is HeapAnalysisSuccess -> it.copy( 85 dumpDurationMillis = heapDumpDurationMillis, 86 metadata = it.metadata + mapOf( 87 ASSERTION_TAG to tag, 88 WAIT_FOR_RETAINED to waitForRetainedDurationMillis.toString(), 89 TOTAL_DURATION to totalDurationMillis.toString() 90 ), 91 ) 92 is HeapAnalysisFailure -> it.copy(dumpDurationMillis = heapDumpDurationMillis) 93 } 94 } 95 heapAnalysisReporter.reportHeapAnalysis(heapAnalysisWithExtraDetails) 96 } 97 98 companion object { 99 private const val ASSERTION_TAG = "assertionTag" 100 private const val WAIT_FOR_RETAINED = "waitForRetainedDurationMillis" 101 private const val TOTAL_DURATION = "totalDurationMillis" 102 private var totalVmDurationMillis = 0L 103 104 val HeapAnalysisSuccess.assertionTag: String? 105 get() = metadata[ASSERTION_TAG] 106 107 val HeapAnalysisSuccess.waitForRetainedDurationMillis: Int? 108 get() = metadata[WAIT_FOR_RETAINED]?.toInt() 109 110 val HeapAnalysisSuccess.totalDurationMillis: Int? 111 get() = metadata[TOTAL_DURATION]?.toInt() 112 } 113 } 114