今天面试有被问题leakcanary是怎么实现的,自己没看过源码只是简单说了下,猜测是通过监控activity的destroy生命周期方法,因为老的版本还需要自己在BaseActivity中的onDestroy方法中用RefWatcher.watch来监控activity是否被回收。 回来补一下,随便记录下避免容易忘记。
注:这里不分析是如何生成的.hprof内存输出文件也不讲解leakcanary是如何根据.hprof分析出内存泄露的引用路径的。
1.初始化
一般在Application的onCreate中通过LeakCanary.install来初始化。并且仅在debug模式和主进程中初始化使用(leakcanary有一个专门用来分析内存泄露用的进程);
还需要注意的是在gradle中引用时需要根据是debug还是release引用不同的包。
上面两个图是release下引用的包中的初始化代码,可以看出什么都没做。我们主要看debug下的实现。
可以看到RefWatcher是AndroidRefWatcherBuilder通过buildAndInstall构造来的。
buildAndInstall其实就是通过build构造出一个RefWatcher,其中有几个重要的构造参数下面讲。然后如果构造成功则把显示泄露的DisplayLeakActivity设置为enable的,这样才能在手机桌面上出现一个Leaks的图标,点击图标进入的activity就是展示所有内存泄露分出结果的界面。 最后通过installOnIcsPlus来监控activity的onDestroy生命周期方法。
如注释描述,这种方式仅适合api >=14(ICE_CREAM_SANDWICH),因为通过Application来注册监控activity生命周期的方法在14后才加的。14前的只能用老的方式在BaseActivity的onDestroy.
到这算是真正的初始化完了。这里在讲下RefWatcher构造时重要的几个参数。其中部分是在RefWatcher中生成部分是在AndroidRefWatcherBuilder中生成的。
a.watchExecutor:只是一个线程调度器,watch操作是在单独的线程里执行的。
b.gcTrigger:触发gc操作用的。 看注释说Runtime的gc()比System.gc()更可能触发gc操作哦~。
c.heapDumper: 当发现泄露时具体生成.hprof文件的操作。
d.heapDumperListener:生成.hprof成功后回调分析的操作。
e.excludedRefs:内置的一些分析时需要过滤掉的引用列表,这个列表中的引用出现泄露时可能是系统的原因,所以过滤掉。
f.剩下的retainedKeys和queue是用来查找泄露对象用的,retainedKeys中存的是没有被gc回收掉的activity的key,而queue是用来存放activity被正常回收的weakreference(这个weakreference包装了activity对象的引用)。
2.监控Activity泄露
从之前讲的可知,监测到activity的onDestroy时会通过watch来监控它的回收情况。这里会给activity生成一个key和一个weakreference,只不过这个weakref中还包含了对应的key. 然后把key添加到retainedKes中。 最后通过ensureGoneAsync方法调用之前构造RefWatcher时的watchExecutor异步执行了ensureGone方法。
上面的代码就是实现找出泄露的activity的方法。
首先调用removeWeaklyReachableReferences方法把当前已经被回收的activity所对应的key从retainedKeys中删除(因为当weakref中包装的对象如果被gc回收时,会把这个weakref放入queue中)。然后通过gone方法判断key是否还在retainedKeys中,如果不在则表示activity已经被回收,watch结束。如果在则还没回收,这时会通过gcTrigger来主动调一次gc操作,然后在调用removeWeaklyReachableReferences去掉已经初始回收的activity对应的key. 如果再次判断gone返回还是false那么就认定这个activity泄露了。 然后就是通过之前设置的heapDumper来生成.hprof。
3.hprof生成
代码不多,因为生成hprof主要还是用的系统的方法。这里的大致流程就是先得到一个文件来存放生成的内存信息,然后通过mainHandler在主线程post一个显示自定义toast的操作。并添加一个IdleHandler,把toast设置给之前定义的FutrueResult,这里仅是为了保证toast先显示出来在执行Debug.dumpHprofData来生成内存信息,因为这个过程会导致gc,阻塞主线程执行导致卡顿。如果是直接post一个显示toast的操作然后紧接着就执行生成内存信息的方法,则可能导致toast在开始时因为卡顿显示不出来。这也是为什么它的toast里面会提示app will freeze(冻住)。随便提下这里是通过CountDownLatch来达到子线程等待主线程确定已经显示toast的。用法感兴趣的可以自行查找,在某些场景它也许可以帮我们更好地解决线程间需要协同的问题。
4.泄露分析
如图,在watch方法中,如果成功生成.hprof文件则会调用heapdumpListener的analyze方法进行分析。
需要注意的是这里传入了一个分析结果处理的class,它本质是一个IntentService.分析结果出来后会启动这个service来处理。
第一步:开启一个IntentService来执行分析操作。
第二步:通过HeapAnalyzer检查泄露,分析出结果。这里不深入,感兴趣可自行研究。
第三步:代码不贴了,就是把分析结果放到intent中,然后开启之前传入的处理分析结果的IntentService来处理。
5.结果处理
代码不贴了,就在DisplayLeakService的onHeapAnalyzed方法中。 大概就是根据分析结果显示一个Notification通知,通知里面带了一个显示泄露具体信息Activity的pendingintent.
6.总结
除了可以了解这个工具实现监测内存泄露的原理和方法,还可以学到几个新的知识点(对我来说~).
1.WeakReference和SoftReference这种引用可以配合一个ReferenceQueue使用来达到监控对象被回收的功能。
2.Runtime.gc比System.gc更可能触发gc操作。(System.gc其实也是调用的Runtime.gc,只是不一定每次调用都会调用到Runtime.gc)
3.CountdownLatch的作用和用法 。Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
4.IntentService的使用。虽然了解原理但是自己没怎么用到过。