LeakCanary 2.0源碼分析與總結(jié)

本文基于LeakCanary 2.0源碼分析
LeakCanary - 官方地址
LeakCanary - GitHub代碼地址

LeakCanary 是什么

概念:LeakCanary是針對Android應(yīng)用的一個(gè)內(nèi)存泄漏監(jiān)控三方庫
能力:Activity、Fragment以及自主監(jiān)控的任何對象
出品:Square

LeakCanary 作用

基于對Android Framework層的認(rèn)知念祭,LeakCanary提供更精準(zhǔn)的泄漏原因分析能力霉咨,從而幫助開發(fā)者快速減少OOM Crash問題

LeakCanary 工作原理

預(yù)備知識

什么是內(nèi)存泄漏

在Java運(yùn)行環(huán)境下黄锤,內(nèi)存泄漏是指某個(gè)程序錯(cuò)誤導(dǎo)致應(yīng)用長時(shí)間一直保留某個(gè)不在需要的對象,以至于它不能被回收厘惦,而它是會(huì)占用內(nèi)存的疟丙,這就意味著內(nèi)存泄漏了嵌巷。持續(xù)累加奠滑,最終有可能導(dǎo)致發(fā)生內(nèi)存溢出問題丹皱。
例如一個(gè)Activity執(zhí)行完onDestroy方法后,它仍然被一個(gè)static變量強(qiáng)引用宋税,從而阻止了Activity被GC回收摊崭,導(dǎo)致Activity發(fā)生內(nèi)存泄漏

怎么判斷一個(gè)對象是否泄漏

從GC Roots出發(fā)進(jìn)行遍歷,強(qiáng)引用可到達(dá)對象杰赛,都是存活對象呢簸,不可達(dá)對象則為即將被回收的對象。如果那些存活對象本應(yīng)該是要被回收的乏屯,那么這個(gè)對象就是發(fā)生了內(nèi)存泄漏(見下圖根时,引用一張圖說明)
實(shí)際過程通常作法是針對核心對象Activity、Fragment進(jìn)行監(jiān)控分析

如何開始監(jiān)控

LeakCanary通過實(shí)現(xiàn)四大組件中的ContentProvider瓶珊,所以可以在App啟動(dòng)的時(shí)候執(zhí)行到LeakCanary的AppWatcherInstaller.onCreate方法啸箫,從而完成0侵入實(shí)現(xiàn)注冊監(jiān)控流程

監(jiān)控什么對象以及如何監(jiān)控到目標(biāo)對象

Activity: 通過注冊 Application.ActivityLifecycleCallbacks實(shí)現(xiàn)onActivityDestroyed執(zhí)行回調(diào),以監(jiān)控需要被回收的Activity是否被回收
Fragment: 通過注冊 FragmentManager.FragmentLifecycleCallbacks實(shí)現(xiàn)onFragmentViewDestroyed和onFragmentDestroyed回調(diào)伞芹,以監(jiān)控需要被回收的Fragment或者View是否被回收

如何確認(rèn)目標(biāo)對象泄漏

  1. 執(zhí)行回收:目標(biāo)對象如果在一個(gè)緩沖時(shí)間(5s)仍未被回收,我們通過手動(dòng)執(zhí)行GC蝉娜,然后在確認(rèn)其是否真的不能被回收
  2. 確認(rèn)是否回收:JVM中唱较,如果創(chuàng)建一個(gè)含有ReferenceQueue的WeakReference的A對象,這個(gè)WeakReference對應(yīng)的A對象如果被回收了召川,則A會(huì)被自動(dòng)加入到ReferenceQueue南缓,所以我們可以通過維護(hù)一個(gè)ReferenceQueue,通過創(chuàng)建目標(biāo)對象的含有ReferenceQueue的WeakReference荧呐,從而監(jiān)聽到目標(biāo)對象是否被回收

更多參考:Reference和ReferenceQueue深入解讀

如何分析目標(biāo)對象泄漏的原因

泄漏原因即尋找泄漏路徑

  1. 通過Debug.dumpHprofData汉形,dump一份hprof數(shù)據(jù)
  2. 讀取hprof數(shù)據(jù),整理出一份GcRoots對象索引
  3. 排序GcRoots對象倍阐,并構(gòu)造一份ReferencePathNode樹
  4. BFS遍歷概疆,找到泄漏對象的最短路徑節(jié)點(diǎn)
  5. 根據(jù)節(jié)點(diǎn),生成泄漏路徑

如何呈現(xiàn)目標(biāo)對象泄漏的原因

  1. 生成一個(gè)HeapDumpScreen峰搪,呈現(xiàn)泄漏信息
  2. 發(fā)出通知

LeakCanary 2.0與1.x版本對比

內(nèi)容 2.0 1.0
語言 kotlin java
使用 僅需引入庫岔冀,自動(dòng)注冊監(jiān)控 除引入庫,還需要手動(dòng)執(zhí)行install
內(nèi)存分析 shark概耻,基于Okio的自實(shí)現(xiàn)的輕巧內(nèi)存分析庫 haha三方庫
其它 fragment使套,支持 androidx

LeakCanary 源碼分析

主要包的結(jié)構(gòu)介紹

主要工作的時(shí)序圖

相關(guān)源碼

注冊流程

AppWatcherInstaller.onCreate

// 繼承 ContentProvider
internal sealed class AppWatcherInstaller : ContentProvider() {
    // App啟動(dòng) 執(zhí)行 onCreate 
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    // 注冊啟動(dòng)(0 侵入)
    InternalAppWatcher.install(application)
    return true
  }

}

InternalAppWatcher.install

fun install(application: Application) {
    InternalAppWatcher.application = application
    
    val configProvider = { AppWatcher.config }
    // Activity destroy方法監(jiān)控注冊
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    // Fragment destroy方法監(jiān)控注冊
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
}

ActivityDestroyWatcher.install

internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {
    // 構(gòu)造 Application.ActivityLifecycleCallbacks
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
          objectWatcher.watch(activity, "Activity received Activity#onDestroy() callback")
        }
      }
    }

  companion object {
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
      // 注冊 生命周期監(jiān)聽回調(diào)
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

InternalLeakCanary.invoke

  // 初始化相關(guān)類
override fun invoke(application: Application) {
    this.application = application
  
    // 添加保留對象監(jiān)聽
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
    // Android heap dumper類
    val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
    // GC觸發(fā)器
    val gcTrigger = GcTrigger.Default
    
    val configProvider = { LeakCanary.config }
    // 相關(guān)線程 handler
    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)
    // Heap Dump 觸發(fā)器
    heapDumpTrigger = HeapDumpTrigger(
       application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
       configProvider
    )
    ...
}
監(jiān)控流程

以Activity.onDestory為例
ActivityDestroyWatcher.lifecycleCallbacks

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
            // 開始監(jiān)控 銷毀的activity
          objectWatcher.watch(activity, "Activity received Activity#onDestroy() callback")
        }
      }
    }```

ObjectWatcher.watch

  @Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    // 移除已經(jīng)回收的監(jiān)聽對象
    removeWeaklyReachableObjects()
    // 隨機(jī)key
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 構(gòu)造KeyedWeakReference 用來監(jiān)聽目標(biāo)對象
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    // 存儲 key + reference
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
        // 執(zhí)行 沒有回收流程
      moveToRetained(key)
    }
  }

ObjectWatcher.moveToRetained

  @Synchronized private fun moveToRetained(key: String) {
    // 再次移除被回收的對象
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    // 如果沒有被回收 則開始執(zhí)行對象未被回收流程
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

HeapDumpTrigger

  override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }
  
  fun onObjectRetained() {
    scheduleRetainedObjectCheck("found new object retained")
  }

  private fun scheduleRetainedObjectCheck(reason: String) {
    checkScheduled = true
    backgroundHandler.post {
      checkScheduled = false
      checkRetainedObjects(reason)
    }
  }

private fun checkRetainedObjects(reason: String) {
    val config = configProvider()
    // A tick will be rescheduled when this is turned back on.
    if (!config.dumpHeap) {
      SharkLog.d { "No checking for retained object: LeakCanary.Config.dumpHeap is false" }
      return
    }
    SharkLog.d { "Checking retained object because $reason" }

    var retainedReferenceCount = objectWatcher.retainedObjectCount
    // 如果還有未被回收的目標(biāo)對象罐呼,則出發(fā)GC,
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
    
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    // 觸發(fā)GC后侦高,對象仍未被回收嫉柴,開始dump
    dumpHeap(retainedReferenceCount, retry = true)
  }

HeapDumpTrigger.dumpHeap

  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean
  ) {
    // 儲存Android 的資源id 及其對應(yīng)的name 
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    // dumpHeap 到file
    val heapDumpFile = heapDumper.dumpHeap()

    lastDisplayedRetainedObjectCount = 0
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
    // 啟動(dòng)service 開始dump分析
    HeapAnalyzerService.runAnalysis(application, heapDumpFile)
  }
  

AndroidHeapDumper.dumpHeap

 override fun dumpHeap(): File? {
    val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
    ...
    // 調(diào)用Debug的dumpHprofData 到目標(biāo)文件
    return Debug.dumpHprofData(heapDumpFile.absolutePath)
             heapDumpFile
  }

分析流程

HeapAnalyzerService.onHandleIntentInForeground

  override fun onHandleIntentInForeground(intent: Intent?) {
    // 獲取目標(biāo) heap dump file
     val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
    // 構(gòu)造 heap分析器
    val heapAnalyzer = HeapAnalyzer(this)
    // ...
    // 執(zhí)行分析流程
    val heapAnalysis =
      heapAnalyzer.analyze(
          heapDumpFile,
          config.referenceMatchers,
          config.computeRetainedHeapSize,
          config.objectInspectors,
          if (config.useExperimentalLeakFinders) config.objectInspectors else listOf(
              ObjectInspectors.KEYED_WEAK_REFERENCE
          ),
          config.metatadaExtractor,
          proguardMappingReader?.readProguardMapping()
      )

    // 回調(diào)分析完成
    config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
  }

HeapAnalyzer.analyze

fun analyze(
    heapDumpFile: File,
    referenceMatchers: List<ReferenceMatcher> = emptyList(),
    computeRetainedHeapSize: Boolean = false,
    objectInspectors: List<ObjectInspector> = emptyList(),
    leakFinders: List<ObjectInspector> = objectInspectors,
    metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
    proguardMapping: ProguardMapping? = null
  ): HeapAnalysis {
    val analysisStartNanoTime = System.nanoTime()

    try {
      listener.onAnalysisProgress(PARSING_HEAP_DUMP)
      // 讀取 文件,然后執(zhí)行分析
      Hprof.open(heapDumpFile)
          .use { hprof ->
            // Hprof -> graph 轉(zhuǎn)換過程 目標(biāo)獲取GcRoots index
            val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)

            listener.onAnalysisProgress(EXTRACTING_METADATA)
            // 獲取Android相關(guān) metadata (如sdk版本奉呛、收集廠商等信息)
            val metadata = metadataExtractor.extractMetadata(graph)
            
            val findLeakInput = FindLeakInput(
                graph, leakFinders, referenceMatchers, computeRetainedHeapSize, objectInspectors
            )
            // 找泄漏最短路徑
            val (applicationLeaks, libraryLeaks) = findLeakInput.findLeaks()
            listener.onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
            // 返回分析成功結(jié)果
            return HeapAnalysisSuccess(
                heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime), metadata,
                applicationLeaks, libraryLeaks
            )
          }

    }

HeapAnalyzer.findLeaks

  private fun FindLeakInput.findLeaks(): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
    // 找未被回收對象的 objectId
    val leakingInstanceObjectIds = findRetainedObjects()
    // 構(gòu)造pathFinder對象
    val pathFinder = PathFinder(graph, listener, referenceMatchers)
    val pathFindingResults =
    // ?? 找泄漏對象到GcRoots的最短路徑
    pathFinder.findPathsFromGcRoots(leakingInstanceObjectIds, computeRetainedHeapSize)

   // 返回 泄漏路徑
    return buildLeakTraces(pathFindingResults)
  }

PathFinder.findPatchsFromGcRoots

  fun findPathsFromGcRoots(
    leakingObjectIds: Set<Long>,
    computeRetainedHeapSize: Boolean
  ): PathFindingResults {
    listener.onAnalysisProgress(FINDING_PATHS_TO_RETAINED_OBJECTS)

    val sizeOfObjectInstances = determineSizeOfObjectInstances(graph)

    val state = State(leakingObjectIds, sizeOfObjectInstances, computeRetainedHeapSize)
    // 執(zhí)行 state计螺。findPathsFromGcRoots
    return state.findPathsFromGcRoots()
  }
  
  private fun State.findPathsFromGcRoots(): PathFindingResults {
    // GcRoots 生成節(jié)點(diǎn)隊(duì)列樹
    enqueueGcRoots()

    val shortestPathsToLeakingObjects = mutableListOf<ReferencePathNode>()
    visitingQueue@ while (queuesNotEmpty) {
      val node = poll() // 循環(huán)取node

        // 泄漏的節(jié)點(diǎn),則添加到shortestPathsToLeakingObjects 直到侧馅,全部找完 
      if (node.objectId in leakingObjectIds) {
        shortestPathsToLeakingObjects.add(node)
        // Found all refs, stop searching (unless computing retained size)
        if (shortestPathsToLeakingObjects.size == leakingObjectIds.size) {
          if (computeRetainedHeapSize) {
            listener.onAnalysisProgress(FINDING_DOMINATORS)
          } else {
            break@visitingQueue
          }
        }
      }
    }
    return PathFindingResults(shortestPathsToLeakingObjects, dominatedObjectIds)
  }
呈現(xiàn)流程

DefaultOnHeapAnalyzedListener.onHeapAnalyzed

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    // 寫入 db
    val (id, groupProjections) = LeaksDbHelper(application)
        .writableDatabase.use { db ->
      val id = HeapAnalysisTable.insert(db, heapAnalysis)
      id to LeakTable.retrieveHeapDumpLeaks(db, id)
    }

    // 生成 泄漏信息到屏幕展示
    val (contentTitle, screenToShow) = when (heapAnalysis) {
      is HeapAnalysisFailure -> application.getString(
          R.string.leak_canary_analysis_failed
      ) to HeapAnalysisFailureScreen(id)
      is HeapAnalysisSuccess -> {
        var leakCount = 0
        var newLeakCount = 0
        var knownLeakCount = 0
        var libraryLeakCount = 0

        for ((_, projection) in groupProjections) {
          leakCount += projection.leakCount
          when {
            projection.isLibraryLeak -> libraryLeakCount += projection.leakCount
            projection.isNew -> newLeakCount += projection.leakCount
            else -> knownLeakCount += projection.leakCount
          }
        }

        application.getString(
            R.string.leak_canary_analysis_success_notification, leakCount, newLeakCount,
            knownLeakCount, libraryLeakCount
        ) to HeapDumpScreen(id)
      }
    }

    val pendingIntent = LeakActivity.createPendingIntent(
        application, arrayListOf(HeapDumpsScreen(), screenToShow)
    )

    val contentText = application.getString(R.string.leak_canary_notification_message)
    // 構(gòu)建通知
    Notifications.showNotification(
        application, contentTitle, contentText, pendingIntent,
        R.id.leak_canary_notification_analysis_result,
        LEAKCANARY_MAX
    )
  }


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末危尿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子馁痴,更是在濱河造成了極大的恐慌谊娇,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罗晕,死亡現(xiàn)場離奇詭異济欢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)小渊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門法褥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酬屉,你說我怎么就攤上這事半等。” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脉漏。 經(jīng)常有香客問我,道長切距,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任惨远,我火速辦了婚禮谜悟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘北秽。我一直安慰自己葡幸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布羡儿。 她就那樣靜靜地躺著礼患,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缅叠,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天悄泥,我揣著相機(jī)與錄音,去河邊找鬼肤粱。 笑死弹囚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的领曼。 我是一名探鬼主播鸥鹉,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼庶骄!你這毒婦竟也來了毁渗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤单刁,失蹤者是張志新(化名)和其女友劉穎灸异,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羔飞,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肺樟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逻淌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片么伯。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卡儒,靈堂內(nèi)的尸體忽然破棺而出田柔,到底是詐尸還是另有隱情,我是刑警寧澤骨望,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布凯楔,位于F島的核電站,受9級特大地震影響锦募,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邻遏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一糠亩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧准验,春花似錦赎线、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春滞项,著一層夾襖步出監(jiān)牢的瞬間狭归,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工文判, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留过椎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓戏仓,卻偏偏與公主長得像疚宇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子赏殃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容