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