跳到内容
🤔 文档有问题? 报告编辑

上传分析结果

您可以添加一个 EventListener 以将分析结果上传到您选择的服务器

class DebugExampleApplication : ExampleApplication() {

  override fun onCreate() {
    super.onCreate()
    val analysisUploadListener = EventListener { event ->
      if (event is HeapAnalysisSucceeded) {
        val heapAnalysis = event.heapAnalysis
        TODO("Upload heap analysis to server")
      }
    }

    LeakCanary.config = LeakCanary.config.run {
      copy(eventListeners = eventListeners + analysisUploadListener)
    }
  }
}

上传到 Bugsnag

内存泄漏追踪 (leak trace) 与堆栈追踪 (stack trace) 有很多相似之处,因此如果您没有足够的工程资源来为 LeakCanary 构建后端,您可以将内存泄漏追踪上传到崩溃报告后端。客户端需要支持通过自定义客户端哈希进行分组,以及支持换行的自定义元数据。

提示

截至本文撰写之时,已知唯一适合上传内存泄漏的库是 Bugsnag 客户端。如果您成功使其与其他库一起工作,请提交一个 issue

创建一个 Bugsnag 账号,为内存泄漏报告创建一个新项目并获取一个API 密钥。确保应用拥有 android.permission.INTERNET 权限,然后添加最新版本的 Bugsnag Android 客户端库到 build.gradle

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
  debugImplementation "com.bugsnag:bugsnag-android:$bugsnagVersion"
}

提示

如果您仅使用 Bugsnag 来上传内存泄漏,则无需设置 Bugsnag Gradle 插件或在应用清单中配置 API key。

创建一个新的 BugsnagLeakUploader

import android.app.Application
import com.bugsnag.android.Bugsnag
import com.bugsnag.android.Configuration
import com.bugsnag.android.ErrorTypes
import com.bugsnag.android.Event
import com.bugsnag.android.ThreadSendPolicy
import shark.HeapAnalysis
import shark.HeapAnalysisFailure
import shark.HeapAnalysisSuccess
import shark.Leak
import shark.LeakTrace
import shark.LeakTraceReference
import shark.LibraryLeak

class BugsnagLeakUploader(applicationContext: Application) {

  private val bugsnagClient = Bugsnag.start(
    applicationContext,
    Configuration("YOUR_BUGSNAG_API_KEY").apply {
      enabledErrorTypes = ErrorTypes(
        anrs = false,
        ndkCrashes = false,
        unhandledExceptions = false,
        unhandledRejections = false
      )
      sendThreads = ThreadSendPolicy.NEVER
    }
  )

  fun upload(heapAnalysis: HeapAnalysis) {
    when (heapAnalysis) {
      is HeapAnalysisSuccess -> {
        val allLeakTraces = heapAnalysis
          .allLeaks
          .toList()
          .flatMap { leak ->
            leak.leakTraces.map { leakTrace -> leak to leakTrace }
          }
        if (allLeakTraces.isEmpty()) {
          // Track how often we perform a heap analysis that yields no result.
          bugsnagClient.notify(NoLeakException()) { event ->
            event.addHeapAnalysis(heapAnalysis)
            true
          }
        } else {
          allLeakTraces.forEach { (leak, leakTrace) ->
            val message = "Memory leak: ${leak.shortDescription}. See LEAK tab."
            val exception = leakTrace.asFakeException(message)
            bugsnagClient.notify(exception) { event ->
              event.addHeapAnalysis(heapAnalysis)
              event.addLeak(leak)
              event.addLeakTrace(leakTrace)
              event.groupingHash = leak.signature
              true
            }
          }
        }
      }
      is HeapAnalysisFailure -> {
        // Please file any reported failure to
        // https://github.com/square/leakcanary/issues
        bugsnagClient.notify(heapAnalysis.exception)
      }
    }
  }

  class NoLeakException : RuntimeException()

  private fun Event.addHeapAnalysis(heapAnalysis: HeapAnalysisSuccess) {
    addMetadata("Leak", "heapDumpPath", heapAnalysis.heapDumpFile.absolutePath)
    heapAnalysis.metadata.forEach { (key, value) ->
      addMetadata("Leak", key, value)
    }
    addMetadata("Leak", "analysisDurationMs", heapAnalysis.analysisDurationMillis)
  }

  private fun Event.addLeak(leak: Leak) {
    addMetadata("Leak", "libraryLeak", leak is LibraryLeak)
    if (leak is LibraryLeak) {
      addMetadata("Leak", "libraryLeakPattern", leak.pattern.toString())
      addMetadata("Leak", "libraryLeakDescription", leak.description)
    }
  }

  private fun Event.addLeakTrace(leakTrace: LeakTrace) {
    addMetadata("Leak", "retainedHeapByteSize", leakTrace.retainedHeapByteSize)
    addMetadata("Leak", "signature", leakTrace.signature)
    addMetadata("Leak", "leakTrace", leakTrace.toString())
  }

  private fun LeakTrace.asFakeException(message: String): RuntimeException {
    val exception = RuntimeException(message)
    val stackTrace = mutableListOf<StackTraceElement>()
    stackTrace.add(StackTraceElement("GcRoot", gcRootType.name, "GcRoot.kt", 42))
    for (cause in referencePath) {
      stackTrace.add(buildStackTraceElement(cause))
    }
    exception.stackTrace = stackTrace.toTypedArray()
    return exception
  }

  private fun buildStackTraceElement(reference: LeakTraceReference): StackTraceElement {
    val file = reference.owningClassName.substringAfterLast(".") + ".kt"
    return StackTraceElement(reference.owningClassName, reference.referenceDisplayName, file, 42)
  }
}

然后添加一个 EventListener 以将分析结果上传到 Bugsnag

class DebugExampleApplication : ExampleApplication() {

  override fun onCreate() {
    super.onCreate()
    LeakCanary.config = LeakCanary.config.copy(
        onHeapAnalyzedListener = BugsnagLeakUploader(applicationContext = this)
    )
  }
}

您应该开始看到内存泄漏报告到 Bugsnag 中,并按其内存泄漏签名进行分组

list

LEAK 标签页包含内存泄漏追踪

leak