Skip to content
🤔 Documentation issue? Report or edit

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.0-beta-3'
}

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 as debugImplementation, 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 production. You have two options:

Option 1: Add object-watcher-android to release builds.

dependencies {
  implementation 'com.squareup.leakcanary:leakcanary-object-watcher-android:2.0-beta-3'
}
  • 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)

  object None : MaybeObjectWatcher {
    override fun watch(watchedObject: Any) {
    }
  }
}

// In debug code
class RealObjectWatcher : MaybeObjectWatcher {
  override fun watch(watchedObject: Any) {
    AppWatcher.objectWatcher.watch(watchedObject)
  }
}

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

In your build.gradle file:

dependencies {
  androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:${leakCanaryVersion}"
}

android {
  defaultConfig {
    // ...

    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    testInstrumentationRunnerArgument "listener", "leakcanary.FailTestOnLeakRunListener"
  }
}

No code is necessary.

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"
    xmlns:tools="http://schemas.android.com/tools"
    >
  <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.*