Upgrading to LeakCanary 2
LeakCanary 2 is a major rewrite. High level changes:
- New heap analyzer, reimplemented from scratch to use 10 times less memory (see Shark).
- APIs updated to simplify configuration and provide access to the new heap analyzer.
- Internals rewritten to 100% Kotlin.
- Multiple leaks detected in one analysis, grouped per leak type
Dependencies¶
Before¶
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
Now¶
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
}
Worth noting¶
- The
leakcanary-android-no-op
artifact is gone. If you have compile errors, see below. - Question: if there’s no no-op anymore, how do I ensure none of this runs during release builds?
- Answer: as long as you add
leakcanary-android
asdebugImplementation
, there won’t be any code referencing LeakCanary in your release builds. - LeakCanary does not depend on the support library anymore, and it doesn’t depend on AndroidX either.
- Detection of AndroidX fragments is automatic if you have the AndroidX fragments dependency.
Default setup code¶
Before¶
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
Now¶
There is no more code for default setup.
Worth noting¶
- LeakCanary auto installs itself
- LeakCanary analysis now runs in the main process so there is no need to call
LeakCanary.isInAnalyzerProcess()
.
Retrieve the RefWatcher¶
Before¶
val refWatcher: RefWatcher = LeakCanary.installedRefWatcher()
Now¶
val objectWatcher: ObjectWatcher = AppWatcher.objectWatcher
Compile errors because RefWatcher is used in release code¶
If you were using RefWatcher
in non debug code, you now get a compile error because the no-op artifact is gone. ObjectWatcher now lives in the object-watcher
artifact, which is suitable for release builds. You have two options:
Option 1: Add object-watcher-android
to release builds.¶
dependencies {
implementation 'com.squareup.leakcanary:leakcanary-object-watcher-android:2.14'
}
- It will automatically keep weak references to destroyed activities, fragments, and any instance you pass to AppWatcher.objectWatcher.
- It will not trigger heap dumps or anything else that LeakCanary does.
- It’s very little code and should have a no impact on your release app.
- You can use it to count how many objects are retained, for example to add metadata to OutOfMemoryError crashes:
val retainedObjectCount = AppWatcher.objectWatcher.retainedObjectCount
Option 2: Make your own ObjectWatcher
interface¶
// In shared code
interface MaybeObjectWatcher {
fun watch(watchedObject: Any, description: String)
object None : MaybeObjectWatcher {
override fun watch(watchedObject: Any, description: String) {
}
}
}
// In debug code
class RealObjectWatcher : MaybeObjectWatcher {
override fun watch(watchedObject: Any, description: String) {
AppWatcher.objectWatcher.watch(watchedObject, description)
}
}
Use MaybeObjectWatcher.None
in release code and RealObjectWatcher
in debug code.
Configuring LeakCanary¶
Before¶
public class DebugExampleApplication extends ExampleApplication {
@Override protected void installLeakCanary() {
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.watchActivities(false)
.buildAndInstall();
}
}
Now¶
AppWatcher is in charge of detecting retained objects. Its configuration can be updated at any time by replacing AppWatcher.config:
class DebugExampleApplication : ExampleApplication() {
override fun onCreate() {
super.onCreate()
AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false)
}
}
LeakCanary is in charge of taking heap dumps and analyzing them. Its configuration can be updated at any time by replacing LeakCanary.config:
disableLeakCanaryButton.setOnClickListener {
LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)
}
Running LeakCanary in instrumentation tests¶
Before¶
In your build.gradle
file:
dependencies {
androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:${leakCanaryVersion}"
}
android {
defaultConfig {
// ...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArgument "listener", "com.squareup.leakcanary.FailTestOnLeakRunListener"
}
}
In your test Application
class:
public class InstrumentationTestExampleApplication extends DebugExampleApplication {
@Override protected void installLeakCanary() {
InstrumentationLeakDetector.instrumentationRefWatcher(this)
.buildAndInstall();
}
}
Now¶
Remove all the previous test related leak detection code then follow Leak detection in UI tests.
Analysis listener / uploading to a server¶
Before¶
public class LeakUploadService extends DisplayLeakService {
@Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
// TODO Upload result to server
}
}
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.listenerServiceClass(LeakUploadService.class)
.buildAndInstall();
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:name="com.example.DebugExampleApplication">
<service android:name="com.example.LeakUploadService" />
</application>
</manifest>
Now¶
class LeakUploader : OnHeapAnalyzedListener {
val defaultListener = DefaultOnHeapAnalyzedListener.create()
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
TODO("Upload heap analysis to server")
// Delegate to default behavior (notification and saving result)
defaultListener.onHeapAnalyzed(heapAnalysis)
}
}
class DebugExampleApplication : ExampleApplication() {
override fun onCreate() {
super.onCreate()
LeakCanary.config = LeakCanary.config.copy(
onHeapAnalyzedListener = LeakUploader()
)
}
}
Matching known library leaks¶
Before¶
ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults()
.staticField("com.samsing.SomeSingleton", "sContext")
.build();
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.excludedRefs(excludedRefs)
.buildAndInstall();
}
Now¶
LeakCanary.config = LeakCanary.config.copy(
referenceMatchers = AndroidReferenceMatchers.appDefaults +
AndroidReferenceMatchers.staticFieldLeak(
"com.samsing.SomeSingleton",
"sContext"
)
)
Info
There is no equivalent API to ExcludedRefs.Builder.clazz()
because it led to abuses. Instead see Ignoring specific activities or fragment classes.
Public API packages¶
Before¶
All public APIs were in com.squareup.leakcanary.*
Now¶
All public APIs are in leakcanary.*