假设你已经体验过leakcanary了,没有体验过的话花五分钟去体验一下。我的上篇文章
《五分钟体验内存泄露检测LeakCanary》
http://www.jianshu.com/p/51395d8e512f
简单来说,leakcanary就是一个库,app可以利用这个库来检测内存泄露,内存泄露了会在状态栏上通知你。这篇文章就简单说下源码吧,因为源码太简单了,我也不打算多讲。
总体流程###
整个过程就是在应用启动时,构建一个观察者,再注册Activity的回调,在Activity销毁的时候,通过这个观察者来观察是否有内存泄露。流程图就是下面这个,简单的不行。
代码流程也说下啦,不然不像程序员了
在LeakCanary中调用install
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
其中这里的refWatcher(application)是个函数,不是类,你见过类是小写开头的吗?这个函数也在LeakCanary中
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
所以这个refWatcher(application)返回的是一个构造者AndroidRefWatcherBuilder,他要构造谁?还用说吗,当然是构造观察者啦,利用buildAndInstall()函数来构造观察者
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
......
.......
return refWatcher;
}
利用了build()函数来构造,看看罗,先屏住呼吸,这个函数稍微长那么一点点,不过也很简单,偷懒直接看他返回值就好,哈哈哈哈,返回RefWatcher对象,RefWatcher是何方神圣?这个RefWatcher对象厉害了,他就是传说中的观察者,拥有火眼金睛,专门观察内存泄露的,历史会记住他的。
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
那看看观察者RefWatcher是怎么构造的,既然它这么鬼重要
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
可以看到他用了好几个东西构造呢
watchExecutor 这玩意是个线程控制器,控制activity销毁后5s再去观察泄露情况
debuggerControl 这玩意是控制debugger的,没多大作用,不想鸟他
gcTrigger 这玩意用来触发垃圾回收的,上面的线程控制器5s后观察有泄露,不算泄露,必须垃圾回收后,在去观察一次。所以最多会观察两次。第一次是5s后观察,第二次是5s后再垃圾回收后观察。
heapDumpListener hprof文件解释完后,会告诉这个监听者。这个监听者就会更新状态栏
excludedRefs 这玩意是做额外处理的,这里面定义了一些类,如果是这些类泄露了,不会提示的。例如我就是想我的Activity泄露,你别管我,那你可以把类名加到excludedRefs 中。
上面这些类差不多就包含了库里面的所有java文件了,我截个图你看,他妈其实这个库没多少文件。所以说代码不再多,没bug就行。
再看看这个源码项目目录,让自己有个大概概念,基本上就是由两个库组成嘛,一个是java基础库,一个是Android定制库啦。Android定制库的很多类都是java基础库的子类,例如基础库的构造者叫RefWatcherBuilder,Android定制库的构造者叫AndroidRefWatcherBuilder ,这都是很顺其自然的东西啦。
妈的,好像有点跑题了。
上面说到构造了观察者RefWatcher,然后为了做到RefWatcher在activity销毁的时候去观察泄露,要先注册Activity的回调,还是在buildAndInstall函数。
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
}
return refWatcher;
}
build()函数构造了观察者了,然后下面一句ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);就是注册回调。
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
....
activityRefWatcher.watchActivities();
....
}
进入 activityRefWatcher.watchActivities();
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
终于看到回调函数注册了耶,看看回调是怎么处理的拉,天,最后就是在activity的ondestory里面利用观察者去观察泄露啦refWatcher.watch(activity);
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity); //观察泄露
}
我嚓,废了好大劲终于观察者refWatcher可以去观察了。下面看看他妹的怎么观察的吧。
refWatcher观察泄露原理###
原理上节说过了,就是利用弱引用WeakReference和引用序列ReferenceQueue的原理实现的。
在activity对象ondestory的时候,新建一个WeakReference对象指向activity对象,如果activity对象被垃圾回收的话,WeakReference对象就会进入引用序列ReferenceQueue。所以,我们只需要在activity对象ondestory后去查看ReferenceQueue序列是否有该WeakReference对象即可。
第一次观察是activity destory 5秒后,观察,如果发现ReferenceQueue序列还没有WeakReference对象,就进入第二次观察,如果有了,就说明没有泄露,结束观察。
第二次观察,跟第一次相比,区别在于会先进行垃圾回收,再去观察ReferenceQueue序列情况。
好了,又要开始看源码了,god
在RefWatcher中的watch函数切入
public void watch(Object watchedReference, String referenceName) {
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
首先用一个key来标记这个指向activity的WeakReference对象,key就相当于这弱引用的名字嘛,例如这个弱引用的名字叫“林黛玉”,林黛玉弱弱的。然后把这个名字key放在一个容器retainedKeys中。
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
这一句就是把弱引用跟引用序列关联在一起啦。然后过5s,读一下引用序列,看一下还有没有这个弱引用
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
queue.poll()就是从引用序列去拿弱引用啦,拿到弱引用后,就读弱引用名字ref.key,如果这个名字在容器retainedKeys中,就把名字从容器去掉。例如读到“林黛玉”,就把林黛玉的名字移除出retainedKeys。最后看retainedKeys有没有“林黛玉”,就知道林黛玉有没有泄露了。gone()函数返回true就是没有泄露。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
整的实现就在一个函数ensureGone中
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences(); //查看弱引用序列,清理容器retainedKeys
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) { //容器没有“林黛玉”,说明没有内存泄漏,结束观察
return DONE;
}
gcTrigger.runGc(); //能走到这里,说明容器还有林黛玉,先垃圾回收一些
removeWeaklyReachableReferences();
if (!gone(reference)) { //说明林黛玉泄露了
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap(); //导出hprof文件
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
Log.i("wenfeng","Could not dump the heap.");
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs)); //解释hprof文件
}
return DONE;
}
最后林黛玉泄露后,会开启一个服务去解释内存堆文件,解释结果会返回给heapdumpListener。
堆文件也有保存在本地,pend代表正在解释的文件,其他是解释完的了。
这个文件的保存路径是由DefaultLeakDirectoryProvider决定的,他就是首先看sdcard有没有权限,有权限就把hprof文件保存到sdcard,没有就保存到内存卡。逻辑太简单了,自己看去。
至于怎么解释hprof文件,哎,又是个大课题,先不说了,我都消化不良了。原理反正就是利用HAHA库去解释,传入弱引用到HAHA库,然后库就去查找这个弱引用对象的最短gcroot路径,然后打印出来,大概这样。有空再聊。
上面说5s后开始观察泄露,是怎么实现的?###
哎,其实是在ensureGoneAsync中实现的拉,光看名字就知道是异步的拉
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
Log.i("wenfeng","ensureGone");
return ensureGone(reference, watchStartNanoTime);
}
});
}
就是有个执行者执行任务,这个执行者就是AndroidWatchExecutor,最后就会在postDelayed中利用子线程去观察泄露啦。可以看到这个还有失败重试机制呢。失败越多,延时就越长。具体你们自己看源码,不细说了。
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
我天,写了这么多字,吓到我了。一般我都是写几百字的,特别在这个快餐时代。其实没必要写这么多,你看看人家google的android视频,都是5分钟之内的,比你一小时来的强。