[翻译] 定制你的 LeakCanary
使用 no-op 依赖
Release Build 使用的leakcanary-android-no-op
依赖只包含LeakCanary
和RefWatcher
两个类。当你开始定制自己的 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_icon
和 R.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
把 RefWatcher
的 heapDumper
替换为自己的就可以实现动态开关。
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 等,凡是允许开发者高度定制的组件,都多多少少有些桥接模式的影子。