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