didi/DoKit · error · UnsupportedOperationException

buildAndInstall() should only be called once.

Error message

buildAndInstall() should only be called once.

What it means

LeakCanary's AndroidRefWatcherBuilder.buildAndInstall() registers a process-wide singleton RefWatcher in LeakCanaryInternals.installedRefWatcher and starts activity/fragment watchers. It deliberately throws UnsupportedOperationException on a second call to prevent duplicated watchers, duplicate heap analysis, and duplicated notifications.

Source

Thrown at Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AndroidRefWatcherBuilder.java:96

  AndroidRefWatcherBuilder maxStoredHeapDumps(int maxStoredHeapDumps) {
    LeakDirectoryProvider leakDirectoryProvider =
        new DefaultLeakDirectoryProvider(context, maxStoredHeapDumps);
    LeakCanary.setLeakDirectoryProvider(leakDirectoryProvider);
    return self();
  }

  /**
   * Creates a {@link RefWatcher} instance and makes it available through {@link
   * LeakCanary#installedRefWatcher()}.
   *
   * Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
   *
   * @throws UnsupportedOperationException if called more than once per Android process.
   */
  public @NonNull
  RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != RefWatcher.DISABLED) {
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

  @Override

View on GitHub (pinned to 626827cddb)

Solutions

  1. Initialize LeakCanary in exactly one place, typically Application.onCreate()
  2. Guard the install: if (LeakCanary.isInAnalyzerProcess(this)) return; before calling buildAndInstall in multi-process apps
  3. Before a second install attempt (tests, plugins), check LeakCanary.installedRefWatcher != null or refactor to a single owner of initialization

Example fix

// before
LeakCanary.refWatcher(this).buildAndInstall(); // called in Application AND in a provider

// after
// single init point in Application.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) {
  return;
}
if (LeakCanary.installedRefWatcher() == null) {
  LeakCanary.refWatcher(this).buildAndInstall();
}
Defensive patterns

Strategy: validation

Validate before calling

if (LeakCanary.isInAnalyzerProcess(context)) return;
if (LeakCanary.installedRefWatcher() == null) { LeakCanary.refWatcher(context).buildAndInstall(); }

Prevention

When it happens

Trigger: Calling LeakCanary.refWatcher(context).buildAndInstall() (or a custom builder chain) more than once in the same process: once in Application.onCreate and again in a debug initializer, a test setup, a library module, or after a manual install in a multi-dex/provider startup path.

Common situations: App + SDK both initializing LeakCanary (DoraemonKit's dokit-leakcanary module plus app-level init). Adding LeakCanary setup to a ContentProvider that runs before Application.onCreate where it is also installed. Tests that call buildAndInstall in setUp without resetting LeakCanaryInternals.installedRefWatcher.

Related errors


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/471c0bd09a818d70. Report an issue: GitHub.