1# Code Recipes 2 3This page contains code recipes to customize LeakCanary to your needs. Read through the section titles and cook your own meal! Also don't forget to check out the [FAQ](faq.md). 4 5!!! bug 6 If you think a recipe might be missing or you're not sure that what you're trying to achieve is possible with the current APIs, please [file an issue](https://github.com/square/leakcanary/issues/new/choose). Your feedback helps us make LeakCanary better for the entire community. 7 8## Watching objects with a lifecycle 9 10The default configuration of LeakCanary will automatically watch Activity, Fragment, Fragment View and ViewModel instances. 11 12In your application, you may have other objects with a lifecycle, such as services, Dagger components, etc. Use [AppWatcher.objectWatcher](/leakcanary/api/leakcanary/-app-watcher/object-watcher/) to watch instances that should be garbage collected: 13 14```kotlin 15class MyService : Service { 16 17 // ... 18 19 override fun onDestroy() { 20 super.onDestroy() 21 AppWatcher.objectWatcher.watch( 22 watchedObject = this, 23 description = "MyService received Service#onDestroy() callback" 24 ) 25 } 26} 27``` 28 29## Configuration 30 31LeakCanary has a default configuration that works well for most apps. You can also customize it to your needs. The LeakCanary configuration is held by two singleton objects (`AppWatcher` and `LeakCanary`) and can be updated at any time. Most developers configure LeakCanary in their **debug** [Application](https://developer.android.com/reference/android/app/Application) class: 32 33```kotlin 34class DebugExampleApplication : ExampleApplication() { 35 36 override fun onCreate() { 37 super.onCreate() 38 AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false) 39 } 40} 41``` 42 43!!! info 44 Create a debug application class in your `src/debug/java` folder. Don't forget to also register it in `src/debug/AndroidManifest.xml`. 45 46To customize the detection of retained objects at runtime, specify the watchers you wish to install via [AppWatcher.manualInstall()](/leakcanary/api/leakcanary/-app-watcher/manual-install/): 47 48```kotlin 49val watchersToInstall = AppWatcher.appDefaultWatchers(this) 50 .filter { it !is FragmentAndViewModelWatcher } 51AppWatcher.manualInstall( 52 application = this, 53 watchersToInstall = watchersToInstall 54) 55``` 56 57To customize the heap dumping & analysis, update [LeakCanary.config](/leakcanary/api/leakcanary/-leak-canary/config/): 58 59```kotlin 60LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3) 61``` 62 63!!! info "Java" 64 In Java, use [LeakCanary.Config.Builder](/leakcanary/api/leakcanary/-leak-canary/-config/-builder/) instead: 65 66 ```java 67 LeakCanary.Config config = LeakCanary.getConfig().newBuilder() 68 .retainedVisibleThreshold(3) 69 .build(); 70 LeakCanary.setConfig(config); 71 ``` 72 73Configure the LeakCanary UI by overriding the following resources: 74 75* `mipmap/leak_canary_icon` see [Icon and label](#icon-and-label) 76* `string/leak_canary_display_activity_label` see [Icon and label](#icon-and-label) 77* `bool/leak_canary_add_dynamic_shortcut` see [Disabling LeakCanary](#disabling-leakcanary) 78* `bool/leak_canary_add_launcher_icon` see [Disabling LeakCanary](#disabling-leakcanary) 79* `layout/leak_canary_heap_dump_toast` the layout for the toast shown when the heap is dumped 80 81## Disabling LeakCanary 82 83Sometimes it's necessary to disable LeakCanary temporarily, for example for a product demo or when running performance tests. You have different options, depending on what you're trying to achieve: 84 85* Create a build variant that does not include the LeakCanary dependencies, see [Setting up LeakCanary for different product flavors](#setting-up-leakcanary-for-different-product-flavors). 86* Disable the heap dumping & analysis: `LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)`. 87* Hide the leak display activity launcher icon: override `R.bool.leak_canary_add_launcher_icon` or call `LeakCanary.showLeakDisplayActivityLauncherIcon(false)` 88 89!!! info 90 When you set `LeakCanary.Config.dumpHeap` to `false`, `AppWatcher.objectWatcher` will still keep track of retained objects, and LeakCanary will look for these objects when you change `LeakCanary.Config.dumpHeap` back to `true`. 91 92## LeakCanary test environment detection 93 94By default, LeakCanary will look for the `org.junit.Test` class in your classpath and if found, will disable itself to avoid running in tests. However, some apps may ship JUnit in their debug classpaths (for example, when using OkHttp's MockWebServer) so we offer a way to customise the class that is used to determine that the app is running in a test environment. 95 96```xml 97<resources> 98 <string name="leak_canary_test_class_name">assertk.Assert</string> 99</resources> 100``` 101 102## Counting retained instances in release builds 103 104The `com.squareup.leakcanary:leakcanary-android` dependency should only be used in debug builds. It depends on `com.squareup.leakcanary:leakcanary-object-watcher-android` which you can use in release builds to track and count retained instances. 105 106In your `build.gradle`: 107 108```gradle 109dependencies { 110 implementation 'com.squareup.leakcanary:leakcanary-object-watcher-android:{{ leak_canary.release }}' 111} 112``` 113 114In your leak reporting code: 115```kotlin 116val retainedInstanceCount = AppWatcher.objectWatcher.retainedObjectCount 117``` 118 119## LeakCanary in release builds 120 121We **do not recommend** including LeakCanary in release builds, as it could negatively impact the experience of your customers. To avoid accidentally including the `com.squareup.leakcanary:leakcanary-android` dependency in a release build, LeakCanary crashes during initialization if the APK is not debuggable. You may have a good reason to create a non debuggable build that includes LeakCanary, for example for a QA build. If necessary, the crashing check can be disabled by overriding the `bool/leak_canary_allow_in_non_debuggable_build` resource, e.g. by creating a file under `res/values` with the following contents: 122 123```xml 124<?xml version="1.0" encoding="utf-8"?> 125<resources> 126 <bool name="leak_canary_allow_in_non_debuggable_build">true</bool> 127</resources> 128``` 129 130## Android TV 131 132LeakCanary works on Android TV devices (FireTV, Nexus player, Nvidia Shield, MiBox, etc.) without any additional setup. However, there are couple things you need to be aware of: 133 134- Android TV doesn't have notifications. LeakCanary will display Toast messages when objects become retained and when leak analysis completes. You can also check Logcat for more details. 135- Due to lack of notifications, the only way to **manually** trigger a heap dump is to background the app. 136- There's a [bug on API 26+ devices](https://issuetracker.google.com/issues/141429184) that prevents the activity that displays leaks from appearing in apps list. As a workaround, LeakCanary prints an `adb shell` command in Logcat after heap dump analysis that launches leak list activity: 137 ``` 138 adb shell am start -n "com.your.package.name/leakcanary.internal.activity.LeakLauncherActivity" 139 ``` 140- Some Android TV devices have very little memory available per app process and this might impact LeakCanary. [Running the LeakCanary analysis in a separate process](#running-the-leakcanary-analysis-in-a-separate-process) might help in such cases. 141 142## Icon and label 143 144The activity that displays leaks comes with a default icon and label, which you can change by providing `R.mipmap.leak_canary_icon` and `R.string.leak_canary_display_activity_label` in your app: 145 146``` 147res/ 148 mipmap-hdpi/ 149 leak_canary_icon.png 150 mipmap-mdpi/ 151 leak_canary_icon.png 152 mipmap-xhdpi/ 153 leak_canary_icon.png 154 mipmap-xxhdpi/ 155 leak_canary_icon.png 156 mipmap-xxxhdpi/ 157 leak_canary_icon.png 158 mipmap-anydpi-v26/ 159 leak_canary_icon.xml 160``` 161 162```xml 163<?xml version="1.0" encoding="utf-8"?> 164<resources> 165 <string name="leak_canary_display_activity_label">MyLeaks</string> 166</resources> 167``` 168 169## Matching known library leaks 170 171Set [LeakCanary.Config.referenceMatchers](/leakcanary/api/leakcanary/-leak-canary/-config/reference-matchers/) to a list that builds on top of [AndroidReferenceMatchers.appDefaults](/leakcanary/api/shark/-android-reference-matchers/-companion/app-defaults/): 172 173```kotlin 174class DebugExampleApplication : ExampleApplication() { 175 176 override fun onCreate() { 177 super.onCreate() 178 LeakCanary.config = LeakCanary.config.copy( 179 referenceMatchers = AndroidReferenceMatchers.appDefaults + 180 AndroidReferenceMatchers.staticFieldLeak( 181 className = "com.samsing.SomeSingleton", 182 fieldName = "sContext", 183 description = "SomeSingleton has a static field leaking a context.", 184 patternApplies = { 185 manufacturer == "Samsing" && sdkInt == 26 186 } 187 ) 188 ) 189 } 190} 191``` 192 193## Ignoring specific activities or fragment classes 194 195Sometimes a 3rd party library provides its own activities or fragments which contain a number of bugs leading to leaks of those specific 3rd party activities and fragments. You should push hard on that library to fix their memory leaks as it's directly impacting your application. That being said, until those are fixed, you have two options: 196 1971. Add the specific leaks as known library leaks (see [Matching known library leaks](#matching-known-library-leaks)). LeakCanary will run when those leaks are detected and then report them as known library leaks. 1982. Disable LeakCanary automatic activity or fragment watching (e.g. `AppWatcher.config = AppWatcher.config.copy(watchActivities = false)`) and then manually pass objects to `AppWatcher.objectWatcher.watch`. 199 200## Identifying leaking objects and labeling objects 201 202```kotlin 203class DebugExampleApplication : ExampleApplication() { 204 205 override fun onCreate() { 206 super.onCreate() 207 val addEntityIdLabel = ObjectInspector { reporter -> 208 reporter.whenInstanceOf("com.example.DbEntity") { instance -> 209 val databaseIdField = instance["com.example.DbEntity", "databaseId"]!! 210 val databaseId = databaseIdField.value.asInt!! 211 labels += "DbEntity.databaseId = $databaseId" 212 } 213 } 214 215 val singletonsInspector = 216 AppSingletonInspector("com.example.MySingleton", "com.example.OtherSingleton") 217 218 val mmvmInspector = ObjectInspector { reporter -> 219 reporter.whenInstanceOf("com.mmvm.SomeViewModel") { instance -> 220 val destroyedField = instance["com.mmvm.SomeViewModel", "destroyed"]!! 221 if (destroyedField.value.asBoolean!!) { 222 leakingReasons += "SomeViewModel.destroyed is true" 223 } else { 224 notLeakingReasons += "SomeViewModel.destroyed is false" 225 } 226 } 227 } 228 229 LeakCanary.config = LeakCanary.config.copy( 230 objectInspectors = AndroidObjectInspectors.appDefaults + 231 listOf(addObjectIdLabel, singletonsInspector, mmvmInspector) 232 ) 233 } 234} 235``` 236 237## Running the LeakCanary analysis in a separate process 238 239LeakCanary runs in your main app process. LeakCanary 2 is optimized to keep memory usage low while analysing and runs in a background thread with priority `Process.THREAD_PRIORITY_BACKGROUND`. If you find that LeakCanary is still using too much memory or impacting the app process performance, you can configure it to run the analysis in a separate process. 240 241All you have to do is replace the `leakcanary-android` dependency with `leakcanary-android-process`: 242 243```groovy 244dependencies { 245 // debugImplementation 'com.squareup.leakcanary:leakcanary-android:${version}' 246 debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:${version}' 247} 248``` 249 250You can call [LeakCanaryProcess.isInAnalyzerProcess](/leakcanary/api/leakcanary/-leak-canary-process/is-in-analyzer-process/) to check if your Application class is being created in the LeakCanary process. This is useful when configuring libraries like Firebase that may crash when running in an unexpected process. 251 252## Setting up LeakCanary for different product flavors 253 254You can setup LeakCanary to run in a specific product flavors of your app. For example, create: 255 256``` 257android { 258 flavorDimensions "default" 259 productFlavors { 260 prod { 261 // ... 262 } 263 qa { 264 // ... 265 } 266 dev { 267 // ... 268 } 269 } 270} 271``` 272 273Then, define a custom configuration for the flavor for which you want to enable LeakCanary: 274 275``` 276android { 277 // ... 278} 279configurations { 280 devDebugImplementation {} 281} 282``` 283 284You can now add the LeakCanary dependency for that configuration: 285 286``` 287dependencies { 288 devDebugImplementation "com.squareup.leakcanary:leakcanary-android:${version}" 289} 290``` 291 292## Extracting metadata from the heap dump 293 294[LeakCanary.Config.metadataExtractor](/leakcanary/api/leakcanary/-leak-canary/-config/metadata-extractor/) extracts metadata from a heap dump. The metadata is then available in `HeapAnalysisSuccess.metadata`. `LeakCanary.Config.metadataExtractor` defaults to `AndroidMetadataExtractor` but you can replace it to extract additional metadata from the hprof. 295 296For example, if you want to include the app version name in your heap analysis reports, you need to first store it in memory (e.g. in a static field) and then you can retrieve it in `MetadataExtractor`. 297 298```kotlin 299class DebugExampleApplication : ExampleApplication() { 300 301 companion object { 302 @JvmStatic 303 lateinit var savedVersionName: String 304 } 305 306 override fun onCreate() { 307 super.onCreate() 308 309 val packageInfo = packageManager.getPackageInfo(packageName, 0) 310 savedVersionName = packageInfo.versionName 311 312 LeakCanary.config = LeakCanary.config.copy( 313 metadataExtractor = MetadataExtractor { graph -> 314 val companionClass = 315 graph.findClassByName("com.example.DebugExampleApplication")!! 316 317 val versionNameField = companionClass["savedVersionName"]!! 318 val versionName = versionNameField.valueAsInstance!!.readAsJavaString()!! 319 320 val defaultMetadata = AndroidMetadataExtractor.extractMetadata(graph) 321 322 mapOf("App Version Name" to versionName) + defaultMetadata 323 }) 324 } 325} 326``` 327 328## Using LeakCanary with obfuscated apps 329 330If obfuscation is turned on then leak traces will be obfuscated. It's possible to automatically deobfuscate leak traces by using a deobfuscation gradle plugin provided by LeakCanary. 331 332You have to add a plugin dependency in your root `build.gradle` file: 333 334```groovy 335buildscript { 336 dependencies { 337 classpath 'com.squareup.leakcanary:leakcanary-deobfuscation-gradle-plugin:${version}' 338 } 339} 340``` 341 342And then you need to apply and configure the plugin in your app (or library) specific `build.gradle` file: 343 344```groovy 345apply plugin: 'com.android.application' 346apply plugin: 'com.squareup.leakcanary.deobfuscation' 347 348leakCanary { 349 // LeakCanary needs to know which variants have obfuscation turned on 350 filterObfuscatedVariants { variant -> 351 variant.name == "debug" 352 } 353} 354``` 355 356Now you can run LeakCanary on an obfuscated app and leak traces will be automatically deobfuscated. 357 358**Important:** never use this plugin on a release variant. This plugin copies obfuscation mapping file and puts it inside the .apk, so if you use it on release build then the obfuscation becomes pointless because the code can be easily deobfuscated using mapping file. 359 360**Warning:** R8 (Google Proguard replacement) can now understand Kotlin language constructs but the side effect is that mapping files can get very large (a couple dozen megabytes). It means that the size of .apk containing copied mapping file will increase as well. This is another reason for not using this plugin on a release variant. 361 362## Detecting leaks in JVM applications 363 364While LeakCanary was designed to work out of the box on Android, it can run on any JVM with a bit of configuration. 365 366Add the ObjectWatcher and Shark dependencies to your build file: 367 368```groovy 369dependencies { 370 implementation 'com.squareup.leakcanary:leakcanary-object-watcher:{{ leak_canary.release }}' 371 implementation 'com.squareup.leakcanary:shark:{{ leak_canary.release }}' 372} 373``` 374 375Define a `HotSpotHeapDumper` to dump the heap: 376 377```kotlin 378import com.sun.management.HotSpotDiagnosticMXBean 379import java.lang.management.ManagementFactory 380 381object HotSpotHeapDumper { 382 private val mBean: HotSpotDiagnosticMXBean by lazy { 383 val server = ManagementFactory.getPlatformMBeanServer() 384 ManagementFactory.newPlatformMXBeanProxy( 385 server, 386 "com.sun.management:type=HotSpotDiagnostic", 387 HotSpotDiagnosticMXBean::class.java 388 ) 389 } 390 391 fun dumpHeap(fileName: String) { 392 mBean.dumpHeap(fileName, LIVE) 393 } 394 395 private const val LIVE = true 396} 397``` 398 399Define a `JvmHeapAnalyzer` to analyze the heap when objects are retained and print the result to the console: 400 401```kotlin 402import leakcanary.GcTrigger 403import leakcanary.ObjectWatcher 404import leakcanary.OnObjectRetainedListener 405import java.io.File 406import java.text.SimpleDateFormat 407import java.util.Date 408import java.util.Locale.US 409 410class JvmHeapAnalyzer(private val objectWatcher: ObjectWatcher) : 411 OnObjectRetainedListener { 412 413 private val fileNameFormat = SimpleDateFormat(DATE_PATTERN, US) 414 415 override fun onObjectRetained() { 416 GcTrigger.Default.runGc() 417 if (objectWatcher.retainedObjectCount == 0) { 418 return 419 } 420 val fileName = fileNameFormat.format(Date()) 421 val hprofFile = File(fileName) 422 423 println("Dumping the heap to ${hprofFile.absolutePath}") 424 HotSpotHeapDumper.dumpHeap(hprofFile.absolutePath) 425 426 val analyzer = HeapAnalyzer( 427 OnAnalysisProgressListener { step -> 428 println("Analysis in progress, working on: ${step.name}") 429 }) 430 431 val heapDumpAnalysis = analyzer.analyze( 432 heapDumpFile = hprofFile, 433 leakingObjectFinder = KeyedWeakReferenceFinder, 434 computeRetainedHeapSize = true, 435 objectInspectors = ObjectInspectors.jdkDefaults 436 ) 437 println(heapDumpAnalysis) 438 } 439 companion object { 440 private const val DATE_PATTERN = "yyyy-MM-dd_HH-mm-ss_SSS'.hprof'" 441 } 442} 443``` 444 445Create an `ObjectWatcher` instance and configure it to watch objects for 5 seconds before notifying a `JvmHeapAnalyzer` instance: 446 447```kotlin 448val scheduledExecutor = Executors.newSingleThreadScheduledExecutor() 449val objectWatcher = ObjectWatcher( 450 clock = Clock { 451 System.currentTimeMillis() 452 }, 453 checkRetainedExecutor = Executor { command -> 454 scheduledExecutor.schedule(command, 5, SECONDS) 455 } 456) 457 458val heapAnalyzer = JvmHeapAnalyzer(objectWatcher) 459objectWatcher.addOnObjectRetainedListener(heapAnalyzer) 460``` 461 462Pass objects that you expect to be garbage collected (e.g. closed resources) to the `ObjectWatcher` instance: 463 464```kotlin 465objectWatcher.watch( 466 watchedObject = closedResource, 467 description = "$closedResource is closed and should be garbage collected" 468) 469``` 470 471If you end up using LeakCanary on a JVM, the community will definitely benefit from your experience, so don't hesitate to [let us know](https://github.com/square/leakcanary/issues/)! 472 473## PackageManager.getLaunchIntentForPackage() returns LeakLauncherActivity 474 475LeakCanary adds a main activity that has a [Intent#CATEGORY_LAUNCHER](https://developer.android.com/reference/android/content/Intent#CATEGORY_LAUNCHER) category. <a href="https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentForPackage(java.lang.String)">PackageManager.getLaunchIntentForPackage()</a> looks for a main activity in the category `Intent#CATEGORY_INFO`, and next for a main activity in the category `Intent#CATEGORY_LAUNCHER`. `PackageManager.getLaunchIntentForPackage()` returns the first activity that matches in the merged manifest of your app. If your app relies on `PackageManager.getLaunchIntentForPackage()`, you have two options: 476 477* Add `Intent#CATEGORY_INFO` to your main activity intent filter, so that it gets picked up first. This is what the Android documentation recommends. 478* Disable the leakcanary launcher activity by setting the `leak_canary_add_launcher_icon` resource boolean to false. 479 480 481