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