本文基於LeakCanary版本: 'com.squareup.leakcanary:leakcanary-android:2.4'
java
在開始LeakCanary源碼分析前,先來了解下Refercence
及ReferenceQueue
,它們是LeakCanary實現內存泄漏監聽的核心。android
(1) Reference即引用,是一個泛型抽象類。Android中的SoftReference
(軟引用)、WeakReference
(弱引用)、PhantomReference
(虛引用)都是繼承自Reference。來看下Reference的幾個主要成員變量。web
public abstract class Reference<T> {
// 引用對象,被回收時置null volatile T referent; //保存即將被回收的reference對象 final ReferenceQueue<? super T> queue; //在Enqueued狀態下即引用加入隊列時,指向下一個待處理Reference對象,默認爲null Reference queueNext; //在Pending狀態下,待入列引用,默認爲null Reference<?> pendingNext; } 複製代碼
Reference有四種狀態:Active
、Pending
、Enqueued
、Inactive
。聲明的時候默認Active狀態,四種狀態的切換關係:數據結構
Pending
,此時queueNext爲空,pendingNext不爲空。
Enqueued
,此時queueNext不爲空,pendingNext爲該Reference)。
Inactive
,此時queueNext爲一個新建虛引用(虛引用get返回null),pendingNext爲該Reference
Inactive
,此時queueNext、pendingNext都爲null
(2) ReferenceQueue
則是一個單向鏈表實現的隊列數據結構,存儲的是Reference對象。包含了入列enqueue、出列poll和移除remove操做app
Reference配合ReferenceQueue就能夠實現對象回收監聽了,先經過一個示例來看看是怎麼實現的。dom
//建立一個引用隊列
ReferenceQueue queue = new ReferenceQueue(); //建立弱引用,並關聯引用隊列queue WeakReference reference = new WeakReference(new Object(),queue); System.out.println(reference); System.gc(); //當reference被成功回收後,能夠從queue中獲取到該引用 System.out.println(queue.remove()); 複製代碼
示例中的對象固然是能夠正常回收的,因此回收後能夠在關聯的引用隊列queue中獲取到該引用。反之,若某個應該被回收的對象,GC結束後在queue中未找到該引用,則代表該引用存在內存泄漏風險,這也就是LeakCanary的基本原理了。編輯器
2.0以前的版本接入過程除了在build.gradle中引入項目外,還須要調用LeakCanary.install(this);
來進行初始化工做。在2.0以後的版本只須要在build.gradle引入項目就完事了。那麼問題來了:2.0以後的版本初始化工做是在哪裏完成的呢?
找了許久,終於在項目工程:leakcanary-object-watcher-android
的manifest文件中發現了祕密:ide
<application>
<provider android:name="leakcanary.internal.AppWatcherInstaller$MainProcess" android:authorities="${applicationId}.leakcanary-installer" android:enabled="@bool/leak_canary_watcher_auto_install" android:exported="false"/> </application> 複製代碼
這裏註冊了一個繼承自ContentProvider
的AppWatcherInstaller
。咱們知道在app啓動時,會先調用註冊的ContentProvider的onCreate完成初始化,在AppWatcherInstaller.onCreate
中果真找到了熟悉的install方法:函數
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application AppWatcher.manualInstall(application) return true } 複製代碼
調用鏈:AppWatcher.manualInstall
-->InternalAppWatcher.install
。具體的初始化邏輯是在InternalAppWatcher,來看源碼:oop
fun install(application: Application) {
//確保在主線程,不然拋出UnsupportedOperationException異常 checkMainThread() //確保application已賦值,application是lateinit修飾的延遲初始化變量 if (this::application.isInitialized) { return } //leakcanary日誌初始化 SharkLog.logger = DefaultCanaryLog() InternalAppWatcher.application = application //日誌配置初始化 val configProvider = { AppWatcher.config } //Activity內存泄漏監聽器初始化 ActivityDestroyWatcher.install(application, objectWatcher, configProvider) //Fragment內存泄漏監聽器初始化 FragmentDestroyWatcher.install(application, objectWatcher, configProvider) //註冊內存泄漏事件回調 onAppWatcherInstalled(application) } 複製代碼
ps:ContentProvider的核心方法CURD在AppWatcherInstaller都是空實現,只用到了onCreate。原來ContentProvider還能夠這麼玩,新姿式get。須要注意的是ContentProvider.onCreate
調用時機介於Application的attachBaseContext
和onCreate
之間,因此不能依賴以後初始化的其餘SDK。
在前面初始過程當中,分別建立了針對Activity及Fragment的監聽器。咱們這裏以Activity監聽爲例進行分析,Fragment監聽除了生命週期監聽方式不一樣外後面的流程都是同樣的。
companion object {
fun install( application: Application, objectWatcher: ObjectWatcher, configProvider: () -> Config ) { //實例化ActivityDestroyWatcher val activityDestroyWatcher = ActivityDestroyWatcher(objectWatcher, configProvider) //註冊ActivityLifecycle監聽 application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks) } } 複製代碼
registerActivityLifecycleCallbacks
是Android Application的一個方法,註冊了該方法,能夠經過回調獲取app中每個Activity的生命週期變化。再來看看ActivityDestroyWatcher對生命週期回調的處理:
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { if (configProvider().watchActivities) { objectWatcher.watch( activity, "${activity::class.java.name} received Activity#onDestroy() callback" ) } } } 複製代碼
ps:ActivityLifecycleCallbacks生命週期回調有那麼多,爲何只用重寫其中一個?關鍵在於by noOpDelegate()
,經過類委託機制將其餘回調實現都交給noOpDelegate,而noOpDelegate是一個空實現的動態代理。新姿式get+1,在遇到只須要實現接口的部分方法時,就能夠這麼玩了,其餘方法實現都委託給空實現代理類就行了。
接着看監聽到Activity onDestroy後的處理:
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { if (configProvider().watchActivities) { objectWatcher.watch( activity, "${activity::class.java.name} received Activity#onDestroy() callback" ) } } } 複製代碼
經過ObjectWatcher來監聽該Activity,即認爲該Activity實例應該被銷燬。若是不能正常銷燬則代表存在內存泄漏。
@Synchronized fun watch( watchedObject: Any, description: String ) { if (!isEnabled()) { return } //@1.清空queue,即移除以前已回收的引用 removeWeaklyReachableObjects() //生成UUID val key = UUID.randomUUID() .toString() //記錄當前時間 val watchUptimeMillis = clock.uptimeMillis() //將當前Activity對象封裝成KeyedWeakReference,並關聯引用隊列queue //KeyedWeakReference繼承自WeakReference,封裝了用於監聽對象的輔助信息 val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue) //輸出日誌 SharkLog.d { "Watching " + (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") + (if (description.isNotEmpty()) " ($description)" else "") + " with key $key" } //將弱引用reference存入監聽列表watchedObjects watchedObjects[key] = reference //@2.進行一次後臺檢查任務,判斷引用對象是否未被回收 checkRetainedExecutor.execute { moveToRetained(key) } } 複製代碼
@1.清空queue,即移除以前已回收的引用。
這個方法很重要,第一次調用是清除以前的已回收對象,後面還會再次調用該方法判斷引用是否正常回收。
這裏涉及到的兩個重要變量:
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference? do { //遍歷引用隊列 ref = queue.poll() as KeyedWeakReference? //將引用隊列中的Reference對象從監聽列表watchedObjects中移除 if (ref != null) { watchedObjects.remove(ref.key) } } while (ref != null) } 複製代碼
@2.進行一次後臺檢查任務
moveToRetained
,5秒後判斷引用對象是否未被回收。
該任務是延遲5s後執行的
private val checkRetainedExecutor = Executor {
//val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5), mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis) } 複製代碼
@Synchronized private fun moveToRetained(key: String) {
//遍歷引用隊列,並將引用隊列中的引用從監聽列表watchedObjects中移除 removeWeaklyReachableObjects() //若對象未能成功移除,則代表引用對象可能存在內存泄漏 val retainedRef = watchedObjects[key] if (retainedRef != null) { retainedRef.retainedUptimeMillis = clock.uptimeMillis() //@3.onObjectRetainedListeners內存泄漏事件回調 onObjectRetainedListeners.forEach { it.onObjectRetained() } } } 複製代碼
在這裏理一下moveToRetained的處理邏輯:
queue
,經過
removeWeaklyReachableObjects
方法遍歷
queue
獲取該引用對象後,將其從監聽列表
watchedObjects
中移除。因此
watchedObjects[key]
也就沒法獲取到引用對象了。
queue
中也就找不到該對象,也就是說監聽列表
watchedObjects
中該對象沒有被刪掉。經過
watchedObjects[key]
能夠拿到該引用對象,便可以判斷該引用對象存在內存泄漏問題。
@3.onObjectRetainedListeners內存泄漏事件回調
發現內存泄漏對象後會調用onObjectRetainedListeners
監聽回調,進行後續處理。那麼這個onObjectRetainedListeners
是在哪裏實現的呢?
在前面InternalAppWatcher.install
初始化時,InternalAppWatcher的初始化方法onAppWatcherInstalled()
中初始化了該監聽。
init {
val internalLeakCanary = try { val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary") leakCanaryListener.getDeclaredField("INSTANCE") .get(null) } catch (ignored: Throwable) { NoLeakCanary } @kotlin.Suppress("UNCHECKED_CAST") onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit } 複製代碼
咱們發現這裏經過反射獲取InternalLeakCanary.INSTANCE
單列對象,這個類位於另外一個包leakcanary-android-core
,因此用了反射。因爲InternalLeakCanary是一個函數對象,onAppWatcherInstalled()
對應的調用方法爲invoke()
來完成監聽註冊。
override fun invoke(application: Application) {
_application = application //檢查是否debug構建模式 checkRunningInDebuggableBuild() //註冊監聽 AppWatcher.objectWatcher.addOnObjectRetainedListener(this) //建立AndroidHeapDumper對象,用於虛擬機dump hprof產生內存快照文件 val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application)) //GcTrigger經過Runtime.getRuntime().gc()觸發GC val gcTrigger = GcTrigger.Default val configProvider = { LeakCanary.config } //建立子線程及對應looper val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME) handlerThread.start() val backgroundHandler = Handler(handlerThread.looper) //HeapDumpTrigger監聽註冊 heapDumpTrigger = HeapDumpTrigger( application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper, configProvider ) //註冊應用可見監聽 application.registerVisibilityListener { applicationVisible -> this.applicationVisible = applicationVisible heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible) } registerResumedActivityListener(application) addDynamicShortcut(application) disableDumpHeapInTests() } 複製代碼
當ObjectWatcher中moveToRetained
發現未回收對象後,經過回調onObjectRetained()
處理時,調用的就是這裏註冊的HeapDumpTrigger.onObjectRetained()
。處理調用鏈較長,直接看關鍵方法:
-->onObjectRetained
-->scheduleRetainedObjectCheck
-->checkRetainedObjects
private fun checkRetainedObjects(reason: String) {
...//代碼省略 //監聽器中未回收對象個數 var retainedReferenceCount = objectWatcher.retainedObjectCount //執行一次GC,再更新未回收對象個數 if (retainedReferenceCount > 0) { gcTrigger.runGc() retainedReferenceCount = objectWatcher.retainedObjectCount } //若對象個數未達到閾值5,返回 if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return ...//代碼省略,60s內只會執行一次 //核心方法,獲取內存快照 dumpHeap(retainedReferenceCount, retry = true) } 複製代碼
private fun dumpHeap( retainedReferenceCount: Int, retry: Boolean ) { ...//代碼省略 //獲取當前內存快照hprof文件 val heapDumpFile = heapDumper.dumpHeap() ...//省略hprof獲取失敗處理 lastDisplayedRetainedObjectCount = 0 lastHeapDumpUptimeMillis = SystemClock.uptimeMillis() //清理以前註冊的監聽 objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) //開啓hprof分析Service,解析hprof文件生成報告 HeapAnalyzerService.runAnalysis(application, heapDumpFile) } 複製代碼
在上面講到的內存泄漏回調處理中,生成了hprof文件,並開啓一個服務來解析該文件。調用鏈:HeapAnalyzerService.analyzeHeap
-->HeapAnalyzer.analyze
。該方法實現瞭解析hprof文件找到內存泄漏對象,並計算對象到GC roots的最短路徑,輸出報告。
fun analyze(.../*參數省略*/): HeapAnalysis {
...//代碼省略 return try { //PARSING_HEAP_DUMP解析狀態回調 listener.onAnalysisProgress(PARSING_HEAP_DUMP) //開始解析hprof文件 Hprof.open(heapDumpFile) .use { hprof -> //從文件中解析獲取對象關係圖結構graph //並獲取圖中的全部GC roots根節點 val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping) //建立FindLeakInput對象 //@4.查找內存泄漏對象 val helpers = FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors) helpers.analyzeGraph( metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime ) } } catch (exception: Throwable) { ...//省略解析異常處理 } } 複製代碼
@4.查找內存泄漏對象
private fun FindLeakInput.analyzeGraph(.../*參數省略*/): HeapAnalysisSuccess {
...//代碼省略 //經過過濾graph中的KeyedWeakReference類型對象來 //找到對應的內存泄漏對象 val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph) //@5.計算內存泄漏對象到GC roots的路徑 val (applicationLeaks, libraryLeaks) = findLeaks(leakingObjectIds) //輸出最終hprof分析結果 return HeapAnalysisSuccess(.../*參數省略*/) } 複製代碼
@5.計算內存泄漏對象到GC roots的路徑
private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
val pathFinder = PathFinder(graph, listener, referenceMatchers) //計算並獲取目標對象到GC roots的最短路徑 val pathFindingResults = pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize) SharkLog.d { "Found ${leakingObjectIds.size} retained objects" } //將這些內存泄漏對象的最短路徑合併成樹結構返回。 return buildLeakTraces(pathFindingResults) } 複製代碼
最終在可視化界面中將hprof分析結果HeapAnalysisSuccess
展現出來:
最後來總結下LeakCanary內存泄漏分析過程吧(Activity):
KeyedWeakReference
對象,並關聯
ReferenceQueue
KeyedWeakReference
類型過濾找到內存泄漏對象
除了這些外,LeakCanary中代碼風格一樣值得學習,包括巧用ContentProvider初始化,kolint類委託進行選擇性方法實現等。
參考文檔:
[1] Android 中的引用類型初探
[2] java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueue
[3] 深刻理解 Android 之 LeakCanary 源碼解析