1*d9e8da70SAndroid Build Coastguard Worker```kotlin 2*d9e8da70SAndroid Build Coastguard Workerimport android.app.Application 3*d9e8da70SAndroid Build Coastguard Workerimport com.bugsnag.android.Bugsnag 4*d9e8da70SAndroid Build Coastguard Workerimport com.bugsnag.android.Configuration 5*d9e8da70SAndroid Build Coastguard Workerimport com.bugsnag.android.ErrorTypes 6*d9e8da70SAndroid Build Coastguard Workerimport com.bugsnag.android.Event 7*d9e8da70SAndroid Build Coastguard Workerimport com.bugsnag.android.ThreadSendPolicy 8*d9e8da70SAndroid Build Coastguard Workerimport shark.HeapAnalysis 9*d9e8da70SAndroid Build Coastguard Workerimport shark.HeapAnalysisFailure 10*d9e8da70SAndroid Build Coastguard Workerimport shark.HeapAnalysisSuccess 11*d9e8da70SAndroid Build Coastguard Workerimport shark.Leak 12*d9e8da70SAndroid Build Coastguard Workerimport shark.LeakTrace 13*d9e8da70SAndroid Build Coastguard Workerimport shark.LeakTraceReference 14*d9e8da70SAndroid Build Coastguard Workerimport shark.LibraryLeak 15*d9e8da70SAndroid Build Coastguard Worker 16*d9e8da70SAndroid Build Coastguard Workerclass BugsnagLeakUploader(applicationContext: Application) { 17*d9e8da70SAndroid Build Coastguard Worker 18*d9e8da70SAndroid Build Coastguard Worker private val bugsnagClient = Bugsnag.start( 19*d9e8da70SAndroid Build Coastguard Worker applicationContext, 20*d9e8da70SAndroid Build Coastguard Worker Configuration("YOUR_BUGSNAG_API_KEY").apply { 21*d9e8da70SAndroid Build Coastguard Worker enabledErrorTypes = ErrorTypes( 22*d9e8da70SAndroid Build Coastguard Worker anrs = false, 23*d9e8da70SAndroid Build Coastguard Worker ndkCrashes = false, 24*d9e8da70SAndroid Build Coastguard Worker unhandledExceptions = false, 25*d9e8da70SAndroid Build Coastguard Worker unhandledRejections = false 26*d9e8da70SAndroid Build Coastguard Worker ) 27*d9e8da70SAndroid Build Coastguard Worker sendThreads = ThreadSendPolicy.NEVER 28*d9e8da70SAndroid Build Coastguard Worker } 29*d9e8da70SAndroid Build Coastguard Worker ) 30*d9e8da70SAndroid Build Coastguard Worker 31*d9e8da70SAndroid Build Coastguard Worker fun upload(heapAnalysis: HeapAnalysis) { 32*d9e8da70SAndroid Build Coastguard Worker when (heapAnalysis) { 33*d9e8da70SAndroid Build Coastguard Worker is HeapAnalysisSuccess -> { 34*d9e8da70SAndroid Build Coastguard Worker val allLeakTraces = heapAnalysis 35*d9e8da70SAndroid Build Coastguard Worker .allLeaks 36*d9e8da70SAndroid Build Coastguard Worker .toList() 37*d9e8da70SAndroid Build Coastguard Worker .flatMap { leak -> 38*d9e8da70SAndroid Build Coastguard Worker leak.leakTraces.map { leakTrace -> leak to leakTrace } 39*d9e8da70SAndroid Build Coastguard Worker } 40*d9e8da70SAndroid Build Coastguard Worker if (allLeakTraces.isEmpty()) { 41*d9e8da70SAndroid Build Coastguard Worker // Track how often we perform a heap analysis that yields no result. 42*d9e8da70SAndroid Build Coastguard Worker bugsnagClient.notify(NoLeakException()) { event -> 43*d9e8da70SAndroid Build Coastguard Worker event.addHeapAnalysis(heapAnalysis) 44*d9e8da70SAndroid Build Coastguard Worker true 45*d9e8da70SAndroid Build Coastguard Worker } 46*d9e8da70SAndroid Build Coastguard Worker } else { 47*d9e8da70SAndroid Build Coastguard Worker allLeakTraces.forEach { (leak, leakTrace) -> 48*d9e8da70SAndroid Build Coastguard Worker val message = "Memory leak: ${leak.shortDescription}. See LEAK tab." 49*d9e8da70SAndroid Build Coastguard Worker val exception = leakTrace.asFakeException(message) 50*d9e8da70SAndroid Build Coastguard Worker bugsnagClient.notify(exception) { event -> 51*d9e8da70SAndroid Build Coastguard Worker event.addHeapAnalysis(heapAnalysis) 52*d9e8da70SAndroid Build Coastguard Worker event.addLeak(leak) 53*d9e8da70SAndroid Build Coastguard Worker event.addLeakTrace(leakTrace) 54*d9e8da70SAndroid Build Coastguard Worker event.groupingHash = leak.signature 55*d9e8da70SAndroid Build Coastguard Worker true 56*d9e8da70SAndroid Build Coastguard Worker } 57*d9e8da70SAndroid Build Coastguard Worker } 58*d9e8da70SAndroid Build Coastguard Worker } 59*d9e8da70SAndroid Build Coastguard Worker } 60*d9e8da70SAndroid Build Coastguard Worker is HeapAnalysisFailure -> { 61*d9e8da70SAndroid Build Coastguard Worker // Please file any reported failure to 62*d9e8da70SAndroid Build Coastguard Worker // https://github.com/square/leakcanary/issues 63*d9e8da70SAndroid Build Coastguard Worker bugsnagClient.notify(heapAnalysis.exception) 64*d9e8da70SAndroid Build Coastguard Worker } 65*d9e8da70SAndroid Build Coastguard Worker } 66*d9e8da70SAndroid Build Coastguard Worker } 67*d9e8da70SAndroid Build Coastguard Worker 68*d9e8da70SAndroid Build Coastguard Worker class NoLeakException : RuntimeException() 69*d9e8da70SAndroid Build Coastguard Worker 70*d9e8da70SAndroid Build Coastguard Worker private fun Event.addHeapAnalysis(heapAnalysis: HeapAnalysisSuccess) { 71*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "heapDumpPath", heapAnalysis.heapDumpFile.absolutePath) 72*d9e8da70SAndroid Build Coastguard Worker heapAnalysis.metadata.forEach { (key, value) -> 73*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", key, value) 74*d9e8da70SAndroid Build Coastguard Worker } 75*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "analysisDurationMs", heapAnalysis.analysisDurationMillis) 76*d9e8da70SAndroid Build Coastguard Worker } 77*d9e8da70SAndroid Build Coastguard Worker 78*d9e8da70SAndroid Build Coastguard Worker private fun Event.addLeak(leak: Leak) { 79*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "libraryLeak", leak is LibraryLeak) 80*d9e8da70SAndroid Build Coastguard Worker if (leak is LibraryLeak) { 81*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "libraryLeakPattern", leak.pattern.toString()) 82*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "libraryLeakDescription", leak.description) 83*d9e8da70SAndroid Build Coastguard Worker } 84*d9e8da70SAndroid Build Coastguard Worker } 85*d9e8da70SAndroid Build Coastguard Worker 86*d9e8da70SAndroid Build Coastguard Worker private fun Event.addLeakTrace(leakTrace: LeakTrace) { 87*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "retainedHeapByteSize", leakTrace.retainedHeapByteSize) 88*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "signature", leakTrace.signature) 89*d9e8da70SAndroid Build Coastguard Worker addMetadata("Leak", "leakTrace", leakTrace.toString()) 90*d9e8da70SAndroid Build Coastguard Worker } 91*d9e8da70SAndroid Build Coastguard Worker 92*d9e8da70SAndroid Build Coastguard Worker private fun LeakTrace.asFakeException(message: String): RuntimeException { 93*d9e8da70SAndroid Build Coastguard Worker val exception = RuntimeException(message) 94*d9e8da70SAndroid Build Coastguard Worker val stackTrace = mutableListOf<StackTraceElement>() 95*d9e8da70SAndroid Build Coastguard Worker stackTrace.add(StackTraceElement("GcRoot", gcRootType.name, "GcRoot.kt", 42)) 96*d9e8da70SAndroid Build Coastguard Worker for (cause in referencePath) { 97*d9e8da70SAndroid Build Coastguard Worker stackTrace.add(buildStackTraceElement(cause)) 98*d9e8da70SAndroid Build Coastguard Worker } 99*d9e8da70SAndroid Build Coastguard Worker exception.stackTrace = stackTrace.toTypedArray() 100*d9e8da70SAndroid Build Coastguard Worker return exception 101*d9e8da70SAndroid Build Coastguard Worker } 102*d9e8da70SAndroid Build Coastguard Worker 103*d9e8da70SAndroid Build Coastguard Worker private fun buildStackTraceElement(reference: LeakTraceReference): StackTraceElement { 104*d9e8da70SAndroid Build Coastguard Worker val file = reference.owningClassName.substringAfterLast(".") + ".kt" 105*d9e8da70SAndroid Build Coastguard Worker return StackTraceElement(reference.owningClassName, reference.referenceDisplayName, file, 42) 106*d9e8da70SAndroid Build Coastguard Worker } 107*d9e8da70SAndroid Build Coastguard Worker} 108*d9e8da70SAndroid Build Coastguard Worker``` 109