showtoast图标样式(toast的show方法)

2022-07-18,,,,

“内存优化会不会?知道怎么定位内存问题吗?”面试官和蔼地坐在小会议室的一侧,亲切地问有些拘谨地小张。

“就是…那个,用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 标签标记:

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的操作。

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

《showtoast图标样式(toast的show方法).doc》

下载本文的Word格式文档,以方便收藏与打印。