[翻译] 定制你的 LeakCanary

Feb 24, 2017

使用 no-op 依赖

Release Build 使用的leakcanary-android-no-op依赖只包含LeakCanaryRefWatcher两个类。当你开始定制自己的 LeakCanary 时,你需要确保你的定制化行为只在 debug build 发生。因为他们可能引用了leakcanary-android-no-op中不存在的类。

我们假设你的 Release Build 声明了 ExampleApplication,而 Debug Build 声明了 ExampleApplication 的子类 DebugExampleApplication

公用的代码:

public class ExampleApplication extends Application {

  public static RefWatcher getRefWatcher(Context context) {
    ExampleApplication application = (ExampleApplication) context.getApplicationContext();
    return application.refWatcher;
  }

  private RefWatcher refWatcher;

  @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;
    }
    refWatcher = installLeakCanary();
  }

  protected RefWatcher installLeakCanary() {
    return RefWatcher.DISABLED;
  }
}

Debug Build 代码:

public class DebugExampleApplication extends ExampleApplication {
  @Override protected RefWatcher installLeakCanary() {
    // Build a customized RefWatcher
    RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .watchDelay(10, TimeUnit.SECONDS)
      .buildAndInstall();
    return refWatcher;
  }
}

这样,你的 Release 代码就不会包含leakcanary-android-no-op库中不存在的任何引用。

Icon 和 Label

DisplayLeakActivity 使用默认的 icon 和 label 分别为 R.drawable.leak_canary_iconR.string.leak_canary_display_activity_label。替换这两个资源,它们就是你的了。

res/
  drawable-hdpi/
    leak_canary_icon.png
  drawable-mdpi/
    leak_canary_icon.png
  drawable-xhdpi/
    leak_canary_icon.png
  drawable-xxhdpi/
    leak_canary_icon.png
  drawable-xxxhdpi/
    leak_canary_icon.png
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="leak_canary_display_activity_label">MyLeaks</string>
</resources>

保存 Leak traces

LeakCanary 保存最多 7 次堆数据。你可以这样改变上限:

public class DebugExampleApplication extends ExampleApplication {
  protected RefWatcher installLeakCanary() {
    RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .maxStoredHeapDumps(42)
      .buildAndInstall();
    return refWatcher;
  }
}

译者注:当然你要知道一次 dump 对应一个 hprof 文件。而这些文件可大可小,这取决于你应用使用的内存大小。小则十几兆,大则几百。因此理论上,你也不能无限制地改变,够用即可。

上传到服务器

你可以改变 LeakCanary 的默认行为,不是去分析它,而是把他们上传到你的服务器。

译者注:OOM 并不总是稳定发生的。大多数情况下,它们的复现概率很小。而我们可以在使用脚本测试时,不能总是盯着手机等着问题复现。所以我们可以把筛选过的内存文件自动上传服务器备用。

你需要创建你自己的AbstractAnalysisResultService。一个简单的方案是在你的 Debug 代码中继承DisplayLeakService

public class LeakUploadService extends DisplayLeakService {
  @Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    if (!result.leakFound || result.excludedLeak) {
      return;
    }
    myServer.uploadLeakBlocking(heapDump.heapDumpFile, leakInfo);
  }
}

在 debug Application 中定制的RefWatcher,使其引用你的Service:

public class DebugExampleApplication extends ExampleApplication {
  @Override protected RefWatcher installLeakCanary() {
    RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .listenerServiceClass(LeakUploadService.class);
      .buildAndInstall();
    return refWatcher;
  }
}

最后把你的 Service 在 AndroidManifest.xml 中注册一下:

<?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>

你当然也可以把 leak traces 上传到 Slack 或 HipChat(Sample),以及你想要的任何地方。

忽略特定的引用

如果有些引用导致了泄露,但你仍然想忽略它。把它加入你的ExcludedRefs:

public class DebugExampleApplication extends ExampleApplication {
  @Override protected RefWatcher installLeakCanary() {
    ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults()
        .instanceField("com.example.ExampleClass", "exampleField")
        .build();
    RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .excludedRefs(excludedRefs)
      .buildAndInstall();
    return refWatcher;
  }
}

不监测特定的 Activity

ActivityRefWatcher 默认监测所有的 activity。若要不监测特定的 Activity 那我们不使用默认的监测行为就好了。(只需要监测制定的 Activity 同理):

public class DebugExampleApplication extends ExampleApplication {
  @Override protected RefWatcher installLeakCanary() {
    LeakCanary.enableDisplayLeakActivity(this);
    RefWatcher refWatcher = LeakCanary.refWatcher(this)
      // Notice we call build() instead of buildAndInstall()
      .build();
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
      public void onActivityDestroyed(Activity activity) {
        if (activity instanceof ThirdPartyActivity) {
            return;
        }
        refWatcher.watch(activity);
      }
      // ...
    });
    return refWatcher;
  }
}

运行时动态开关 LeakCanary

RefWatcherheapDumper 替换为自己的就可以实现动态开关。

public class DebugExampleApplication extends ExampleApplication {

 TogglableHeapDumper heapDumper;

 @Override protected RefWatcher installLeakCanary() {
   LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
   AndroidHeapDumper defaultDumper = new AndroidHeapDumper(context, leakDirectoryProvider);
   heapDumper = new TogglableHeapDumper(defaultDumper);
   RefWatcher refWatcher = LeakCanary.refWatcher(this)
     .heapDumper(heapDumper)
     .buildAndInstall();
     return refWatcher;
 }

 public static class TogglableHeapDumper implements HeapDumper {
   private final HeapDumper defaultDumper;
   private boolean enabled = true;

   public TogglableHeapDumper(HeapDumper defaultDumper) {
     this.defaultDumper = defaultDumper;
   }

   public void toggle() {
     enabled = !enabled;
   }

   @Override public File dumpHeap() {
     return enabled? defaultDumper.dumpHeap() : HeapDumper.RETRY_LATER;
   }
 }
}

译者总结:

LeakCanary 行为的核心就是RefWatcher这个类。

定制 LeakCanary 行为的过程,就是改变RefWatcher参数的过程。

RefWatcher 是桥接模式的一个典型应用。

事实上,很多著名的项目包括开源的 Glide、谷歌的 RecyclerView 等,凡是允许开发者高度定制的组件,都多多少少有些桥接模式的影子。

/* 看板娘 */