xref: /aosp_15_r20/external/leakcanary2/docs/upgrading-to-leakcanary-2.0.md (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1LeakCanary 2 is a major rewrite. High level changes:
2
3* New heap analyzer, reimplemented from scratch to use 10 times less memory ([see Shark](shark.md)).
4* APIs updated to simplify configuration and provide access to the new heap analyzer.
5* Internals rewritten to 100% Kotlin.
6* Multiple leaks detected in one analysis, grouped per leak type
7
8## Dependencies
9
10### Before
11
12```groovy
13dependencies {
14  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
15  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
16  // Optional, if you use support library fragments:
17  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
18}
19```
20
21### Now
22
23```groovy
24dependencies {
25  debugImplementation 'com.squareup.leakcanary:leakcanary-android:{{ leak_canary.release }}'
26}
27```
28
29### Worth noting
30
31* The `leakcanary-android-no-op` artifact is gone. If you have compile errors, see below.
32  * **Question**: if there's no no-op anymore, how do I ensure none of this runs during release builds?
33  * **Answer**: as long as you add `leakcanary-android` as `debugImplementation`, there won't be any code referencing LeakCanary in your release builds.
34* LeakCanary does not depend on the support library anymore, and it doesn't depend on AndroidX either.
35* Detection of AndroidX fragments is automatic if you have the AndroidX fragments dependency.
36
37## Default setup code
38
39### Before
40
41```java
42public class ExampleApplication extends Application {
43
44  @Override public void onCreate() {
45    super.onCreate();
46    if (LeakCanary.isInAnalyzerProcess(this)) {
47      // This process is dedicated to LeakCanary for heap analysis.
48      // You should not init your app in this process.
49      return;
50    }
51    LeakCanary.install(this);
52    // Normal app init code...
53  }
54}
55```
56
57### Now
58
59There is no more code for default setup.
60
61### Worth noting
62
63* LeakCanary auto installs itself
64* LeakCanary analysis now runs in the main process so there is no need to call `LeakCanary.isInAnalyzerProcess()`.
65
66## Retrieve the RefWatcher
67
68### Before
69
70```kotlin
71val refWatcher: RefWatcher = LeakCanary.installedRefWatcher()
72```
73
74### Now
75
76```kotlin
77val objectWatcher: ObjectWatcher = AppWatcher.objectWatcher
78```
79
80## Compile errors because RefWatcher is used in release code
81
82If you were using `RefWatcher` in non debug code, you now get a compile error because the no-op artifact is gone. [ObjectWatcher](/leakcanary/api/leakcanary-object-watcher/leakcanary/-object-watcher/) now lives in the `object-watcher` artifact, which is suitable for release builds. You have two options:
83
84### Option 1: Add `object-watcher-android` to release builds.
85
86```groovy
87dependencies {
88  implementation 'com.squareup.leakcanary:leakcanary-object-watcher-android:{{ leak_canary.release }}'
89}
90```
91
92* It will automatically keep weak references to destroyed activities, fragments, and any instance you pass to [AppWatcher.objectWatcher](/leakcanary/api/leakcanary-object-watcher-android/leakcanary/-app-watcher/object-watcher/).
93* It will not trigger heap dumps or anything else that LeakCanary does.
94* It's very little code and should have a no impact on your release app.
95* You can use it to count how many objects are retained, for example to add metadata to OutOfMemoryError crashes:
96
97```kotlin
98val retainedObjectCount = AppWatcher.objectWatcher.retainedObjectCount
99```
100
101### Option 2: Make your own `ObjectWatcher` interface
102
103```kotlin
104// In shared code
105interface MaybeObjectWatcher {
106  fun watch(watchedObject: Any, description: String)
107
108  object None : MaybeObjectWatcher {
109    override fun watch(watchedObject: Any, description: String) {
110    }
111  }
112}
113
114// In debug code
115class RealObjectWatcher : MaybeObjectWatcher {
116  override fun watch(watchedObject: Any, description: String) {
117    AppWatcher.objectWatcher.watch(watchedObject, description)
118  }
119}
120```
121
122Use `MaybeObjectWatcher.None` in release code and `RealObjectWatcher` in debug code.
123
124## Configuring LeakCanary
125
126### Before
127
128```java
129public class DebugExampleApplication extends ExampleApplication {
130
131  @Override protected void installLeakCanary() {
132    RefWatcher refWatcher = LeakCanary.refWatcher(this)
133      .watchActivities(false)
134      .buildAndInstall();
135  }
136}
137```
138
139### Now
140
141AppWatcher is in charge of detecting retained objects. Its configuration can be updated at any time by replacing [AppWatcher.config](/leakcanary/api/leakcanary-object-watcher-android/leakcanary/-app-watcher/config/):
142
143```kotlin
144class DebugExampleApplication : ExampleApplication() {
145
146  override fun onCreate() {
147    super.onCreate()
148    AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false)
149  }
150}
151```
152
153LeakCanary is in charge of taking heap dumps and analyzing them. Its configuration can be updated at any time by replacing [LeakCanary.config](/leakcanary/api/leakcanary-android-core/leakcanary/-leak-canary/config/):
154
155```kotlin
156disableLeakCanaryButton.setOnClickListener {
157  LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)
158}
159```
160
161## Running LeakCanary in instrumentation tests
162
163### Before
164
165In your `build.gradle` file:
166
167```groovy
168dependencies {
169  androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:${leakCanaryVersion}"
170}
171
172android {
173  defaultConfig {
174    // ...
175
176    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
177    testInstrumentationRunnerArgument "listener", "com.squareup.leakcanary.FailTestOnLeakRunListener"
178  }
179}
180```
181
182In your test `Application` class:
183
184```java
185public class InstrumentationTestExampleApplication extends DebugExampleApplication {
186  @Override protected void installLeakCanary() {
187    InstrumentationLeakDetector.instrumentationRefWatcher(this)
188      .buildAndInstall();
189  }
190}
191```
192
193### Now
194
195Remove all the previous test related leak detection code then follow
196[Leak detection in UI tests](ui-tests.md).
197
198## Analysis listener / uploading to a server
199
200### Before
201
202
203```java
204public class LeakUploadService extends DisplayLeakService {
205  @Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
206    // TODO Upload result to server
207  }
208}
209```
210
211```java
212RefWatcher refWatcher = LeakCanary.refWatcher(this)
213  .listenerServiceClass(LeakUploadService.class)
214  .buildAndInstall();
215```
216
217```xml
218<?xml version="1.0" encoding="utf-8"?>
219<manifest xmlns:android="http://schemas.android.com/apk/res/android">
220  <application android:name="com.example.DebugExampleApplication">
221    <service android:name="com.example.LeakUploadService" />
222  </application>
223</manifest>
224```
225
226### Now
227
228```Kotlin
229class LeakUploader : OnHeapAnalyzedListener {
230
231  val defaultListener = DefaultOnHeapAnalyzedListener.create()
232
233  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
234    TODO("Upload heap analysis to server")
235
236    // Delegate to default behavior (notification and saving result)
237    defaultListener.onHeapAnalyzed(heapAnalysis)
238  }
239}
240
241class DebugExampleApplication : ExampleApplication() {
242
243  override fun onCreate() {
244    super.onCreate()
245    LeakCanary.config = LeakCanary.config.copy(
246        onHeapAnalyzedListener = LeakUploader()
247    )
248  }
249}
250```
251
252### Matching known library leaks
253
254### Before
255
256```java
257ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults()
258    .staticField("com.samsing.SomeSingleton", "sContext")
259    .build();
260RefWatcher refWatcher = LeakCanary.refWatcher(this)
261  .excludedRefs(excludedRefs)
262  .buildAndInstall();
263}
264```
265
266### Now
267
268```kotlin
269LeakCanary.config = LeakCanary.config.copy(
270    referenceMatchers = AndroidReferenceMatchers.appDefaults +
271        AndroidReferenceMatchers.staticFieldLeak(
272            "com.samsing.SomeSingleton",
273            "sContext"
274        )
275)
276```
277
278!!! info
279    There is no equivalent API to `ExcludedRefs.Builder.clazz()` because it led to abuses. Instead see [Ignoring specific activities or fragment classes](recipes.md#ignoring-specific-activities-or-fragment-classes).
280
281## Public API packages
282
283### Before
284
285All public APIs were in `com.squareup.leakcanary.*`
286
287### Now
288
289All public APIs are in `leakcanary.*`
290