您的位置:首页 > 移动开发 > Android开发

LeakCanary,Android内存泄露处理利器

2016-08-01 16:35 267 查看
本篇博文转自黑月神话,原文链接黑月神话

在以前的文章中我讲到过如何使用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库,以及实例代码。运行代码之后,按照界面提示操作几次,稍等几秒,你将会在通知栏看到一个内存泄漏的通知。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 内存泄露