toast的show方法「showtoast圖標樣式」

“內存優化會不會?知道怎麼定位內存問題嗎?”面試官和藹地坐在小會議室的一側,親切地問有些拘謹地小張。

“就是…那個,用LeakCanary 檢測一下泄漏,然後找到對應泄漏的地方,把錯誤的代碼改一下,沒回收的引用回收掉,優化下長短生命周期線程的依賴關係吧”

“那你了解LeakCanary 分析內存泄漏的原理嗎?”

“不好意思,平時沒有注意去看過” 小張心想:面試怎麼老問這個,我只是個普通的菜鳥啊。

前言

app性能優化總是開發中必不可少的一環,而其中內存優化又是重點之一。內存泄漏帶來的內存溢出崩潰,內存抖動帶來的卡頓不流暢。都在切切實實地影響着用戶的體驗。我們常常會使用LeakCanary來定位內存泄漏問題。也是時候來探索一下人家是怎麼實現的了。

名詞理解

hprof : hprof 文件是 Java 的 內存快照文件(Heap Profile 的縮寫),格式後綴為 .hprof,在leakCanary 中用於內存保存分析 WeakReference : 弱引用,當一個對象僅僅被weak reference(弱引用)指向, 而沒有任何其他strong reference(強引用)指向的時候, 如果這時GC運行, 那麼這個對象就會被回收,不論當前的內存空間是否足夠,這個對象都會被回收。在leakCanary 中用於監測該回收的無用對象是否被釋放。 curtains:Square 的另一個開源框架,Curtains 提供了用於處理 Android 窗口的集中式 API。在leakCanary中用於監測window rootView 在detached 後的內存泄漏。

目錄

本文主要從以下幾點入手分析

  1. 如何在項目中使用 LeakCanary工具
  2. 官方原理說明
  3. 默認如何監聽Activity ,view ,fragment 和 viewmodel
  4. Watcher.watch(object) 如何監聽內存泄漏
  5. 如何保存內存泄漏內存文件
  6. 如何分析內存泄漏文件
  7. 展示內存泄漏堆棧到ui中 不支持在 Docs 外粘貼 block

一,怎麼用?

查看官網文檔 可以看出使用方法非常簡單,基礎用法只需要添加相關依賴就行

//(1)
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
複製代碼

debugImplementation 只在debug模式的編譯和最終的debug apk打包時有效 注(1):標註的代碼中用了一行就實現了初始化,怎麼做到的呢? 通過查看源碼可以看到,leakcanary 通過 ContentProvider 進行初始化,在AppWatcherInstaller 類的oncreate方法中調用了真正的初始化代碼AppWatcher.manualInstall(application)。在AndroidManifest.xml中註冊該provider,註冊的ContentProvider會在 application 啟動的時候自動回調 oncreate方法。

internal sealed class AppWatcherInstaller : ContentProvider() {
  /**[MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */
  // (1)
  internal class MainProcess : AppWatcherInstaller()
  internal class LeakCanaryProcess : AppWatcherInstaller()
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    ///(2)
    AppWatcher.manualInstall(application)
    return true
  }
  //...
  }
複製代碼

說明一下源碼中的數字標註

  1. 代碼中定義了兩個內部類繼承自 AppWatcherInstaller。當用戶額外依賴 leakcanary-android-process 模塊的時候,自動在 process=”:leakcanary” 也註冊該provider。

代碼參見
leakcanary-android-process 模塊中的AndroidManifest.xml

  1. 這是真正的初始化代碼註冊入口

二,官方闡述

官方說明

本小節來自於官方網站的工作原理說明精簡 安裝 LeakCanary 後,它會通過 4 個步驟自動檢測並報告內存泄漏:

  1. 檢測被持有的對象LeakCanary 掛鈎到 Android 生命周期以自動檢測活動和片段何時被銷毀並應進行垃圾收集。這些被銷毀的對象被傳遞給一個ObjectWatcher,它持有對它們的弱引用。 可以主動觀察一個不再需要的對象比如一個 dettached view 或者 已經銷毀的 presenter
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
複製代碼

如果ObjectWatcher在等待 5 秒並運行垃圾收集後沒有清除持有的弱引用,則被監視的對象被認為是保留的,並且可能會泄漏。LeakCanary 將此記錄到 Logcat:

D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
  (Activity received Activity#onDestroy() callback) 

... 5 seconds later ...

D LeakCanary: Scheduling check for retained objects because found new object
  retained
複製代碼
  1. Dumping the heap 轉儲堆信息到文件中當保留對象的數量達到閾值時,LeakCanary 將 Java 內存快照 dumping 轉儲到 Android 文件系統上的.hprof文件(堆內存快照)中。轉儲堆會在短時間內凍結應用程序,並展示下圖的吐司:
  2. 分析堆內存LeakCanary使用Shark解析.hprof文件並在該內存快照文件中定位被保留的泄漏對象。 對於每個保留對象,LeakCanary 找到該對象的引用路徑,該引用阻止了垃圾收集器對它的回收。也就是泄漏跟蹤。 LeakCanary為每個泄漏跟蹤創建一個簽名 (對持有的引用屬性進行相加做sha1Hash),並將具有相同簽名的泄漏(即由相同錯誤引起的泄漏)組合在一起。如何創建簽名和通過簽名分組有待後文分析。
  3. 分類內存泄漏LeakCanary 將它在您的應用中發現的泄漏分為兩類:Application Leaks (應用程序泄漏)Library Leaks(庫泄漏)。一個Library Leaks是由已知的第三方庫導致的,你沒有控制權。這種泄漏正在影響您的應用程序,但不幸的是,修復它可能不在您的控制範圍內,因此 LeakCanary 將其分離出來。 這兩個類別分開Logcat結果中打印:
====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS
====================================
1 LIBRARY LEAK
...
┬───
│ GC Root: Local variable in native code
│
...
複製代碼

LeakCanary在其泄漏列表展示中會將其用Library Leak 標籤標記:Andorid進階一:LeakCanary源碼分析,從頭到尾搞個明白

img

LeakCanary 附帶一個已知泄漏的數據庫,它通過引用名稱的模式匹配來識別。例如:

Leak pattern: instance field android.app.Activity$1#this$0
Description: Android Q added a new IRequestFinishCallback$Stub class [...]
┬───
│ GC Root: Global variable in native code
│
├─ android.app.Activity$1 instance
│    Leaking: UNKNOWN
│    Anonymous subclass of android.app.IRequestFinishCallback$Stub
│    ↓ Activity$1.this$0
│                 ~~~~~~
╰→ com.example.MainActivity instance
複製代碼

Library Leaks 通常我們都無力對齊進行修復 您可以在AndroidReferenceMatchers類中查看已知泄漏的完整列表。如果您發現無法識別的 Android SDK 泄漏,請報告。您還可以自定義已知庫泄漏的列表。

三,監測activity,fragment,rootView和viewmodel

前面提到初始化的代碼如下,所以我們 查看manualInstall 的內部細節。

///初始化代碼
AppWatcher.manualInstall(application)

///AppWatcher 的 manualInstall 代碼
@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
   //*******檢查是否為主線程********/
  checkMainThread()
  if (isInstalled) {
    throw IllegalStateException(
      "AppWatcher already installed, see exception cause for prior install call", installCause
    )
  }
  check(retainedDelayMillis >= 0) {
    "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
  }
  installCause = RuntimeException("manualInstall() first called here")
  this.retainedDelayMillis = retainedDelayMillis
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // Requires AppWatcher.objectWatcher to be set
  ///(2)
  LeakCanaryDelegate.loadLeakCanary(application)
  ///(1)
  watchersToInstall.forEach {
    it.install()
  }
}
複製代碼

AppWatcher 作為Android 平台使用 ObjectWatcher 封裝的api中心。自動安裝配置默認的監聽。 以上代碼關鍵的地方用數字標出了

(1)Install 默認的監聽觀察

標註(1)處的代碼執行了 InstallableWatcher 的 install 操作,在調用的時候並沒有傳遞 watchersToInstall 參數,所以使用的是 appDefaultWatchers(application)。該處代碼在下面,提供了 四個默認監聽的Watcher

fun appDefaultWatchers(
  application: Application,
  ///(1.1)
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ///(1.2)
    ActivityWatcher(application, reachabilityWatcher),
    ///(1.3)
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    ///(1.4)
    RootViewWatcher(reachabilityWatcher),
    ///(1.5)
    ServiceWatcher(reachabilityWatcher)
  )
}
複製代碼

用數字標出的四個我們逐個分析

(1.1) reachabilityWatcher 參數

標註(1.1)處的代碼是一個 ReachabilityWatcher 參數,reachabilityWatcher 在後續的四個實例創建時候都有用到,代碼中可以看到reachabilityWatcher實例是AppWatcher 的成員變量:objectWatcher,對應的實例化代碼如下。

/**
 * The [ObjectWatcher] used by AppWatcher to detect retained objects.
 * Only set when [isInstalled] is true.
 */
val objectWatcher = ObjectWatcher(
  clock = { SystemClock.uptimeMillis() },
  checkRetainedExecutor = {
    check(isInstalled) {
      "AppWatcher not installed"
    }
    mainHandler.postDelayed(it, retainedDelayMillis)
  },
  isEnabled = { true }
)
複製代碼

可以看到objectWatcher 是一個 ObjectWatcher對象,該對象負責檢測持有對象的泄漏情況,會在第三小節進行分析。 回到 ActivityWatcher 實例的創建,繼續往下看標註的代碼

(1.2)ActivityWatcher 實例 完成Activity 實例的監聽

回到之前,標註(1.2)處的代碼創建了ActivityWatcher實例,並在install 的時候安裝,查看ActivityWatcher 類的源碼,看監聽Activity泄漏是怎麼實現的

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
     //(1.2.1) 通過動態代理,構造出生命周期回調的實現類 
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        //(1.2.3)
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    //(1.2.3)
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
複製代碼

(1.2.1) lifecycleCallbacks 實例

標註(1.2.1)處的代碼創建了
ActivityLifecycleCallbacks實例,該實例實現了Application.ActivityLifecycleCallbacks。通過 by “*noOpDelegate*“() ,利用動態代理實現了其他回調方法,感興趣的可以查看 noOpDelegate 的源碼

(1.2.2) activity監聽器的 install 方法

標註(1.2.2)處的代碼是初始化的主要代碼,該方法很簡單,就是在application的 中註冊 lifecycleCallbacks,在activity 被destroy 的時候會走到其中實現的方法

(1.2.3) 監聽activity 的 onActivityDestroyed 回調

標註(1.2.3)處的代碼是初始化的主要代碼,在 activity被銷毀的時候,回調該方法,在其中檢查該實例是否有泄漏,調用AppWatcher.objectWatcher. expectWeaklyReachable 方法,在其中完成activity的泄漏監測。 這時候又回到了 1.1 提到的 ObjectWatcher源碼,相關分析看第四節 。

(1.2-end)Activity監測相關總結

這樣ActivityInstaller 就看完了,了解了Activity 的初始化代碼以及加入監聽的細節。總結一下分為如下幾步:

  1. 調用ActivityInstaller.install 初始化方法
  2. 在Application 註冊ActivityLifecycleCallbacks
  3. 在所有activity onDestroy的時候調用ObjectWatcher的 expectWeaklyReachable方法,檢查過五秒後activity對象是否有被內存回收。標記內存泄漏。下一節分析。
  4. 檢測到內存泄漏的後續操作。後文分析。

(1.3) FragmentAndViewModelWatcher 監測 Fragment 和Viewodel實例

(1.3)處是創建了
FragmentAndViewModelWatcher 實例。監測fragment和viewmodel的內存泄漏。

該類實現了 SupportFragment和 androidxFragment以及androidO 的兼容,作為sdk開發來說,這種 兼容方式可以學習一下。

private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityCreated(
      activity: Activity,
      savedInstanceState: Bundle?
    ) {
      for (watcher in fragmentDestroyWatchers) {
        watcher(activity)
      }
    }
  }

override fun install() {
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
複製代碼

和ActivityWatcher 同樣的,install是註冊了生命周期監聽。不過是在對每個 activity create 的時候,交給 fragmentDestroyWatchers 元素們監聽。所以 fragmentDestroyWatchers才是真正的fragment和viewmodel 監聽者。 接下來看 fragmentDestroyWatchers 的元素們創建:

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

   //(1.3.1) android框架自帶的fragment泄漏監測支持從 AndroidO(26)開始。
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }
   //(1.3.2)
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME,
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
   //(1.3.3)
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  fragmentDestroyWatchers
}
複製代碼

可以看到內部創建了
AndroidOFragmentDestroyWatcher 來針對Fragment 進行監聽。原理是利用在 FragmentManager 中註冊
FragmentManager.FragmentLifecycleCallbacks 來監聽fragment 和 fragment.view 以及viewmodel 的實例泄漏。 從官方文檔可知,android內部的 fragment 在Api 26中才添加。所以LeakCanary針對於android框架自帶的fragment泄漏監測支持也是從 AndroidO(26)開始,見代碼(1.3.1)。 標註的 1.3.1,1.3.2,1.3.3 實例化的三個Wathcer 分別是
AndroidOFragmentDestroyWatcher,
AndroidXFragmentDestroyWatcher,
AndroidSupportFragmentDestroyWatcher。內部實現代碼大同小異,通過反射實例化不同的Watcher實現了androidX 和support 以及安卓版本間的兼容。

(1.3.1) AndroidOFragmentDestroyWatcher 實例

(1.3.1)處的代碼添加了一個androidO的觀察者實例。詳情見代碼,因為實現大同小異,分析參考1.3.2.

(1.3.2) AndroidXFragmentDestroyWatcher 實例

(1.3.2)處的代碼 調用 getWatcherIfAvailable 通過反射創建了
AndroidXFragmentDestroyWatcher實例,如果不存在Androidx庫則返回null。 現在跳到
AndroidXFragmentDestroyWatcher 的源碼分析

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {

  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
    //(1.3.2.1)初始化 ViewModelClearedWatcher 
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
     //監測 fragment.view 的泄漏情況
      val view = fragment.view
      if (view != null) {
        reachabilityWatcher.expectWeaklyReachable(
          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
    ) {
      //監測 fragment 的泄漏情況
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }

 ///初始化,註冊fragmentLifecycleCallbacks
  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      //註冊activity的 viewModel 監聽回調
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}
複製代碼

通過源碼可以看到,初始化該watcher是通過以下幾步。

  1. FragmentManager.registerFragmentLifecycleCallbacks 註冊監聽回調
  2. ViewModelClearedWatcher.install 初始化了對於activity.viewModel的監聽
  3. 在回調onFragmentCreated 中回調中使用ViewModelClearedWatcher.install註冊了對於fragment.viewModel的監聽。
  4. 在 onFragmentViewDestroyed 監聽 fragment.view 的泄漏
  5. 在 onFragmentDestroyed 監聽 fragment的泄漏。 監聽方法和ActivityWatcher大同小異,不同是多了個 ViewModelClearedWatcher.install 。現在分析這一塊的源碼,也就是標註中的 (1.3.2.1)。
//該watcher 繼承了ViewModel,生命周期被 ViewModelStoreOwner 管理。
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  private val viewModelMap: Map<String, ViewModel>?

  init {
    //(1.3.2.3)通過反射獲取所有的 store 存儲的所有viewModelMap
    viewModelMap = try {
      val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
      mMapField.isAccessible = true
      @Suppress("UNCHECKED_CAST")
      mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
      null
    }
  }

  override fun onCleared() {
    ///(1.3.2.4) viewmodle 被清理釋放的時候回調,檢查所有viewmodle 是否會有泄漏
    viewModelMap?.values?.forEach { viewModel ->
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      ///(1.3.2.2) 獲取ViewModelClearedWatcher實例
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}
複製代碼

通過代碼,可以看到viewModel的泄漏監測是通過創建一個新的viewModel實例來實現。在該實例的onCleared處監聽storeOwner的其餘 viewModel 是否有泄漏。標註出的代碼逐一分析:

(1.3.2.2 ) 處代碼:

獲取ViewModelClearedWatcher 實例,在自定義的 Factory中傳入storeOwner 和 reachabilityWatcher。

(1.3.2.3 ) 處代碼:

通過反射獲取storeOwner 的viewModelMap

(1.3.2.4 ) 處代碼:

在ViewModel完成使命OnClear的時候,開始監測storeOwner旗下所有ViewModel的內存泄漏情況。

(1.3-end)Fragment 和 viewmodel 監測泄漏總結:

監測方式都是通過ObjectWatcher的 expectWeaklyReachable 方法進行。fragment 利用
FragmentLifecyclerCallback回調註冊實現,ViewModel 則是在對應StoreOwner下創建了監測viewModel來實現生命周期的響應。 其中我們也能學習到通過反射來創建對應的平台兼容實現對象方式。以及藉助創建viewModel來監聽其餘ViewModel生命周期的想法。

(1.4) RootViewWatcher 的源碼分析

默認的四個Watcher中,來到了接下來的 RootViewWatcher。window rootview 監聽依賴了squre自家的Curtains框架。

implementation "com.squareup.curtains:curtains:1.0.1"
複製代碼

類的關鍵源碼如下:

 private val listener = OnRootViewAddedListener { rootView ->
 //如果是 Dialog TOOLTIP, TOAST, UNKNOWN 等類型的windows 
 //trackDetached 為true
    if (trackDetached) {
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

        val watchDetachedView = Runnable {
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
          )
        }

        override fun onViewAttachedToWindow(v: View) {
          mainHandler.removeCallbacks(watchDetachedView)
        }

        override fun onViewDetachedFromWindow(v: View) {
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }

  override fun install() {
    Curtains.onRootViewsChangedListeners += listener
  }

  override fun uninstall() {
    Curtains.onRootViewsChangedListeners -= listener
  }
}
複製代碼

看到關鍵代碼,就是 在Curtains中添加
onRootViewsChangedListeners 監聽器。當windowsType類型為 **Dialog** ***TOOLTIP***, ***TOAST***,或者未知的時候 ,在 onViewDetachedFromWindow 的時候監聽泄漏情況。 Curtains中的監聽器會在windows rootView 變化的時候被全局調用。Curtains是squareup 的另一個開源庫,Curtains 提供了用於處理 Android 窗口的集中式 API。具體移步他的官方倉庫。

(1.5) ServiceWatcher 監聽Service內存泄漏

接下來就是AppWatcher中的最後一個Watcher。 ServiceWatcher。代碼比較長,截取關鍵點分析。

(1.5.1)先看成員變量 activityThreadServices :

private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
private val activityThreadInstance by lazy {
  activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
}

private val activityThreadServices by lazy {
  val mServicesField =
    activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }

  @Suppress("UNCHECKED_CAST")
  mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
複製代碼

activityThreadServices 是個裝了所有<IBinder, Service> 對的Map。代碼中可以看到很粗暴地,直接通過反射從ActivityThread實例中拿到了mServices 變量 。賦值給activityThreadServices。 源碼中有多個swap操作,在install的時候執行,主要目的是將原來的一些service相關生命周期回調加上一些鉤子,用來監測內存泄漏,並且會在unInstall的時候給換回來。

(1.5.2)swapActivityThreadHandlerCallback :

拿到ActivityThread 的Handler,將其回調的 handleMessage,換成加了料的Handler.Callback,加料代碼如下

Handler.Callback { msg ->
  if (msg.what == STOP_SERVICE) {
    val key = msg.obj as IBinder
    activityThreadServices[key]?.let {
      onServicePreDestroy(key, it)
    }
  }
  mCallback?.handleMessage(msg) ?: false
}
複製代碼

代碼中可以看到,主要是對於 STOP_SERVICE 的操作做了一個鉤子,在之前執行 onServicePreDestroy。主要作用是為該service 創建一個弱引用,並且加到servicesToBeDestroyed[token] 中 。

(1.5.3)然後再看 swapActivityManager 方法。

該方法完成了將ActivityManager替換成IActivityManager的一個動態代理類。代碼如下:

Proxy.newProxyInstance(
  activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
//private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
  if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
    val token = args!![0] as IBinder
    if (servicesToBeDestroyed.containsKey(token)) {
       ///(1.5.3)
      onServiceDestroyed(token)
    }
  }
  try {
    if (args == null) {
      method.invoke(activityManagerInstance)
    } else {
      method.invoke(activityManagerInstance, *args)
    }
  } catch (invocationException: InvocationTargetException) {
    throw invocationException.targetException
  }
}
複製代碼

代碼所示,替換後的ActivityManager 在調用serviceDoneExecuting 方法的時候添加了個鉤子,如果該service在之前加入的servicesToBeDestroyed map中,則調用onServiceDestroyed 監測該service內存泄漏。

(1.5.4)代碼的onServiceDestroyed具體代碼如下

private fun onServiceDestroyed(token: IBinder) {
  servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
    serviceWeakReference.get()?.let { service ->
      reachabilityWatcher.expectWeaklyReachable(
        service, "${service::class.java.name} received Service#onDestroy() callback"
      )
    }
  }
}
複製代碼

這裡面的代碼很熟悉,和之前監測activity等是一樣的。 回到swapActivityManager方法,看代理ActivityManager的具體類型。 可以看到代理的對象如下面代碼所示,根據版本不同可能是ActivityManager 實例或者是ActivityManagerNative實例。 代理的接口是 Class.forName(“
android.app.IActivityManager”)。

val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  "android.app.ActivityManager" to "IActivityManagerSingleton"
} else {
  "android.app.ActivityManagerNative" to "gDefault"
}
複製代碼

(1.5-end)Service 泄漏監測總結

總結一下,service的泄漏分析通過加鉤子的方式,對一些系統執行做了監聽。主要分為以下幾步:

  1. 獲取ActivityThread中mService變量,得到service實例的引用
  2. 通過swapActivityThreadHandlerCallback 在ActivityThread 的 Handler.sendMessage 中添加鉤子,在執行到msg.what == STOP_SERVICE 的時候

四,ObjectWatcher 保留對象檢查分析

我們轉到 ObjectWatcher 的 expectWeaklyReachable 方法看看

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
   //是否啟用 , AppWatcher 持有的ObjectWatcher 默認是啟用的
  if (!isEnabled()) {
    return
  }
  ///移除之前已經被回收的監聽對象
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
    .toString()
  val watchUptimeMillis = clock.uptimeMillis()
   //(1) 創建弱引用
  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"
  }

  watchedObjects[key] = reference
  checkRetainedExecutor.execute {
    //(2)
    moveToRetained(key)
  }
}
複製代碼

繼續分析源碼中標註的地方。

(1) 創建弱引用

標註(1.2.4)處的代碼是初始化的主要代碼,創建要觀察對象的弱引用,傳入queue 作為gc 後的對象信息存儲隊列,WeakReference 中,當持有對象唄gc的時候,會將其包裝對象壓入隊列中。可以在後續對該隊列進行觀察。

(2) moveToRetained(key),檢查對應key對象的保留

作為Executor的runner 執行,在AppWatcher中,默認延遲五秒後執行該方法 查看源碼分析

@Synchronized private fun moveToRetained(key: String) {
///移除已經被回收的觀察對象
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
  //記錄泄漏時間
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    //回調泄漏監聽
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}
複製代碼

從上述代碼可知,ObjectWatcher 監測內存泄漏總共有以下幾步

  1. 清除已經被內存回收的監聽對象
  2. 創建弱引用,傳入 ReferenceQueue 作為gc 信息保存隊列
  3. 在延遲指定的時間後,再次檢查針對的對象是否被回收(通過檢查ReferenceQueue隊列內有無該WeakReference實例)
  4. 檢測到對象沒有被回收後,回調 onObjectRetainedListeners 們的 onObjectRetained

五,dumpHeap,怎麼個DumpHeap流程

(1.1)objectWatcher 添加 OnObjectRetainedListeners 監聽

回到最初AppWatcher的 manualInstall 方法。 可以看到其中執行了loadLeakCanary 方法。 代碼如下:

///(2)
  LeakCanaryDelegate.loadLeakCanary(application)
 //反射獲取InternalLeakCanary實例
  val loadLeakCanary by lazy {
  try {
    val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
    leakCanaryListener.getDeclaredField("INSTANCE")
      .get(null) as (Application) -> Unit
  } catch (ignored: Throwable) {
    NoLeakCanary
  }
}
複製代碼

該方法通過反射獲取了 InternalLeakCanary 的靜態實例。 並且調用了他的 invoke(application: Application)方法,所以我們接下來看InternalLeakCanary的該方法:

override fun invoke(application: Application) {
  _application = application

  checkRunningInDebuggableBuild()
  //(1.2)添加 addOnObjectRetainedListener
  AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

  val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
    //Gc觸發器
  val gcTrigger = GcTrigger.Default

  val configProvider = { LeakCanary.config }

  val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
  handlerThread.start()
  val backgroundHandler = Handler(handlerThread.looper)
///(1.3)
  heapDumpTrigger = HeapDumpTrigger(
    application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
    configProvider
  )
  ///(1.4) 添加application前後台變化監聽
  application.registerVisibilityListener { applicationVisible ->
    this.applicationVisible = applicationVisible
    heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
  }
  //(1.5)
  registerResumedActivityListener(application)
  //(1.6)
  addDynamicShortcut(application)

   // 6 判斷是否應該DumpHeap 
  // We post so that the log happens after Application.onCreate()
  mainHandler.post {
    // https://github.com/square/leakcanary/issues/1981
    // We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
    // which blocks until loaded and that creates a StrictMode violation.
    backgroundHandler.post {
      SharkLog.d {
        when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
          is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
          is Nope -> application.getString(
            R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
          )
        }
      }
    }
  }
}
複製代碼

我們看到初始化的時候做了這麼6步

  • (1.2) 將自己加入到ObjectWatcher 的對象異常持有監聽器中
  • (1.3)創建內存快照轉儲觸發器 HeapDumpTrigger
  • (1.4)監聽application 前後台變動,並且記錄來到後台時間,便於LeakCanary 針對剛剛切入後台的一些destroy操作做泄漏監測
  • (1.5)註冊activity生命周期回調,獲取當前resumed的activity實例
  • (1.6)添加動態的桌面快捷入口
  • (1.7)在異步線程中,判斷是否處於可dumpHeap的狀態,如果處於觸發一次內存泄漏檢查 其中最重要的是 1.2,我們重點分析作為ObjectRetainedListener 他在回調中做了哪些工作。

(1.2)添加對象異常持有監聽

可以看到代碼(1.2),在objectWatcher將自己加入到泄漏監測回調中。 當ObjectWatcher監測到對象依然被異常持有的時候,會回調 onObjectRetained 方法。 從源碼中可知,其中調用了 heapDumpTrigger的
scheduleRetainedObjectCheck方法, 代碼如下。

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}
複製代碼

HeapDumpTrigger 顧名思義,就是內存快照轉儲的觸發器。在回調中最終調用了HeapDumpTrigger 的 checkRetainedObjects方法來檢查內存泄漏。

(1.3)檢查內存泄漏checkRetainedObjects

private fun checkRetainedObjects() {
  val iCanHasHeap = HeapDumpControl.iCanHasHeap()

  val config = configProvider()
 //省略一些代碼,主要是判斷 iCanHasHeap。
 //如果當前處於不dump內存快照的狀態,就先不處理。如果有新的異常持有對象被發現則發送通知提示
 //%d retained objects, tap to dump heap
  /** ...*/

  var retainedReferenceCount = objectWatcher.retainedObjectCount

   //主動觸發gc
  if (retainedReferenceCount > 0) {
    gcTrigger.runGc()
    //重新獲取異常持有對象
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }
  //如果泄漏數量小於閾值,且app在前台,或者剛轉入後台,就展示泄漏通知,並先返回
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

//如果泄漏數量到達dumpHeap要求,繼續往下
   ///轉儲內存快照在  WAIT_BETWEEN_HEAP_DUMPS_MILLIS (默認60秒)只會觸發一次,如果之前剛觸發過,就先不生成內存快照,直接發送通知了事。
//省略轉儲快照時機判斷,不滿足的話會提示 Last heap dump was less than a minute ago
/**...*/

  dismissRetainedCountNotification()
  val visibility = if (applicationVisible) "visible" else "not visible"
  ///轉儲內存快照
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}
複製代碼

這一塊也可以看出檢測是否需要dumpHeap分為4步。

  1. 如果沒有檢測到異常持有的對象,返回
  2. 如果有異常對象,主動觸發gc
  3. 如果還有異常對象,就是內存泄漏了。
  4. 判斷泄漏數量是否到達需要dump的地步
  5. 判斷一分鐘內是否叫進行過dump了
  6. dumpHeap 前面都是判斷代碼,關鍵重點在於dumpHeap方法

(1.4)dumpHeap 轉儲內存快照

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
  saveResourceIdNamesToMemory()
  val heapDumpUptimeMillis = SystemClock.uptimeMillis()
  KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
  when (val heapDumpResult = heapDumper.dumpHeap()) {
    is NoHeapDump -> {
     //省略 dump失敗,等待重試代碼和發送失敗通知代碼
    }
    is HeapDump -> {
      lastDisplayedRetainedObjectCount = 0
      lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
      ///清除 objectWatcher 中,在heapDumpUptimeMillis之前持有的對象,也就是已經dump的對象
      objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
       // 發送文件到HeapAnalyzerService解析
      HeapAnalyzerService.runAnalysis(
        context = application,
        heapDumpFile = heapDumpResult.file,
        heapDumpDurationMillis = heapDumpResult.durationMillis,
        heapDumpReason = reason
      )
    }
  }
}
複製代碼

HeapDumpTrigger#dumpHeap中調用到了 AndroidHeapDumper#dumpHeap方法。 並且在dump後馬上調用
HeapAnalyzerService.runAnalysis 進行內存分析工作,該方法在下一節分析。先看AndroidHeapDumper#dumHeap源碼

override fun dumpHeap(): DumpHeapResult {
//創建新的hprof 文件
  val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump

  val waitingForToast = FutureResult<Toast?>()
  ///展示dump吐司
  showToast(waitingForToast)

  ///如果展示吐司時間超過五秒,就不dump了
  if (!waitingForToast.wait(5, SECONDS)) {
    SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
    return NoHeapDump
  }

  //省略dumpHeap通知欄提示消息代碼
  val toast = waitingForToast.get()

  return try {
    val durationMillis = measureDurationMillis {
    //調用DumpHprofData
      Debug.dumpHprofData(heapDumpFile.absolutePath)
    }
    if (heapDumpFile.length() == 0L) {
      SharkLog.d { "Dumped heap file is 0 byte length" }
      NoHeapDump
    } else {
      HeapDump(file = heapDumpFile, durationMillis = durationMillis)
    }
  } catch (e: Exception) {
    SharkLog.d(e) { "Could not dump heap" }
    // Abort heap dump
    NoHeapDump
  } finally {
    cancelToast(toast)
    notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
  }
}
複製代碼

在該方法內,最終調用 Debug.dumpHprofData 方法 完成hprof 快照的生成。

六,分析內存 HeapAnalyzerService

上面代碼分析中可以看到,在dumpHeap後緊跟着就是啟動內存分析服務的方法。 現在我們跳轉到HeapAnalyzerService的源碼處。

override fun onHandleIntentInForeground(intent: Intent?) {
     //省略參數獲取代碼
  val config = LeakCanary.config
  val heapAnalysis = if (heapDumpFile.exists()) {
    analyzeHeap(heapDumpFile, config)
  } else {
    missingFileFailure(heapDumpFile)
  }
   //省略完善分析結果屬性的代碼
  onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
  config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
複製代碼

可以看到重點在於 analyzeHeap,其中調用了 HeapAnalyzer#analyze HeapAnalyzer 類位於shark模塊中。

(1)HeapAnalyzer#analyze

內存分析方法代碼如下:

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

 //省略內存快照文件不存在的處理代碼

  return try {
    listener.onAnalysisProgress(PARSING_HEAP_DUMP)
   ///io讀取 內存快照
    val sourceProvider = ConstantMemoryMetricsDualSourceProvider(FileSourceProvider(heapDumpFile))
    sourceProvider.openHeapGraph(proguardMapping).use { graph ->
      val helpers =
        FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
     //關鍵代碼:在此處找到泄漏的結果以及其對應調用棧
      val result = helpers.analyzeGraph(
        metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
      )
      val lruCacheStats = (graph as HprofHeapGraph).lruCacheStats()
      ///io讀取狀態
      val randomAccessStats =
        "RandomAccess[" +
          "bytes=${sourceProvider.randomAccessByteReads}," +
          "reads=${sourceProvider.randomAccessReadCount}," +
          "travel=${sourceProvider.randomAccessByteTravel}," +
          "range=${sourceProvider.byteTravelRange}," +
          "size=${heapDumpFile.length()}" +
          "]"
      val stats = "$lruCacheStats $randomAccessStats"
      result.copy(metadata = result.metadata + ("Stats" to stats))
    }
  } catch (exception: Throwable) {
  //省略異常處理
  }
}
複製代碼

通過分析代碼可知:分析內存快照分為以下5步:

  1. 讀取hprof內存快照文件
  2. 找到LeakCanary 標記的泄漏對象們的數量和弱引用包裝 ids,class name 為com.squareup.leakcanary.KeyedWeakReference

代碼在 KeyedWeakReferenceFinder#findLeakingObjectIds

  1. 找到泄漏對象的gcRoot開始的路徑

代碼在PathFinder#findPathsFromGcRoots

  1. 返回分析結果,走結果回調
  2. 回調內 展示內存分析成功或者失敗的通知欄消息,並將泄漏列表存儲到數據庫中

詳情代碼看
DefaultOnHeapAnalyzedListener#onHeapAnalyzed 以及 LeaksDbHelper

  1. 點開通知欄跳轉到LeaksActivity 展示內存泄漏信息。

七,總結

終於從頭到尾,總算是梳理了一波LeakCanary 源碼

過程中學習到了這麼多—>

  • 主動調用Gc的方式 GcTrigger.Default.runGc()
Runtime.getRuntime().gc()
複製代碼
  • seald class 密封類來表達狀態,比如以下幾個(關鍵好處在於使用when可以直接覆蓋所有情況,而不必使用else)。
sealed class ICanHazHeap {
  object Yup : ICanHazHeap()
  abstract class Nope(val reason: () -> String) : ICanHazHeap()
  class SilentNope(reason: () -> String) : Nope(reason)
  class NotifyingNope(reason: () -> String) : Nope(reason)
}
sealed class Result {
  data class Done(
    val analysis: HeapAnalysis,
    val stripHeapDumpDurationMillis: Long? = null
    ) : Result()
  data class Canceled(val cancelReason: String) : Result()
}
複製代碼
  • 了解了系統創建內存快照的api
 Debug.dumpHprofData(heapDumpFile.absolutePath)
複製代碼
  • 知道了通過 ReferenceQueue 檢測內存對象是否被gc,之前WeakReference都很少用。
  • 學習了leakCanary的分模塊思想。作為sdk,很多功能模塊引入自動開啟。比如 leakcanary-android-process 自動開啟對應進程等。
  • 學習了通過反射hook代碼,替換實例達成添加鉤子的操作。比如在Service泄漏監聽代碼中,替換Handler和activityManager的操作。

多多看源碼還是有好處的。難怪我之前工作都找不到。看的太少了。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/252333.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-14 02:15
下一篇 2024-12-14 02:15

相關推薦

發表回復

登錄後才能評論