xref: /aosp_15_r20/external/leakcanary2/docs/leakcanary-for-releases.md (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1### LeakCanary for releases
2
3Fixing leaks found in debug builds helps reduce `Application Not Responding` freezes and
4`OutfOfMemoryError` error crashes, but only scratches the surface of all the leaks that can happen.
5For the leaks that are found in debug builds, it's hard to determine which leaks to fix first.
6
7This situation is very similar to debug crashes, where we are
8often unable to make an accurate assessment of their future impact in a production environment nor
9find all crashes that will happen in production. For crashes, apps typically monitor a crash rate by
10having a release crash reporting pipeline, with counts to prioritize fixes.
11
12LeakCanary for releases exposes APIs to run a heap analysis in release builds, in production.
13
14!!! danger
15    Everything about this is experimental. Running a heap analysis in production is not a very
16    common thing to do, and we're still learning and experimenting with this. Also, both the
17    artifact name and the APIs may change.
18
19## Getting started
20
21LeakCanary provides an artifact dedicated to detecting leaks in release builds:
22
23```groovy
24dependencies {
25  // LeakCanary for releases
26  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-release:{{ leak_canary.release }}'
27  // Optional: detect retained objects. This helps but is not required.
28  releaseImplementation 'com.squareup.leakcanary:leakcanary-object-watcher-android:{{ leak_canary.release }}'
29}
30```
31
32Here's a code example that runs a heap analysis when the screen is turned off or the app enters background, checking first if a [Firebase Remote Config](https://firebase.google.com/products/remote-config) flag is turned on, and uploading the result to Bugsnag:
33
34```kotlin
35import android.os.Process.THREAD_PRIORITY_BACKGROUND
36import java.util.concurrent.Executors
37import kotlin.concurrent.thread
38import leakcanary.BackgroundTrigger
39import leakcanary.HeapAnalysisClient
40import leakcanary.HeapAnalysisConfig
41import leakcanary.HeapAnalysisInterceptor
42import leakcanary.HeapAnalysisInterceptor.Chain
43import leakcanary.HeapAnalysisJob
44import leakcanary.HeapAnalysisJob.Result.Done
45import leakcanary.ScreenOffTrigger
46
47class ReleaseExampleApplication : ExampleApplication() {
48
49  override fun onCreate() {
50    super.onCreate()
51
52    // Delete any remaining heap dump (if we crashed)
53    analysisExecutor.execute {
54      analysisClient.deleteHeapDumpFiles()
55    }
56
57    // Starts heap analysis on background importance
58    BackgroundTrigger(
59      application = this,
60      analysisClient = analysisClient,
61      analysisExecutor = analysisExecutor,
62      analysisCallback = analysisCallback
63    ).start()
64
65    // Starts heap analysis when screen off
66    ScreenOffTrigger(
67      application = this,
68      analysisClient = analysisClient,
69      analysisExecutor = analysisExecutor,
70      analysisCallback = analysisCallback
71    ).start()
72  }
73
74  /**
75   * Call this to trigger heap analysis manually, e.g. from
76   * a help button.
77   *
78   * This method returns a `HeapAnalysisJob` on which you can
79   * call `HeapAnalysisJob.cancel()` at any time.
80   */
81  fun triggerHeapAnalysisNow(): HeapAnalysisJob {
82    val job = analysisClient.newJob()
83    analysisExecutor.execute {
84      val result = job.execute()
85      analysisCallback(result)
86    }
87    return job
88  }
89
90  private val analysisClient by lazy {
91    HeapAnalysisClient(
92      // Use private app storage. cacheDir is never backed up which is important.
93      heapDumpDirectoryProvider = { cacheDir },
94      // stripHeapDump: remove all user data from hprof before analysis.
95      config = HeapAnalysisConfig(stripHeapDump = true),
96      // Default interceptors may cancel analysis for several other reasons.
97      interceptors = listOf(flagInterceptor) + HeapAnalysisClient.defaultInterceptors(this)
98    )
99  }
100
101  // Cancels heap analysis if "heap_analysis_flag" is false.
102  private val flagInterceptor = object : HeapAnalysisInterceptor {
103    val remoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
104
105    override fun intercept(chain: Chain): HeapAnalysisJob.Result {
106      if (remoteConfig.getBoolean("heap_analysis_flag")) {
107        chain.job.cancel("heap_analysis_flag false")
108      }
109      return chain.proceed()
110    }
111  }
112
113  private val analysisExecutor = Executors.newSingleThreadExecutor {
114    thread(start = false, name = "Heap analysis executor") {
115      android.os.Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND)
116      it.run()
117    }
118  }
119
120  private val analysisCallback: (HeapAnalysisJob.Result) -> Unit = { result ->
121    if (result is Done) {
122      uploader.upload(result.analysis)
123    }
124  }
125
126  private val uploader by lazy {
127    BugsnagLeakUploader(this@ReleaseExampleApplication)
128  }
129}
130```
131
132Here's the `BugsnagLeakUploader`:
133
134--8<-- "docs/snippets/bugsnag-uploader.md"
135