欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解LeakCanary分析內(nèi)存泄露如何實(shí)現(xiàn)

 更新時(shí)間:2022年12月06日 11:41:05   作者:流浪漢kylin  
這篇文章主要為大家介紹了詳解LeakCanary分析內(nèi)存泄露如何實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

平時(shí)我們都有用到LeakCanary來分析內(nèi)存泄露的情況,這里可以來看看LeakCanary是如何實(shí)現(xiàn)的,它的內(nèi)部又有哪些比較有意思的操作。

LeakCanary的使用

官方文檔:square.github.io/leakcanary/…

引用方式

dependencies {
    // debugImplementation because LeakCanary should only run in debug builds. 
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
}

可以看到LeakCanary的新版本中依賴非常簡單,甚至不需要你做什么就可以直接使用。

LeakCanary原理

LeakCanary的封裝主要是利用ContentProvider,LeakCanary檢測內(nèi)存泄漏主要是監(jiān)聽Activity和Fragment、view的生命周期,配合弱引用和ReferenceQueue。

源碼淺析

初始化

首先debugImplementation只是在Debug的包會(huì)依賴,在正式包不會(huì)把LeakCanary的內(nèi)容打進(jìn)包中。

LeakCanary的初始化是使用了ContentProvider,ContentProvider的onCreate會(huì)在Application的onCreate之前,它把ContentProvider寫在自己的AndroidMainifest中,打包時(shí)會(huì)進(jìn)行合并,所以這整個(gè)過程都不需要接入端做初始化操作。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher" >
    <uses-sdk android:minSdkVersion="14" />
    <application>
        <provider
            android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
            android:authorities="${applicationId}.leakcanary-installer"
            android:enabled="@bool/leak_canary_watcher_auto_install"
            android:exported="false" />
    </application>
</manifest>

這是它在AndroidManifest所定義的,打包的時(shí)候會(huì)合并所有的AndroidManifest

這就是它自動(dòng)初始化的操作,也比較明顯了,不用過多解釋。

使用

先看看它要監(jiān)測什么,因?yàn)長eakCanary 2.x的代碼都是kotlin寫的,所以這里得分析kotlin,如果不熟悉kt的朋友,我只能說盡量講慢一些,因?yàn)槲蚁肟磁f版本的能不能用java來分析,但是簡單看了下源碼上是有一定的差別,所以還是要分析2.x。

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

從這里看到他主要分析Activity、Fragment和Fragment的View、RootView、Service。

看Activity的監(jiān)聽ActivityWatcher

監(jiān)聽Activity調(diào)用Destroy時(shí)會(huì)調(diào)用reachabilityWatcher的expectWeaklyReachable方法。
這里可以看看舊版本的做法(正好以前有記錄)

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
      @Override public void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    };

舊版本是調(diào)用refWatcher的watch,雖然代碼不同,但是思想一樣,再看看舊版本的Fragment

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
    new FragmentManager.FragmentLifecycleCallbacks() {
      @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
        View view = fragment.getView();
        if (view != null) {
          refWatcher.watch(view);
        }
      }
      @Override
      public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
        refWatcher.watch(fragment);
      }
    };

這里監(jiān)聽了Fragment和Fragment的View,所以相比于新版本,舊版本只監(jiān)聽Activity、Fragment和Fragment的View

再回到新版本,分析完Activity的監(jiān)聽之后看看Fragment的

最終Destroy之后也是調(diào)用到reachabilityWatcher的expectWeaklyReachable。然后看看RootViewWatcher的操作

private val listener = OnRootViewAddedListener { rootView ->
  val trackDetached = when(rootView.windowType) {
    PHONE_WINDOW -> {
      when (rootView.phoneWindow?.callback?.wrappedCallback) {
        is Activity -> false
        is Dialog -> {
          ......
        }
        else -> true
      }
    }
    POPUP_WINDOW -> false
    TOOLTIP, TOAST, UNKNOWN -> true
  }
  if (trackDetached) {
    rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
      val watchDetachedView = Runnable {
        reachabilityWatcher.expectWeaklyReachable(
          rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
        )
      }
        ......
    })
  }
}

最終也是調(diào)用到reachabilityWatcher的expectWeaklyReachabl。最后再看看Service的。

這邊因?yàn)橹皇亲鰷\析,不是源碼詳細(xì)分析,所以我這邊就不去一個(gè)個(gè)分析是如何調(diào)用到銷毀的這個(gè)方法的,我們通過上面的方法得到一個(gè)結(jié)論,Activity、Fragment和Fragment的View、RootView、Service,他們幾個(gè),在銷毀時(shí)都會(huì)調(diào)用到reachabilityWatcher的expectWeaklyReachabl。所以這些地方就是檢測對象是否泄漏的入口。

然后我們來看看expectWeaklyReachable方法

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  // 先從queue中移除一次已回收對象
  removeWeaklyReachableObjects()
  // 生成隨機(jī)數(shù)當(dāng)成key
  val key = UUID.randomUUID().toString()
  val watchUptimeMillis = clock.uptimeMillis()
  // 創(chuàng)建弱引用關(guān)聯(lián)ReferenceQueue
  val reference =
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
  ......
  // 把reference和key 添加到一個(gè)Map中
  watchedObjects[key] = reference
  // 下一步
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

你們運(yùn)氣真好,我正好以前也有記錄舊版本的refWatcher的watch方法

public void watch(Object watchedReference, String referenceName) {
        ......
        // 生成隨機(jī)數(shù)當(dāng)成key
        String key = UUID.randomUUID().toString();
        // 把key 添加到一個(gè)Set中
        this.retainedKeys.add(key);
        // 創(chuàng)建弱引用關(guān)聯(lián)ReferenceQueue
        KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
        // 下一步
        this.ensureGoneAsync(watchStartNanoTime, reference);
}

通過對比發(fā)現(xiàn),模板的流程是一樣的,但是細(xì)節(jié)不一樣,以前是用Set,現(xiàn)在是用Map,這就是我覺得不能拿舊版本代碼來分析的原因。

文章寫到這里,突然想到一個(gè)很有意思的東西,你要是面試時(shí),面試官看過新版本的代碼,你看的是舊版本的代碼,結(jié)果如果問到一些比較深入的細(xì)節(jié),你答出來的和他所理解的不同,那就尷尬了,所以面試時(shí)得先說清楚你是看過舊版本的代碼

看到用一個(gè)弱引用生成一個(gè)key和對象綁定起來。然后調(diào)用ensureGoneAsync方法

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    this.watchExecutor.execute(new Retryable() {
        public Result run() {
            return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
        }
    });
}

execute里面會(huì)調(diào)用到waitForIdle方法。

我們再回到新版本的代碼中

checkRetainedExecutor.execute其實(shí)是會(huì)執(zhí)行到這里(kt里面的是寫得簡單,但是不熟的話可以先別管怎么執(zhí)行的,只要先知道反正執(zhí)行到這個(gè)地方就行)

這里是做了一個(gè)延時(shí)發(fā)送消息的操作,延時(shí)5秒,具體代碼在這里

寫到這里我感覺有點(diǎn)慌了,因?yàn)槿绻皇靕t的朋友可能真會(huì)看困,其實(shí)如果看不懂這個(gè)代碼的話沒關(guān)系,只要我圈出來的地方,我覺是大概能看懂的,然后流程我會(huì)說,我的意思是沒必要深入去看每一行是什么意思,我們的目的是找出大概的流程(用游戲的說法,我們是走主線任務(wù),不是要全收集)

延遲5秒后會(huì)調(diào)回到前面的moveToRetained(key)。那不好意思各位,我又要拿舊版本來對比了,因?yàn)榧?xì)節(jié)不同。

private void waitForIdle(final Retryable retryable, final int failedAttempts) {
  // 使用IdleHandler來實(shí)現(xiàn)在閑時(shí)才去執(zhí)行后面的流程
  Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override public boolean queueIdle() {
      postToBackgroundWithDelay(retryable, failedAttempts);
      return false;
    }
  });
}

使用IdleHandler來完成閑時(shí)觸發(fā),我不記得很早之前的版本是不是也用的IdleHandler,這里使用IdleHandler只能說有好有壞吧,好處是閑時(shí)觸發(fā)確實(shí)是一個(gè)很好的操作,不好的地方是如果一直有異步消息,就一直不會(huì)觸發(fā)后面的流程。

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
  long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
  long delayMillis = initialDelayMillis * exponentialBackoffFactor;
  // 根據(jù)上下文去計(jì)算,這里是5秒
  backgroundHandler.postDelayed(new Runnable() {
    @Override public void run() {
      Retryable.Result result = retryable.run();
      if (result == RETRY) {
        postWaitForIdle(retryable, failedAttempts + 1);
      }
    }
  }, delayMillis);
}

看到舊版本是先用IdelHanlder,在閑時(shí)觸發(fā)的情況下再去延時(shí)5秒,而新版本是直接延時(shí)5秒,不使用IdelHandler,我沒看過這塊具體的文檔描述,我猜是為了防止餓死,如果用IdelHanlder的話可能會(huì)出現(xiàn)一直不觸發(fā)的情況。

返回看新版本的moveToRetained

@Synchronized private fun moveToRetained(key: String) {
  // 從ReferenceQueue中拿出對象移除
  removeWeaklyReachableObjects()
  // 經(jīng)過上一步之后判斷Map中還有沒有這個(gè)key,有的話進(jìn)入下一步操作
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}
private fun removeWeaklyReachableObjects() {
  // 從ReferenceQueue中拿出對象,然后從Map中移除
  var ref: KeyedWeakReference?
  do {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

moveToRetained主要是從ReferenceQueue中找出弱引用對象,然后移除Map中相應(yīng)的弱引用對象。弱引用+ReferenceQueue的使用,應(yīng)該不用多說吧,如果弱引用持有的對象被回收,弱引用會(huì)添加到ReferenceQueue中。所以watchedObjects代表的是應(yīng)該將要被回收的對象,queue表示已經(jīng)被回收的對象,這步操作就是從queue中找出已經(jīng)回收的對象,然后從watchedObjects移除相應(yīng)的對象,剩下的的就是應(yīng)該被回收卻沒被回收的對象。如果對象被正常回收,那這整個(gè)流程就走完了,如果沒被回收,會(huì)執(zhí)行到onObjectRetained(),之后就是Dump操作了,之后的就是內(nèi)存分析、彈出通知那堆操作了,去分析內(nèi)存的泄漏這些,因?yàn)閮?nèi)容比較多,這篇先大概就先到這里。

總結(jié)

淺析,就是只做了簡單分析LeakCanary的整個(gè)工作過程和工作原理。

原理就是用弱引用和ReferenceQueue去判斷應(yīng)該被回收的對象是否已經(jīng)被回收。大致的工作流程是:監(jiān)聽Activity、Fragment和Fragment的View、RootView、Service對象的銷毀,然后將這些對象放入“應(yīng)該被回收”的容器中,然后5秒后通過弱引用和ReferenceQueue去判斷對象是否已被回收,如果被回收則從容器中刪除對應(yīng)的對象,否則進(jìn)行內(nèi)存分析。

至于是如何判斷不同對象的銷毀和如何分析內(nèi)存情況找出泄漏的引用鏈,這其中也是細(xì)節(jié)滿滿,但是我個(gè)人LeakCanary應(yīng)該是看過兩三次源碼了,從一開始手動(dòng)初始化,到舊版本java的實(shí)現(xiàn)方式,到現(xiàn)在用kt去實(shí)現(xiàn),能發(fā)現(xiàn)它的核心思想其實(shí)是一樣的,只不過在不斷的優(yōu)化一些細(xì)節(jié)和不斷的擴(kuò)展可以監(jiān)測的對象。

以上就是詳解LeakCanary分析內(nèi)存泄露如何實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于LeakCanary分析內(nèi)存泄露的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android 獲取隨機(jī)驗(yàn)證碼功能示例

    Android 獲取隨機(jī)驗(yàn)證碼功能示例

    這篇文章主要介紹了Android 獲取隨機(jī)驗(yàn)證碼功能示例,需要的朋友可以參考下
    2017-06-06
  • Android開發(fā)中如何去掉app標(biāo)題欄的實(shí)現(xiàn)

    Android開發(fā)中如何去掉app標(biāo)題欄的實(shí)現(xiàn)

    這篇文章主要介紹了Android開發(fā)中如何去掉app標(biāo)題欄的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Android搜索框通用版

    Android搜索框通用版

    這篇文章主要為大家詳細(xì)介紹了Android搜索框通用版的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-06-06
  • android 設(shè)置圓角圖片實(shí)現(xiàn)代碼

    android 設(shè)置圓角圖片實(shí)現(xiàn)代碼

    在android應(yīng)用開發(fā)中,可能是美化需要,圖片需要處理成圓角,本文將給出實(shí)現(xiàn)代碼,開發(fā)中的遇到此問題的朋友可以參考下
    2012-11-11
  • Android 使用Glide加載網(wǎng)絡(luò)圖片等比例縮放的實(shí)現(xiàn)方法

    Android 使用Glide加載網(wǎng)絡(luò)圖片等比例縮放的實(shí)現(xiàn)方法

    這篇文章主要介紹了Android 使用Glide加載網(wǎng)絡(luò)圖片等比例縮放的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2018-08-08
  • Android使用Handler實(shí)現(xiàn)倒計(jì)時(shí)功能

    Android使用Handler實(shí)現(xiàn)倒計(jì)時(shí)功能

    這篇文章主要為大家詳細(xì)介紹了Android使用Handler實(shí)現(xiàn)倒計(jì)時(shí)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • Flutter集成高德地圖并添加自定義Maker的實(shí)踐

    Flutter集成高德地圖并添加自定義Maker的實(shí)踐

    本文主要介紹了Flutter集成高德地圖并添加自定義Maker的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • Android?JetPack組件的支持庫Databinding詳解

    Android?JetPack組件的支持庫Databinding詳解

    DataBinding是Google發(fā)布的一個(gè)數(shù)據(jù)綁定框架,它能夠讓開發(fā)者減少重復(fù)性非常高的代碼,如findViewById這樣的操作。其核心優(yōu)勢是解決了數(shù)據(jù)分解映射到各個(gè)view的問題,在MVVM框架中,實(shí)現(xiàn)的View和Viewmode的雙向數(shù)據(jù)綁定
    2022-08-08
  • android實(shí)現(xiàn)注冊登錄程序

    android實(shí)現(xiàn)注冊登錄程序

    這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)注冊登錄程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Android RadioGroup多行顯示效果 解決單選問題

    Android RadioGroup多行顯示效果 解決單選問題

    這篇文章主要為大家詳細(xì)介紹了Android RadioGroup多行顯示效果,解決單選問題,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11

最新評論