leakcanary簡(jiǎn)單分析

今天面試有被問(wèn)題leakcanary是怎么實(shí)現(xiàn)的,自己沒(méi)看過(guò)源碼只是簡(jiǎn)單說(shuō)了下,猜測(cè)是通過(guò)監(jiān)控activity的destroy生命周期方法,因?yàn)槔系陌姹具€需要自己在BaseActivity中的onDestroy方法中用RefWatcher.watch來(lái)監(jiān)控activity是否被回收。 ?回來(lái)補(bǔ)一下,隨便記錄下避免容易忘記。

注:這里不分析是如何生成的.hprof內(nèi)存輸出文件也不講解leakcanary是如何根據(jù).hprof分析出內(nèi)存泄露的引用路徑的。

1.初始化

一般在Application的onCreate中通過(guò)LeakCanary.install來(lái)初始化。并且僅在debug模式和主進(jìn)程中初始化使用(leakcanary有一個(gè)專門用來(lái)分析內(nèi)存泄露用的進(jìn)程);

還需要注意的是在gradle中引用時(shí)需要根據(jù)是debug還是release引用不同的包。

上面兩個(gè)圖是release下引用的包中的初始化代碼,可以看出什么都沒(méi)做。我們主要看debug下的實(shí)現(xiàn)。

可以看到RefWatcher是AndroidRefWatcherBuilder通過(guò)buildAndInstall構(gòu)造來(lái)的。

buildAndInstall其實(shí)就是通過(guò)build構(gòu)造出一個(gè)RefWatcher,其中有幾個(gè)重要的構(gòu)造參數(shù)下面講。然后如果構(gòu)造成功則把顯示泄露的DisplayLeakActivity設(shè)置為enable的,這樣才能在手機(jī)桌面上出現(xiàn)一個(gè)Leaks的圖標(biāo),點(diǎn)擊圖標(biāo)進(jìn)入的activity就是展示所有內(nèi)存泄露分出結(jié)果的界面。 ?最后通過(guò)installOnIcsPlus來(lái)監(jiān)控activity的onDestroy生命周期方法。

如注釋描述,這種方式僅適合api >=14(ICE_CREAM_SANDWICH),因?yàn)橥ㄟ^(guò)Application來(lái)注冊(cè)監(jiān)控activity生命周期的方法在14后才加的。14前的只能用老的方式在BaseActivity的onDestroy.

到這算是真正的初始化完了。這里在講下RefWatcher構(gòu)造時(shí)重要的幾個(gè)參數(shù)。其中部分是在RefWatcher中生成部分是在AndroidRefWatcherBuilder中生成的。

a.watchExecutor:只是一個(gè)線程調(diào)度器,watch操作是在單獨(dú)的線程里執(zhí)行的。

b.gcTrigger:觸發(fā)gc操作用的。 看注釋說(shuō)Runtime的gc()比System.gc()更可能觸發(fā)gc操作哦~。

c.heapDumper: 當(dāng)發(fā)現(xiàn)泄露時(shí)具體生成.hprof文件的操作。

d.heapDumperListener:生成.hprof成功后回調(diào)分析的操作。

e.excludedRefs:內(nèi)置的一些分析時(shí)需要過(guò)濾掉的引用列表,這個(gè)列表中的引用出現(xiàn)泄露時(shí)可能是系統(tǒng)的原因,所以過(guò)濾掉。

f.剩下的retainedKeys和queue是用來(lái)查找泄露對(duì)象用的,retainedKeys中存的是沒(méi)有被gc回收掉的activity的key,而queue是用來(lái)存放activity被正?;厥盏膚eakreference(這個(gè)weakreference包裝了activity對(duì)象的引用)。?

2.監(jiān)控Activity泄露

從之前講的可知,監(jiān)測(cè)到activity的onDestroy時(shí)會(huì)通過(guò)watch來(lái)監(jiān)控它的回收情況。這里會(huì)給activity生成一個(gè)key和一個(gè)weakreference,只不過(guò)這個(gè)weakref中還包含了對(duì)應(yīng)的key. 然后把key添加到retainedKes中。 最后通過(guò)ensureGoneAsync方法調(diào)用之前構(gòu)造RefWatcher時(shí)的watchExecutor異步執(zhí)行了ensureGone方法。

上面的代碼就是實(shí)現(xiàn)找出泄露的activity的方法。

首先調(diào)用removeWeaklyReachableReferences方法把當(dāng)前已經(jīng)被回收的activity所對(duì)應(yīng)的key從retainedKeys中刪除(因?yàn)楫?dāng)weakref中包裝的對(duì)象如果被gc回收時(shí),會(huì)把這個(gè)weakref放入queue中)。然后通過(guò)gone方法判斷key是否還在retainedKeys中,如果不在則表示activity已經(jīng)被回收,watch結(jié)束。如果在則還沒(méi)回收,這時(shí)會(huì)通過(guò)gcTrigger來(lái)主動(dòng)調(diào)一次gc操作,然后在調(diào)用removeWeaklyReachableReferences去掉已經(jīng)初始回收的activity對(duì)應(yīng)的key. 如果再次判斷gone返回還是false那么就認(rèn)定這個(gè)activity泄露了。 然后就是通過(guò)之前設(shè)置的heapDumper來(lái)生成.hprof。

3.hprof生成

代碼不多,因?yàn)樯蒱prof主要還是用的系統(tǒng)的方法。這里的大致流程就是先得到一個(gè)文件來(lái)存放生成的內(nèi)存信息,然后通過(guò)mainHandler在主線程post一個(gè)顯示自定義toast的操作。并添加一個(gè)IdleHandler,把toast設(shè)置給之前定義的FutrueResult,這里僅是為了保證toast先顯示出來(lái)在執(zhí)行Debug.dumpHprofData來(lái)生成內(nèi)存信息,因?yàn)檫@個(gè)過(guò)程會(huì)導(dǎo)致gc,阻塞主線程執(zhí)行導(dǎo)致卡頓。如果是直接post一個(gè)顯示toast的操作然后緊接著就執(zhí)行生成內(nèi)存信息的方法,則可能導(dǎo)致toast在開(kāi)始時(shí)因?yàn)榭D顯示不出來(lái)。這也是為什么它的toast里面會(huì)提示app will freeze(凍住)。隨便提下這里是通過(guò)CountDownLatch來(lái)達(dá)到子線程等待主線程確定已經(jīng)顯示toast的。用法感興趣的可以自行查找,在某些場(chǎng)景它也許可以幫我們更好地解決線程間需要協(xié)同的問(wèn)題。

4.泄露分析

如圖,在watch方法中,如果成功生成.hprof文件則會(huì)調(diào)用heapdumpListener的analyze方法進(jìn)行分析。

需要注意的是這里傳入了一個(gè)分析結(jié)果處理的class,它本質(zhì)是一個(gè)IntentService.分析結(jié)果出來(lái)后會(huì)啟動(dòng)這個(gè)service來(lái)處理。

第一步:開(kāi)啟一個(gè)IntentService來(lái)執(zhí)行分析操作。

第二步:通過(guò)HeapAnalyzer檢查泄露,分析出結(jié)果。這里不深入,感興趣可自行研究。

第三步:代碼不貼了,就是把分析結(jié)果放到intent中,然后開(kāi)啟之前傳入的處理分析結(jié)果的IntentService來(lái)處理。

5.結(jié)果處理

代碼不貼了,就在DisplayLeakService的onHeapAnalyzed方法中。 大概就是根據(jù)分析結(jié)果顯示一個(gè)Notification通知,通知里面帶了一個(gè)顯示泄露具體信息Activity的pendingintent.


6.總結(jié)

除了可以了解這個(gè)工具實(shí)現(xiàn)監(jiān)測(cè)內(nèi)存泄露的原理和方法,還可以學(xué)到幾個(gè)新的知識(shí)點(diǎn)(對(duì)我來(lái)說(shuō)~).

1.WeakReference和SoftReference這種引用可以配合一個(gè)ReferenceQueue使用來(lái)達(dá)到監(jiān)控對(duì)象被回收的功能。

2.Runtime.gc比System.gc更可能觸發(fā)gc操作。(System.gc其實(shí)也是調(diào)用的Runtime.gc,只是不一定每次調(diào)用都會(huì)調(diào)用到Runtime.gc)

3.CountdownLatch的作用和用法 。Java并發(fā)編程:CountDownLatch、CyclicBarrier和Semaphore

4.IntentService的使用。雖然了解原理但是自己沒(méi)怎么用到過(guò)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容