本篇博文转自黑月神话,原文链接黑月神话
在以前的文章中我讲到过如何使用eclipse和MAT分析内存泄漏(Android内存泄漏分析实战),但是这样的分析往往发生在内存泄漏之后,只能是亡羊补牢。那么我们能不能更早的发现内存泄漏呢?答案是肯定的,LeakCanary能够做到。延伸阅读(LeakCanary源码解析)
什么是LeakCanary
LeakCanary是一个用于检测内存泄漏的工具,可以用于Java和Android,是由著名开源组织Square贡献。
开始使用
debug版本和realse版本用不同的依赖库。由于国内被墙的原因Maven没办法用,导致Android Studio使用不便。所以我自己整合了一个适合eclipse的LeakCanary的库,代码托管在github上面。项目地址:https://github.com/mooncong/Leakcanary-lib.git
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
那么接下来,LeakCanary将会在debug版本中自动探测内存泄漏,当发生内存泄漏的时候就会在通知栏显示一个通知。
什么是内存泄露
对象在其生命周期内完成使命之后,我们就希望这些对象被回收掉。但是如果还存在对象的引用,那么这个对象将不会被回收的。它还会占用内存,这就造成了内存泄露。持续累加,内存很快被耗尽。
比如,当Activity.onDestroy 被调用之后,activity 以及它涉及到的 view 和相关的 bitmap 都应该被回收。但是,如果有一个后台线程持有这个 activity 的引用,那么 activity 对应的内存就不能被回收。这最终将会导致内存耗尽,然后因为 OOM 而 crash。
如何使用
使用RefWatcher监控本应该被垃圾回收器回收的对象
RefWatcher refWatcher = {...};
// 监控一个object对象
refWatcher.watch(object);
LeakCanary.install()返回一个预定义的RefWatcher。它将启动一个ActivityRefWatcher,在Activity.onDestroy()方法调用之后,自动探测Activity的内存泄漏。(注:只支持ICS及以后的版本,具体原因详见代码)
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();
refWatcher = LeakCanary.install(this);
}
}
也可以使用RefWatcher探测Fragment泄漏
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
LeakCanary工作原理
RefWatcher.watch()创建一个KeyedWeakReference到北监控的对象。
接下来,在后台线程中检测这个引用是否被清除,如果没有将会触发GC。
如果引用仍然没有清除,将heap内存dump到一个.hprof的文件存放到手机系统里。
HeapAnalyzerService在另外一个独立的进程中启动,使用HeapAnalyzer解析heap内存通过HAHA这个项目
HeapAnalyzer计算出到GC ROOTs的最短强引用路径决定是否发生Leak,然后建立导致泄漏的引用链。
结果被回传到应用程序进程的DisplayLeakService中,然后显示一个泄漏的通知。
如何复制leak trace
在logcat中可以看见leak trace
In com.example.leakcanary:1.0:1 com.example.leakcanary.MainActivity has leaked:
* GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1')
* references com.example.leakcanary.MainActivity$3.this$0 (anonymous class extends android.os.AsyncTask)
* leaks com.example.leakcanary.MainActivity instance
* Reference Key: e71f3bf5-d786-4145-8539-584afaecad1d
* Device: Genymotion generic Google Nexus 6 - 5.1.0 - API 22 - 1440x2560 vbox86p
* Android Version: 5.1 API: 22
* Durations: watch=5086ms, gc=110ms, heap dump=435ms, analysis=2086ms
也可以从Action bar分享leak trace和heap内存文件。
如何修复内存泄漏?
一旦发生内存泄漏,找出哪一个引用不应该存在。然后分析为什么它还存在。通常它是一个注册的监听器没有被反注册,或者是close()方法没有调用,一个匿名内部类持有了一个外部类的引用,等等。
Android SDK导致的内存泄漏
在过去的日子里,很多内存泄漏已经被修复了,但是当泄漏发生的时候,普通的应用开发很难去修复它。因此,LeakCanary已经建立了一个已知问题的列表,AndroidExcludedRefs.java。如果你发现一个新问题。请提交一个issue并附上Leak trace,reference key,设备和系统版本。要是附上heap文件的链接就更好了。在新发布的Android版本中尤其重要。你有机会帮助尽早的发现内存泄漏,那将会有益于整个Android社区。
Leak trace之外
有时候Leak trace不能够,可以使用MAT或者YourKit深挖dump文件。MAT使用可以参考以前的一片文章Android内存泄漏分析实战
找到所有的com.squareup.leakcanary.KeyedWeakReference实例。
查看他们的每一个key值。
找到key字段等于LeakCanary报告的引用key的KeyedWeakReference。
KeyedWeakReference的referent字段就是泄漏的对象。
接下来,就是动手修复了。最好是检查到 GC root 的最短强引用路径开始。
保持Leak traces
DisplayLeakActivity默认保存7个heap dumps和leak traces,可以通过下面的配置自定义。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="__leak_canary_max_stored_leaks">20</integer>
</resources>
上传 leak trace 到服务器
你可以改变处理完成的默认行为,将 leak trace 和 heap dump 上传到你的服务器以便统计分析。 创建一个 AbstractAnalysisResultService,最简单的就是继承 DefaultAnalysisResultService
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);
}
}
请确认Realse版本的应用使用RefWatcher.DISABLED
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();
refWatcher = installLeakCanary();
}
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
}
自定义RefWatcher
public class DebugExampleApplication extends ExampleApplication {
protected RefWatcher installLeakCanary() {
return LeakCanary.install(app, LeakUploadService.class);
}
}
不要忘记在manifest中注册Service
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.androi.com/tools"
>
<application android:name="com.example.DebugExampleApplication">
<service android:name="com.example.LeakUploadService" />
</application>
</manifest>
demo
LeakCanary实战,包含eclipse整合的lib库,以及实例代码。运行代码之后,按照界面提示操作几次,稍等几秒,你将会在通知栏看到一个内存泄漏的通知。