在以前的文章中我讲到过如何使用eclipse和MAT分析内存泄漏(Android内存泄漏分析实战),但是这样的分析往往发生在内存泄漏之后,只能是亡羊补牢。那么我们能不能更早的发现内存泄漏呢?答案是肯定的,LeakCanary能够做到。延伸阅读(LeakCanary源码解析)
LeakCanary best practice
什么是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'
}
|
在你的Application中
|
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及以后的版本,具体原因详见代码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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库,以及实例代码。运行代码之后,按照界面提示操作几次,稍等几秒,你将会在通知栏看到一个内存泄漏的通知。