[Advanced Andorid] LeakCanary source code analysis, understand from beginning to end

"Will it be possible to optimize memory? Do you know how to locate memory problems?" The interviewer sat kindly on the side of the small conference room and asked the somewhat restrained Xiao Zhang kindly.

"That's it, use LeakCanary to detect the leak, then find the corresponding leak, change the wrong code, recycle the unreclaimed references, and optimize the dependencies of the long and short life cycle threads."

"Then do you understand how LeakCanary analyzes memory leaks?"

"I'm sorry, I didn't pay attention to it."

Xiao Zhang thought to himself: Why do you keep asking this in interviews? I'm just an ordinary rookie.

foreword

App performance optimization is always an essential part of development, and memory optimization is one of the key points. The memory overflow caused by the memory leak crashes, and the stuck caused by the memory jitter is not smooth. All of them have a real impact on the user experience. We often use LeakCanary to locate memory leaks. It's also time to explore how people do it.

noun understanding

hprof: The hprof file is a Java memory snapshot file (abbreviation for Heap Profile), the format suffix is ​​.hprof, which is used for memory preservation analysis in leakCanary

WeakReference: Weak reference, when an object is only pointed to by a weak reference (weak reference) without any other strong reference (strong reference), if the GC runs at this time, then the object will be reclaimed, regardless of the current memory space Whether it is enough, the object will be recycled. Used in leakCanary to monitor whether the reclaimed useless objects are released.

curtains: Another open source framework from Square, Curtains provides a centralized API for working with Android windows. Used in leakCanary to monitor window rootViewmemory leaks after detached.

Table of contents

This paper mainly analyzes the following points:

  1. How to use LeakCanarytools
  2. Official principle statement
  3. How to monitor Activity, view, fragment and viewmodel by default
  4. Watcher.watch(object) 如何监听内存泄漏
  5. 如何保存内存泄漏内存文件
  6. 如何分析内存泄漏文件
  7. 展示内存泄漏堆栈到ui中

image.png

一,怎么用?

查看官网文档 可以看出使用方法非常简单,基础用法只需要添加相关依赖就行

//(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

代码(2),这是真正的初始化代码注册入口

二,官方阐述

官方说明

本小节来自于官方网站的工作原理说明精简

安装 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文件(堆内存快照)中。转储堆会在短时间内冻结应用程序,并展示下图的吐司: img

  1. 分析堆内存

LeakCanary使用Shark解析.hprof文件并在该内存快照文件中定位被保留的泄漏对象。 对于每个保留对象,LeakCanary 找到该对象的引用路径,该引用阻止了垃圾收集器对它的回收。也就是泄漏跟踪。 LeakCanary为每个泄漏跟踪创建一个签名 (对持有的引用属性进行相加做sha1Hash),并将具有相同签名的泄漏(即由相同错误引起的泄漏)组合在一起。如何创建签名和通过签名分组有待后文分析。

  1. 分类内存泄漏

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 标签标记: 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的内存泄漏。

该类实现了 SupportFragmentandroidxFragment以及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才是真正的fragmentviewmodel 监听者。 接下来看 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 来监听fragmentfragment.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 ) 处代码:

通过反射获取storeOwnerviewModelMap

(1.3.2.4 ) 处代码:

在ViewModel完成使命OnClear的时候,开始监测storeOwner旗下所有ViewModel的内存泄漏情况。

(1.3-end)Fragment 和 viewmodel 监测泄漏总结:

监测方式都是通过ObjectWatcherexpectWeaklyReachable 方法进行。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 方法。 从源码中可知,其中调用了 heapDumpTriggerscheduleRetainedObjectCheck方法, 代码如下。

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}
复制代码

HeapDumpTrigger 顾名思义,就是内存快照转储的触发器。在回调中最终调用了HeapDumpTriggercheckRetainedObjects方法来检查内存泄漏。

(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泄漏监听代码中,替换HandleractivityManager的操作。

多多看源码还是有好处的。难怪我之前工作都找不到。看的太少了。

Guess you like

Origin juejin.im/post/6976905932403900446