今天面試有被問題leakcanary是怎么實現(xiàn)的赚窃,自己沒看過源碼只是簡單說了下庐舟,猜測是通過監(jiān)控activity的destroy生命周期方法,因為老的版本還需要自己在BaseActivity中的onDestroy方法中用RefWatcher.watch來監(jiān)控activity是否被回收。 ?回來補一下情连,隨便記錄下避免容易忘記貌矿。
注:這里不分析是如何生成的.hprof內(nèi)存輸出文件也不講解leakcanary是如何根據(jù).hprof分析出內(nèi)存泄露的引用路徑的炭菌。
1.初始化
一般在Application的onCreate中通過LeakCanary.install來初始化。并且僅在debug模式和主進程中初始化使用(leakcanary有一個專門用來分析內(nèi)存泄露用的進程);
還需要注意的是在gradle中引用時需要根據(jù)是debug還是release引用不同的包逛漫。
上面兩個圖是release下引用的包中的初始化代碼黑低,可以看出什么都沒做。我們主要看debug下的實現(xiàn)。
可以看到RefWatcher是AndroidRefWatcherBuilder通過buildAndInstall構(gòu)造來的克握。
buildAndInstall其實就是通過build構(gòu)造出一個RefWatcher蕾管,其中有幾個重要的構(gòu)造參數(shù)下面講。然后如果構(gòu)造成功則把顯示泄露的DisplayLeakActivity設置為enable的菩暗,這樣才能在手機桌面上出現(xiàn)一個Leaks的圖標掰曾,點擊圖標進入的activity就是展示所有內(nèi)存泄露分出結(jié)果的界面。 ?最后通過installOnIcsPlus來監(jiān)控activity的onDestroy生命周期方法停团。
如注釋描述旷坦,這種方式僅適合api >=14(ICE_CREAM_SANDWICH),因為通過Application來注冊監(jiān)控activity生命周期的方法在14后才加的。14前的只能用老的方式在BaseActivity的onDestroy.
到這算是真正的初始化完了佑稠。這里在講下RefWatcher構(gòu)造時重要的幾個參數(shù)秒梅。其中部分是在RefWatcher中生成部分是在AndroidRefWatcherBuilder中生成的。
a.watchExecutor:只是一個線程調(diào)度器舌胶,watch操作是在單獨的線程里執(zhí)行的捆蜀。
b.gcTrigger:觸發(fā)gc操作用的。 看注釋說Runtime的gc()比System.gc()更可能觸發(fā)gc操作哦~辆琅。
c.heapDumper: 當發(fā)現(xiàn)泄露時具體生成.hprof文件的操作漱办。
d.heapDumperListener:生成.hprof成功后回調(diào)分析的操作。
e.excludedRefs:內(nèi)置的一些分析時需要過濾掉的引用列表婉烟,這個列表中的引用出現(xiàn)泄露時可能是系統(tǒng)的原因娩井,所以過濾掉。
f.剩下的retainedKeys和queue是用來查找泄露對象用的似袁,retainedKeys中存的是沒有被gc回收掉的activity的key,而queue是用來存放activity被正扯蠢保回收的weakreference(這個weakreference包裝了activity對象的引用)。?
2.監(jiān)控Activity泄露
從之前講的可知昙衅,監(jiān)測到activity的onDestroy時會通過watch來監(jiān)控它的回收情況扬霜。這里會給activity生成一個key和一個weakreference,只不過這個weakref中還包含了對應的key. 然后把key添加到retainedKes中而涉。 最后通過ensureGoneAsync方法調(diào)用之前構(gòu)造RefWatcher時的watchExecutor異步執(zhí)行了ensureGone方法著瓶。
上面的代碼就是實現(xiàn)找出泄露的activity的方法。
首先調(diào)用removeWeaklyReachableReferences方法把當前已經(jīng)被回收的activity所對應的key從retainedKeys中刪除(因為當weakref中包裝的對象如果被gc回收時啼县,會把這個weakref放入queue中)材原。然后通過gone方法判斷key是否還在retainedKeys中,如果不在則表示activity已經(jīng)被回收季眷,watch結(jié)束余蟹。如果在則還沒回收,這時會通過gcTrigger來主動調(diào)一次gc操作子刮,然后在調(diào)用removeWeaklyReachableReferences去掉已經(jīng)初始回收的activity對應的key. 如果再次判斷gone返回還是false那么就認定這個activity泄露了威酒。 然后就是通過之前設置的heapDumper來生成.hprof。
3.hprof生成
代碼不多,因為生成hprof主要還是用的系統(tǒng)的方法葵孤。這里的大致流程就是先得到一個文件來存放生成的內(nèi)存信息担钮,然后通過mainHandler在主線程post一個顯示自定義toast的操作。并添加一個IdleHandler佛呻,把toast設置給之前定義的FutrueResult裳朋,這里僅是為了保證toast先顯示出來在執(zhí)行Debug.dumpHprofData來生成內(nèi)存信息,因為這個過程會導致gc吓著,阻塞主線程執(zhí)行導致卡頓鲤嫡。如果是直接post一個顯示toast的操作然后緊接著就執(zhí)行生成內(nèi)存信息的方法,則可能導致toast在開始時因為卡頓顯示不出來绑莺。這也是為什么它的toast里面會提示app will freeze(凍住)暖眼。隨便提下這里是通過CountDownLatch來達到子線程等待主線程確定已經(jīng)顯示toast的。用法感興趣的可以自行查找纺裁,在某些場景它也許可以幫我們更好地解決線程間需要協(xié)同的問題诫肠。
4.泄露分析
如圖,在watch方法中欺缘,如果成功生成.hprof文件則會調(diào)用heapdumpListener的analyze方法進行分析栋豫。
需要注意的是這里傳入了一個分析結(jié)果處理的class,它本質(zhì)是一個IntentService.分析結(jié)果出來后會啟動這個service來處理。
第一步:開啟一個IntentService來執(zhí)行分析操作谚殊。
第二步:通過HeapAnalyzer檢查泄露丧鸯,分析出結(jié)果。這里不深入嫩絮,感興趣可自行研究丛肢。
第三步:代碼不貼了,就是把分析結(jié)果放到intent中剿干,然后開啟之前傳入的處理分析結(jié)果的IntentService來處理蜂怎。
5.結(jié)果處理
代碼不貼了,就在DisplayLeakService的onHeapAnalyzed方法中置尔。 大概就是根據(jù)分析結(jié)果顯示一個Notification通知杠步,通知里面帶了一個顯示泄露具體信息Activity的pendingintent.
6.總結(jié)
除了可以了解這個工具實現(xiàn)監(jiān)測內(nèi)存泄露的原理和方法,還可以學到幾個新的知識點(對我來說~).
1.WeakReference和SoftReference這種引用可以配合一個ReferenceQueue使用來達到監(jiān)控對象被回收的功能榜轿。
2.Runtime.gc比System.gc更可能觸發(fā)gc操作幽歼。(System.gc其實也是調(diào)用的Runtime.gc,只是不一定每次調(diào)用都會調(diào)用到Runtime.gc)
3.CountdownLatch的作用和用法 差导。Java并發(fā)編程:CountDownLatch、CyclicBarrier和Semaphore
4.IntentService的使用猪勇。雖然了解原理但是自己沒怎么用到過设褐。