LeakCanary使用和工作原理分析

LeakCanary是一個非常受歡迎的android內(nèi)存泄漏檢測工具捐腿,只需要在項(xiàng)目中引入即可

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'

然后 沒有什么 初始化驶拱,注冊 之類的任何操作坛增,就OK了翅雏。
很是奇怪撒穷,下面從 LeakCanary 如何啟動調(diào)用和工作原理做一下 簡單的總結(jié)分析妆绞;

LeakCanary如何啟動調(diào)用

既然LeakCanary,沒有任何初始化的代碼調(diào)用恋沃,LeakCanary 就有可能在AndroidManifest清單文件中 聲明了ContentProvider必搞,借助ContentProvider的特性來實(shí)現(xiàn)初始化;
先看一下 debug.apk中的 清單文件內(nèi)容:


image.png

果然如此囊咏,LeakCanary的aar中 已經(jīng)聲明了這個 AppWatcherInstaller 對象恕洲,所以就不需要在app中的AndroidManifest中 再次聲明了;

/**
 * Content providers are loaded before the application class is created. [AppWatcherInstaller] is
 * used to install [leakcanary.AppWatcher] on application start.
 */
internal sealed class AppWatcherInstaller : ContentProvider() {

  /**
   * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
   */
  internal class MainProcess : AppWatcherInstaller()

  /**
   * When using the `leakcanary-android-process` artifact instead of `leakcanary-android`,
   * [LeakCanaryProcess] automatically sets up the LeakCanary code
   */
  internal class LeakCanaryProcess : AppWatcherInstaller()

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
...

看一下 自定義的AppWatcherInstaller 內(nèi)容提供者的代碼梅割。
自定義的AppWatcherInstaller 對象霜第,會在Application調(diào)用onCreate方法之前,創(chuàng)建并調(diào)用其自身的onCraete方法;
而且注釋中 已經(jīng)給出了 這一解釋, Content providers are loaded before the application class is created. [AppWatcherInstaller] is used to install [leakcanary.AppWatcher] on application start
從而 調(diào)用

AppWatcher.manualInstall(application)

觸發(fā)了LeakCanary 的初始化方法;

LeakCanary的工作原理

LeakCanary的初始化

//AppWatcher
  fun manualInstall(application: Application) {
    InternalAppWatcher.install(application)
  }
#InternalAppWatcher
  fun install(application: Application) {
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    InternalAppWatcher.application = application
    if (isDebuggableBuild) {
      SharkLog.logger = DefaultCanaryLog()
    }

    val configProvider = { AppWatcher.config }
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
  }

LeakCanary的初始化 先調(diào)用了AppWatcher的manualInstall方法炮捧,然后又調(diào)用了 InternalAppWatcher的 install方法,關(guān)鍵代碼就在 install方法中惦银;
install方法進(jìn)行了如下操作:

  1. 檢測當(dāng)前是否在主線程

checkMainThread()

  1. 持有Application對象

InternalAppWatcher.application = application

3.分別調(diào)了ActivityDestroyWatcher和FragmentDestroyWatcher 的伴生對象的 install 方法

  • ActivityDestroyWatcher.install
//ActivityDestroyWatcher 
internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {

  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"
          )
        }
      }
    }

  companion object {
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

ActivityDestroyWatcher.install方法 為application 注冊了一個 Activity生命周期變化監(jiān)聽的對象 lifecycleCallbacks 咆课;在每個Activity對象 被銷毀 調(diào)用onDestroyed方法時,使用objectWatcher 對象來 檢測activity對象的回收

  • FragmentDestroyWatcher.install
    FragmentDestroyWatcher.install方法扯俱,和ActivityDestroyWatcher.install類型书蚪,也是為aplication對象 注冊了一個Activity生命周期變化的監(jiān)聽對象,但是主要是為了 監(jiān)聽Fragment對象的銷毀 ,在調(diào)用 onFragmentDestroyed(),onFragmentViewDestroyed()時迅栅,使用objectWatcher 對象來 檢測view對象和fragment對象的回收殊校。
//FragmentDestroyWatcher
fun install(
    application: Application,
    objectWatcher: ObjectWatcher,
    configProvider: () -> AppWatcher.Config
  ) {
    val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
          AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
      )
    }
    ...
    application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    })
  }
//AndroidOFragmentDestroyWatcher
internal class AndroidOFragmentDestroyWatcher(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : (Activity) -> Unit {
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
        objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      if (configProvider().watchFragments) {
        objectWatcher.watch(
            fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
        )
      }
    }
  }

  override fun invoke(activity: Activity) {
    val fragmentManager = activity.fragmentManager
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
  }
}

LeakCanary 檢測對象是否被回收

LeakCanary 檢測內(nèi)存泄漏的關(guān)鍵就是,在Activity或者Fragment銷毀的時候读存,觸發(fā)一次gc內(nèi)存回收为流,然后判斷Activity或者Fragment對象 是否依然存在,如果對象依然存在沒有被回收让簿,就說明 可能存在內(nèi)存泄漏敬察,積累過多可能引發(fā)OOM內(nèi)存溢出。
這樣就有一個 疑問尔当,如何判斷一個對象是否被gc回收呢莲祸?

借助弱引用的特性,只要jvm的垃圾回收器 掃描到 弱引用對象 ,弱引用對象 就會被回收釋放掉锐帜,但是如果 沒有被回收 只能說明 這個對象還被其他static變量引用 或者native method引用田盈;
偽代碼如下:

        Integer a=1;
        ReferenceQueue<Integer> referenceQueue = new ReferenceQueue<>();
        WeakReference<Integer> weakReference = new WeakReference<>(a, referenceQueue);
        //觸發(fā)gc,5秒后 檢測referenceQueue是否存在對象
        System.gc();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (referenceQueue.poll() != null) {
                    //a對象 已經(jīng)被回收
                }
            }
        },5000);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缴阎,一起剝皮案震驚了整個濱河市允瞧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌药蜻,老刑警劉巖瓷式,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異语泽,居然都是意外死亡贸典,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門踱卵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來廊驼,“玉大人,你說我怎么就攤上這事惋砂《士妫” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵西饵,是天一觀的道長酝掩。 經(jīng)常有香客問我,道長眷柔,這世上最難降的妖魔是什么期虾? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮驯嘱,結(jié)果婚禮上镶苞,老公的妹妹穿的比我還像新娘。我一直安慰自己鞠评,他們只是感情好茂蚓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剃幌,像睡著了一般聋涨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上负乡,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天牛郑,我揣著相機(jī)與錄音,去河邊找鬼敬鬓。 笑死淹朋,一個胖子當(dāng)著我的面吹牛笙各,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播础芍,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼杈抢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了仑性?” 一聲冷哼從身側(cè)響起惶楼,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诊杆,沒想到半個月后歼捐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晨汹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年豹储,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淘这。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡剥扣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铝穷,到底是詐尸還是另有隱情钠怯,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布曙聂,位于F島的核電站晦炊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宁脊。R本人自食惡果不足惜断国,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朦佩。 院中可真熱鬧并思,春花似錦庐氮、人聲如沸语稠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仙畦。三九已至,卻和暖如春音婶,著一層夾襖步出監(jiān)牢的瞬間慨畸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工衣式, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寸士,地道東北人檐什。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像弱卡,于是被迫代替她去往敵國和親乃正。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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