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

Android 進(jìn)階實現(xiàn)性能優(yōu)化之OOM與Leakcanary詳解原理

 更新時間:2021年11月02日 09:15:59   作者:臨木小屋  
LeakCanary 是大名鼎鼎的 square 公司開源的內(nèi)存泄漏檢測工具。目前上大部分App在開發(fā)測試階段都會接入此工具用于檢測潛在的內(nèi)存泄漏問題,做的好一點的可能會搭建一個服務(wù)器用于保存各個設(shè)備上的內(nèi)存泄漏問題再集中處理

本文主要探討以下幾個問題:

  • Android內(nèi)存泄漏常見場景以及解決方案
  • Leakcanary 使用及原理

Android內(nèi)存泄漏常見場景以及解決方案

資源性對象未關(guān)閉

對于資源性對象不再使用時,應(yīng)該立即調(diào)用它的close()函數(shù),將其關(guān)閉,然后再置為null。例如Bitmap等資源未關(guān)閉會造成內(nèi)存泄漏,此時我們應(yīng)該在Activity銷毀時及時關(guān)閉。

注冊對象未注銷

例如BraodcastReceiver、EventBus未注銷造成的內(nèi)存泄漏,我們應(yīng)該在Activity銷毀時及時注銷。

類的靜態(tài)變量持有大數(shù)據(jù)

對象盡量避免使用靜態(tài)變量存儲數(shù)據(jù),特別是大數(shù)據(jù)對象,建議使用數(shù)據(jù)庫存儲。

單例造成的內(nèi)存泄漏

優(yōu)先使用Application的Context,如需使用Activity的Context,可以在傳入Context時使用弱引用進(jìn)行封裝,然后,在使用到的地方從弱引用中獲取Context,如果獲取不到,則直接return即可。

非靜態(tài)內(nèi)部類的靜態(tài)實例

該實例的生命周期和應(yīng)用一樣長,這就導(dǎo)致該靜態(tài)實例一直持有該Activity的引用,Activity的內(nèi)存資源不能正常回收。此時,我們可以將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例,如果需要使用Context,盡量使用Application Context,如果需要使用Activity Context,就記得用完后置空讓GC可以回收,否則還是會內(nèi)存泄漏。

Handler臨時性內(nèi)存泄漏

Message發(fā)出之后存儲在MessageQueue中,在Message中存在一個target,它是Handler的一個引用,Message在Queue中存在的時間過長,就會導(dǎo)致Handler無法被回收。如果Handler是非靜態(tài)的,則會導(dǎo)致Activity或者Service不會被回收。并且消息隊列是在一個Looper線程中不斷地輪詢處理消息,當(dāng)這個Activity退出時,消息隊列中還有未處理的消息或者正在處理的消息,并且消息隊列中的Message持有Handler實例的引用,Handler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收,引發(fā)內(nèi)存泄漏。解決方案如下所示:
1. 使用一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象(一般是Activity)使用弱引用,這樣在回收時,也可以回收Handler持有的對象。
2. 在Activity的Destroy或者Stop時,應(yīng)該移除消息隊列中的消息,避免Looper線程的消息隊列中有待處理的消息需要處理。需要注意的是,AsyncTask內(nèi)部也是Handler機(jī)制,同樣存在內(nèi)存泄漏風(fēng)險,但其一般是臨時性的。對于類似AsyncTask或是線程造成的內(nèi)存泄漏,我們也可以將AsyncTask和Runnable類獨立出來或者使用靜態(tài)內(nèi)部類。

容器中的對象沒清理造成的內(nèi)存泄漏

在退出程序之前,將集合里的東西clear,然后置為null,再退出程序

WebView

WebView都存在內(nèi)存泄漏的問題,在應(yīng)用中只要使用一次WebView,內(nèi)存就不會被釋放掉。我們可以為WebView開啟一個獨立的進(jìn)程,使用AIDL與應(yīng)用的主進(jìn)程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時機(jī)進(jìn)行銷毀,達(dá)到正常釋放內(nèi)存的目的。

使用ListView時造成的內(nèi)存泄漏

在構(gòu)造Adapter時,使用緩存的convertView。

Leakcanary

leakcanary 導(dǎo)入

//  leakcanary 添加支持庫即可,只在debug下使用
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'

leakcanary 是如何安裝的

leakcanary 不需要初始化,用的是 ContentProvider!

ContentProvider.onCreate 方法比 Application.onCreate 更早執(zhí)行。LeakCanary 源碼的 Manifest.xml 里有聲明ContentProvider,apk打包流程中會把所有的Manifest合并到app 的 Manifest 里,即APP就有了ContentProvider。

//	package="com.squareup.leakcanary.leaksentry"
  <application>
    <provider
        android:name="leakcanary.internal.LeakSentryInstaller"
        android:authorities="${applicationId}.leak-sentry-installer"
        android:exported="false"/>
  </application>

下面是初始化的代碼

internal class LeakSentryInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    CanaryLog.logger = DefaultCanaryLog()
    val application = context!!.applicationContext as Application
     // 進(jìn)行初始化工作,核心
    InternalLeakSentry.install(application)
    return true
  }

監(jiān)聽實現(xiàn)

  fun install(application: Application) {
    CanaryLog.d("Installing LeakSentry")
    // 只能在主線程調(diào)用,否則會拋出異常
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    InternalLeakSentry.application = application

    val configProvider = { LeakSentry.config }
    // 監(jiān)聽 Activity.onDestroy()
    ActivityDestroyWatcher.install(
        application, refWatcher, configProvider
    )
    // 監(jiān)聽 Fragment.onDestroy()
    FragmentDestroyWatcher.install(
        application, refWatcher, configProvider
    )
    // Sentry 哨兵
    listener.onLeakSentryInstalled(application)
  }

leakcanary 如何監(jiān)聽Activity、Fragment銷毀

在了解監(jiān)聽過程前有必要了解下 ActivityLifecycleCallbacks 與 FragmentLifeCycleCallbacks

// ActivityLifecycleCallbacks 接口
public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity var1, Bundle var2);

    void onActivityStarted(Activity var1);

    void onActivityResumed(Activity var1);

    void onActivityPaused(Activity var1);

    void onActivityStopped(Activity var1);

    void onActivitySaveInstanceState(Activity var1, Bundle var2);

    void onActivityDestroyed(Activity var1);
}
// FragmentLifecycleCallbacks 接口
public abstract static class FragmentLifecycleCallbacks {

    public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}

    public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {}

    public void onFragmentDestroyed(FragmentManager fm, Fragment f) {}

    // 省略其他的生命周期 ...
  }

Application 類提供了 registerActivityLifecycleCallbacks 和 unregisterActivityLifecycleCallbacks 方法用于注冊和反注冊 Activity 的生命周期監(jiān)聽類,這樣我們就能在 Application 中對所有的 Activity 生命周期回調(diào)中做一些統(tǒng)一處理。同理,F(xiàn)ragmentManager 類提供了 registerFragmentLifecycleCallbacks 和 unregisterFragmentLifecycleCallbacks 方法用戶注冊和反注冊 Fragment 的生命周期監(jiān)聽類,這樣我們對每一個 Activity 進(jìn)行注冊,就能獲取所有的 Fragment 生命周期回調(diào)。

下面是 ActivityDestroyWatcher 的實現(xiàn),refWatcher 監(jiān)聽 activity 的 onActivityDestroyed

internal class ActivityDestroyWatcher private constructor(
  private val refWatcher: RefWatcher,
  private val configProvider: () -> Config
) {

  private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() {
    override fun onActivityDestroyed(activity: Activity) {
      if (configProvider().watchActivities) {
        // 監(jiān)聽到 onDestroy() 之后,通過 refWatcher 監(jiān)測 Activity
        refWatcher.watch(activity)
      }
    }
  }

  companion object {
    fun install(
      application: Application,
      refWatcher: RefWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(refWatcher, configProvider)
      // 注冊 Activity 生命周期監(jiān)聽
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

如此一來Activity、Fragment在調(diào)用onDestroy時我們都能知道。講道理,如果在調(diào)用onDestroy時被GC是正常的,如果沒有被回收則是發(fā)生了內(nèi)存泄漏,這是我們要處理的。那 refWatcher.watch(activity) 監(jiān)聽到銷毀后怎么處理?

RefWatcher 核心原理

在讀這塊代碼前舉個栗子比較好理解:比如我們?nèi)タ萍贾行拿嬖?/p>

  • 進(jìn)去的時候會登記個人信息在觀察列表,并標(biāo)明停留時間30分鐘
  • 30分鐘過后查看是否有登出
  • 如果未登出將信息由觀察列表轉(zhuǎn)移至懷疑列表
  • 懷疑列表名單超過5個時,找公安人員確定是否是恐怖分子
  • 確定是恐怖分子,警察抓人

RefWatcher 的實現(xiàn)原理跟上面的栗子神似:

  • Activity調(diào)用onDestroy后,以UUID生成key,被KeyedWeakReference包裝,并與ReferenceQueue關(guān)聯(lián),并把<key,KeyedWeakReference>存入 watchedReferences 中(watchedReferences 對應(yīng)觀察隊列)
  • 等待5s時間
  • 調(diào)用 moveToRetained 方法,先判斷是否已經(jīng)釋放,如果未釋放由 watchedReferences (觀察隊列) 轉(zhuǎn)入 retainedReferences(懷疑隊列)
  • 當(dāng) retainedReferences 隊列的長度大于5時,先調(diào)用一次GC,用HAHA這個開源庫去分析dump之后的heap內(nèi)存
  • 確定內(nèi)存泄漏對象

咱們先看下 refWatcher.watch(activity) 的實現(xiàn)

  @Synchronized fun watch(
    watchedReference: Any,
    referenceName: String
  ) {
    if (!isEnabled()) {
      return
    }
    // 移除隊列中將要被 GC 的引用
    removeWeaklyReachableReferences() 
    val key = UUID.randomUUID().toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 構(gòu)建當(dāng)前引用的弱引用對象,并關(guān)聯(lián)引用隊列 queue
    val reference = KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)
    if (referenceName != "") {
      CanaryLog.d(
          "Watching instance of %s named %s with key %s", reference.className,
          referenceName, key
      )
    } else {
      CanaryLog.d(
          "Watching instance of %s with key %s", reference.className, key
      )
    }
    // 將引用存入 watchedReferences
    watchedReferences[key] = reference 
    checkRetainedExecutor.execute {
      // 如果當(dāng)前引用未被移除,仍在 watchedReferences  隊列中,
      // 說明仍未被 GC,移入 retainedReferences 隊列中,暫時標(biāo)記為泄露
      moveToRetained(key) 
    }
  }

分析上面這段代碼都做了什么:

  • 移除隊列中將要被 GC 的引用,這里的隊列包括 watchedReferences 和 retainedReferences
  • 使用UUID生成唯一key,構(gòu)建 WeakReference 包裝 activity 并與 ReferenceQueue 關(guān)聯(lián)
  • 將 reference 放入觀察隊列 watchedReferences 中
  • 線程池調(diào)用 moveToRetained 函數(shù),此函數(shù)先走一遍gc,依舊沒回收的對象會進(jìn)入 retainedReferences 懷疑隊列,當(dāng)隊列大于5時調(diào)用HAHA庫走可達(dá)性分析確定是否是內(nèi)存泄漏

下面是細(xì)節(jié)分析 —》removeWeaklyReachableReferences() 邏輯

  private fun removeWeaklyReachableReferences() {
    // 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.
    // 弱引用一旦變得弱可達(dá),就會立即入隊。這將在 finalization 或者 GC 之前發(fā)生。
    var ref: KeyedWeakReference?
    do {
      // 隊列 queue 中的對象都是會被 GC 的
      ref = queue.poll() as KeyedWeakReference? 
      //說明被釋放了
      if (ref != null) {
        val removedRef = watchedReferences.remove(ref.key)//獲取被釋放的引用的key
        if (removedRef == null) {
          retainedReferences.remove(ref.key)
        }
        // 移除 watchedReferences 隊列中的會被 GC 的 ref 對象,剩下的就是可能泄露的對象
      }
    } while (ref != null)
  }

removeWeaklyReachableReferences 函數(shù)會根據(jù) ReferenceQueue 出來的 KeyedWeakReference 的 key 移除 watchedReferences(觀察隊列)和 retainedReferences(懷疑隊列)中的引用,即把已經(jīng)釋放的移出,剩下的是內(nèi)存泄漏的

moveToRetained(key) 邏輯實現(xiàn)

  @Synchronized private fun moveToRetained(key: String) {
    // 再次調(diào)用,防止遺漏
    removeWeaklyReachableReferences() 
    val retainedRef = watchedReferences.remove(key)
    //說明可能存在內(nèi)存泄漏
    if (retainedRef != null) {
      retainedReferences[key] = retainedRef
      onReferenceRetained()
    }
  }

此函數(shù)的作用:

  • 走一遍 removeWeaklyReachableReferences 方法,將已經(jīng)回收的清除
  • 將 watchedReferences(觀察隊列)中未被回收的引用移到 retainedReferences(懷疑隊列)中
  • onReferenceRetained() 則是在工作線程中檢測內(nèi)存泄漏,最后會調(diào)用 checkRetainedInstances 函數(shù)

下面是 checkRetainedInstances 的具體實現(xiàn)

  private fun checkRetainedInstances(reason: String) {
    CanaryLog.d("Checking retained instances because %s", reason)
    val config = configProvider()
    // A tick will be rescheduled when this is turned back on.
    if (!config.dumpHeap) {
      return
    }

    var retainedKeys = refWatcher.retainedKeys

    // 當(dāng)前泄露實例個數(shù)小于 5 個,不進(jìn)行 heap dump
    if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return

    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      showRetainedCountWithDebuggerAttached(retainedKeys.size)
      scheduleRetainedInstanceCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
      CanaryLog.d(
          "Not checking for leaks while the debugger is attached, will retry in %d ms",
          WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

    // 可能存在被觀察的引用將要變得弱可達(dá),但是還未入隊引用隊列。
    // 這時候應(yīng)該主動調(diào)用一次 GC,可能可以避免一次 heap dump
    gcTrigger.runGc()

    retainedKeys = refWatcher.retainedKeys

    if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return

    HeapDumpMemoryStore.setRetainedKeysForHeapDump(retainedKeys)

    CanaryLog.d("Found %d retained references, dumping the heap", retainedKeys.size)
    HeapDumpMemoryStore.heapDumpUptimeMillis = SystemClock.uptimeMillis()
    dismissNotification()
    val heapDumpFile = heapDumper.dumpHeap() // AndroidHeapDumper
    if (heapDumpFile == null) {
      CanaryLog.d("Failed to dump heap, will retry in %d ms", WAIT_AFTER_DUMP_FAILED_MILLIS)
      scheduleRetainedInstanceCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS)
      showRetainedCountWithHeapDumpFailed(retainedKeys.size)
      return
    }

    refWatcher.removeRetainedKeys(retainedKeys) // 移除已經(jīng) heap dump 的 retainedKeys

    HeapAnalyzerService.runAnalysis(application, heapDumpFile) // 分析 heap dump 文件
  }

流程圖

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

到此這篇關(guān)于Android 進(jìn)階實現(xiàn)性能優(yōu)化之OOM與Leakcanary詳解原理的文章就介紹到這了,更多相關(guān)Android 性能優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論