git地址: square/leakcanary
git readme:
中文翻译@Jacksgong
一款Android与Java的内存检测库
“A small leak will sink a gret ship.” - Benjamin Franklin
I. 开始
build.gradle
中的配置:
1 | dependencies { |
Application
class 中的配置:
1 | public class ExampleApplication extends Application { |
这样就可以了! 在debug包中activity内存泄漏将会被监听到,并且将会自动显示一个通知(show a notification)。
II. 为什么要使用LeakCanary?
好问题! 我们已经在 博客文章中回答了这个问题。
III. 应该怎么使用它呢?
使用RefWatcher
来监听引用是否已经被GC:
1 | RefWatcher refWatcher = ; |
LeakCanary.install()
会返回预设的RefWatcher
,并且安装了一个ActivityRefWatcher
来监听activity调用了Activity.onDestroy()
以后的泄漏。
1 | public class ExampleApplication extends Application { |
你也可以使用RefWatcher
来监听fragment的泄漏:
1 | public abstract class BaseFragment extends Fragment { |
IV. LeakCanary是如何工作的呢?
RefWatcher.watch()
创建了一个KeyedWeakReference
到了监控的对象。- 之后,在后台线程,它检查引用是否已经被释放,如果没有它将促发一次GC。
- 如果引用依然没有被释放,它会导出heap到存储在app文件系统的
a.hprof
文件。 HeapAnalyzerService
在单独的一个进程被启动,并且HeapAnalyzer
使用HAHA
来解析heap。HeapAnalyzer
由于采用了单独的reference key,在heap dump中找到了KeyedWeakReference
并且定位到泄漏的引用。HeapAnalyzer
通过计算出到GC根部最短路径的强引用来决定是否这里是泄漏了,并且建立导致泄漏的引用关系链。- 结果将传回在app进程的
DisplayLeakService
,并且显示泄漏通知。
V. 我应该如何拷贝leak trace呢?
可以在Logcat中看到leak trace:
1 | In com.example.leakcanary:1.0:1 com.example.leakcanary.MainActivity has leaked: |
也可以从action bar menu分享leak trace与heap dump文件。
VI. 应该如何解决内存泄漏呢?
一旦拥有了leak trace,就可以分析出哪个路径中的引用不应该存在,然后分析出引用依然存在的原因。通常情况是注册的监听没有反注册,或者是close()
方法没有调用,或者是一个未知的类((通常也是没有句柄的对象,就纯new出来执行了某方法)hold住了外部类的引用。如果你分析不出你代码中的问题,别放弃,可以在Stack Overflow question(使用leakcanary
标签)中创建相关问题。
VII. 我的泄漏是因为执行Android SDK导致的!
随着时间的推移,已经有一些已知的由于Android SDK的执行导致的内存泄漏得到了作为生厂商AOSP的修复。当发生这样的内存泄漏的时候,其实我们作为应用开发者能做的很少。对于这样的问题,LeakCanary已经有内建了一个忽略已知Android SDK泄漏的列表: AndroidExcludedRefs.java
。如果你发现了新的,请提供leak trace、reference key、设备版本与Android版来创建问题,当然如果能够提供一个heap dump的文件连接更好。
这对于新发布的Android来说是特别重要的,你有机会能够帮助尽早发现新的内存泄漏,使整个Android社区受益。
开发版本的快照: Sonatype’s snapshots
repository。
VIII. 超出leak trace范围
通常leak trace是不够的,还需要通过MAT或者YourKit来深挖heap dump,下面是你如何通过heap dump来找出泄漏:
- 找到
com.squareup.leakcanary.KeyedWeakReference
所有的实例。 - 对于每个实例,查看它的
key
成员变量。 - 找到包含与LeakCanary报出的reference key相同
key
成员变量的KeyedWeakReference
。 - 那么这个
KeyedWeakReference
中的reference
成员变量,就是你泄漏了的对象。 - 到此为止,剩余的工作就是,开始查找到GC Roots最短路径(不包含弱引用)。
IX. 定制
图标与标注(Icon and Label)
DisplayLeakActivity
默认是使用默认的图标与标注,当然你可以通过在你的app中提供R.drawable.__leak_canary_icon
与R.string.__leak_canary_display_activity_label
来定制这个:
1 | res/ |
1 |
|
存储leak traces
DisplayLeakActivity
最多在app目录中存储7个heap dumps与leak traces 文件。你可以通过在你的app中提供R.integer.__leak_canary_max_stored_leaks
来定制这个:
1 |
|
上传到服务器
可以通过修改默认的行为来上传leak trace与heap dump到指定的服务器。
创建一个你自己的AbstractAnalysisResultService
。最简单的方法是在debug的app中继承DefaultAnalysisResultService
:
1 | public class LeakUploadService extends DefaultAnalysisResultService { |
要确认发布的Application类使用无效的RefWatcher
:
1 | public class ExampleApplication extends Application { |
在你debug的Application类中创建一个自定义的RefWatcher
:
1 | public class DebugExampleApplication extends ExampleApplication { |
不要忘了在debug的manifest里面注册service:
1 |
|
你也可以上传leak traces到Slack或者HipChat,这里是一个例子
LeakCanary 名称是为了表达canary in a coal mine,因为LeakCanary是通过提供危险预警,检测风险的哨兵,维护者@edenman提的建议!
X. License
1 | Copyright 2015 Square, Inc. |