leakcanery原理分析

LeakCanary是Android上用于檢查內(nèi)存泄漏的工具丈咐,LeakCanary大大減少因內(nèi)存泄漏導致的內(nèi)存溢出(OutOfMemoryError)崩潰凌彬。

從1.6.3開始小槐,LeakCanary就使用Kotlin重寫了一次辐马,這里的的源碼來自于版本2.5转砖,加入引用:

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
LeakCanary包結構
leakcanary-android
集成入口模塊,提供 LeakCanary 安裝哀卫,公開 API 等能力

leakcanary-android-process
和 leakcanary-android 一樣丽涩,區(qū)別是會在單獨的進程進行分析

leakcanary-android-core
核心模塊

leakcanary-object-watcher-android萄唇,leakcanary-object-watcher-android-androidx,leakcanary-watcher-android-support-fragments
對象實例觀察模塊屎勘,在 Activity侄柔,F(xiàn)ragment 等對象的生命周期中共啃,注冊對指定對象實例的觀察,有 Activity暂题,F(xiàn)ragment移剪,F(xiàn)ragment View,ViewModel 等

shark-android
提供特定于 Android 平臺的分析能力薪者。例如設備的信息纵苛,Android 版本,已知的內(nèi)存泄露問題等

shark
hprof 文件解析與分析的入口模塊

shark-graph
分析堆中對象的關系圖模塊

shark-hprof
解析 hprof 文件模塊

shark-log
日志模塊
初始化

LeakCanary 2.0不需要添加代碼便可以跟隨APP啟動啸胧,省去了1.6版本前需要install的代碼赶站。原理在于利用了ContentProvider的特性,ContentProvider.onCreate方法會先于Application.onCreate執(zhí)行纺念。

leakcanary庫中聲明的ContentProvider贝椿。
    //注冊ContentProvider @leakcanary-object-watcher-android/src/main/AndroidManifest.xml
<application>
    <provider
        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
        android:authorities="${applicationId}.leakcanary-installer"
        android:exported="false"/>
  </application>

//@AppWatcherInstaller.kt
internal class LeakCanaryProcess : AppWatcherInstaller() {
    override fun onCreate(): Boolean {
      super.onCreate()
      AppWatcher.config = AppWatcher.config.copy(enabled = false)
      return true
    }
  }

  override fun onCreate(): Boolean {
    //獲取application
    val application = context!!.applicationContext as Application
    //-->2.1 加載LeakCanary
    InternalAppWatcher.install(application)
    return true
  }
}

//2.1 加載LeakCanary @InternalAppWatcher.kt
fun install(application: Application) {
    ...
    //檢查當前線程是否有主線程
    checkMainThread()
    if (this::application.isInitialized) {
      //如果LeakCanary已經(jīng)加載過,直接放回
      return
    }
    InternalAppWatcher.application = application

    val configProvider = { AppWatcher.config }
    //-->2.1監(jiān)視Activity
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    //-->2.2監(jiān)視Fragment
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    //-->2.3調(diào)用上層模塊InternalLeakCanary.invoke
    onAppWatcherInstalled(application)
  }

監(jiān)聽器利用了Activity陷谱、fragment的生命周期回調(diào)烙博,在ActivityDestroyWatcher類中瑟蜈,獲取該銷毀的activity,添加了該activity的監(jiān)聽渣窜。

companion object {
  fun install(
    application: Application,
    objectWatcher: ObjectWatcher,
    configProvider: () -> Config
  ) {
    //-->2.1.1 創(chuàng)建Activity destroy監(jiān)聽回調(diào)
    val activityDestroyWatcher =
      ActivityDestroyWatcher(objectWatcher, configProvider)
    //-->2.1.2 同Application綁定
    application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
  }
}

//2.1.1 創(chuàng)建Activity destroy監(jiān)聽回調(diào)
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        //Activity destroy觸發(fā)存在對象檢查
        if (configProvider().watchActivities) {
          // -->2.1.2 objectWatcher監(jiān)視activity
          objectWatcher.watch(
              activity, "${activity::class.java.name} received Activity#onDestroy() callback"
          )
        }
      }
    }

lifecycleCallbacks監(jiān)聽Activity的onDestroy方法铺根,正常情況下activity在onDestroy后需要立即被回收,onActivityDestroyed方法最終會調(diào)用RefWatcher.watch方法:

  @Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    //根據(jù)activity創(chuàng)建對應的弱引用乔宿,并綁定ReferenceQueue
    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數(shù)組中
    watchedObjects[key] = reference
    //啟動延時5s任務
    checkRetainedExecutor.execute {
      //獲取GC無法回收的Activity
      moveToRetained(key)
    }
  }

監(jiān)測機制利用了Java的WeakReference和ReferenceQueue位迂,通過將Activity包裝到WeakReference中,被WeakReference包裝過的Activity對象如果被回收详瑞,該WeakReference引用會被放到ReferenceQueue中掂林,通過監(jiān)測ReferenceQueue里面的內(nèi)容就能檢查到Activity是否能夠被回收。

ReferenceQueue:

引用隊列坝橡,換言之就是存放引用的隊列泻帮,保存的是Reference對象。其作用在于Reference對象所引用的對象被GC回收時计寇,該Reference對象將會被加入引用隊列的隊尾锣杂。

  //獲取GC無法回收的Activity
  @Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      //保存當前時間作為泄漏時間
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
     //通知InternalLeakCanary發(fā)生內(nèi)存泄漏
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }


  private fun removeWeaklyReachableObjects() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        //在watchedObjects中刪除不發(fā)送內(nèi)存泄漏對象,剩下內(nèi)存泄漏對象
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
  //通知InternalLeakCanary發(fā)生內(nèi)存泄漏 @HeapDumpTrigger.kt
  override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      //通知heapDumpTrigger有內(nèi)存泄漏
      heapDumpTrigger.onObjectRetained()
    }
  }
  • LeakCanary檢測內(nèi)存泄漏的基本流程

1番宁、 首先通過removeWeaklyReachablereference來移除已經(jīng)被回收的Activity引用

2元莫、 通過gone(reference)判斷當前弱引用對應的Activity是否已經(jīng)被回收,如果已經(jīng)回收說明activity能夠被GC贝淤,直接返回即可柒竞。

3、 如果Activity沒有被回收播聪,調(diào)用GcTigger.runGc方法運行GC,GC完成后在運行第1步布隔,然后運行第2步判斷Activity是否被回收了离陶,如果這時候還沒有被回收,那就說明Activity可能已經(jīng)泄露衅檀。

4招刨、 如果Activity泄露了,就抓取內(nèi)存dump文件(Debug.dumpHprofData)

5哀军、 之后通過HeapAnalyzerService.runAnalysis進行分析內(nèi)存文件分析
接著通過HeapAnalyzer(checkForLeak—findLeakingReference---findLeakTrace)來進行內(nèi)存泄漏分析沉眶。

6、 最后通過DisplayLeakService進行內(nèi)存泄漏的展示杉适。

參考

LeakCanary原理解析
LeakCanary2源碼分析
關于LeakCanary2.0的四個問題

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谎倔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猿推,更是在濱河造成了極大的恐慌片习,老刑警劉巖捌肴,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異藕咏,居然都是意外死亡状知,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門孽查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饥悴,“玉大人,你說我怎么就攤上這事盲再∥魃瑁” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵洲胖,是天一觀的道長济榨。 經(jīng)常有香客問我,道長绿映,這世上最難降的妖魔是什么擒滑? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮叉弦,結果婚禮上丐一,老公的妹妹穿的比我還像新娘。我一直安慰自己淹冰,他們只是感情好库车,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著樱拴,像睡著了一般柠衍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晶乔,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天珍坊,我揣著相機與錄音,去河邊找鬼正罢。 笑死阵漏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的翻具。 我是一名探鬼主播履怯,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼裆泳!你這毒婦竟也來了叹洲?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤晾虑,失蹤者是張志新(化名)和其女友劉穎疹味,沒想到半個月后仅叫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡糙捺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年诫咱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洪灯。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡坎缭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出签钩,到底是詐尸還是另有隱情掏呼,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布铅檩,位于F島的核電站憎夷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昧旨。R本人自食惡果不足惜拾给,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兔沃。 院中可真熱鬧蒋得,春花似錦、人聲如沸乒疏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怕吴。三九已至窍侧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間转绷,已是汗流浹背疏之。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留暇咆,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓丙曙,卻偏偏與公主長得像爸业,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亏镰,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354