我报名参加金石方案1期挑战——分割10万奖池,这是我的第2篇文章,点击查看活动详情

本篇文章主要是剖析LeakCanary一个不起眼的类:BackgroundTrigger。当呈现内存走漏时,这个类能够协助咱们当运用处于后台状况时再履行相关的dump heap操作。

依赖如下:

implementation 'com.squareup.leakcanary:leakcanary-android-release:2.9.1'

还没剖析这个类之前相信大家下意识的经过下面两种办法完成前后台监听:

  1. 经过Application.registerActivityLifecycleCallbacks()添加回调的办法来剖析当时运用处于前台仍是后台

  2. 运用Application等级的ProcessLifecycleOwner添加Observer的办法监听前后台

可是,BackgroundTrigger不是彻底这么完成的,而且其监听运用是否切换到后台状况和上面两种办法监听运用切换到后台状况,这两个后台状况一些情况不是等价的,比方:

上面两种监听到后台状况,而BackgroundTrigger不一定处于所监听的后台状况,而BackgroundTrigger监听到的后台状况,一定意味着上面两种办法监听也是处于后台状况。

除此之外,BackgroundTrigger还有一个其他的狠活十分值得大家学习一下,接下来咱们开端剖析。

监听运用是否处于后台

这儿咱们从BackgroundTrigger的入口办法start()剖析:

fun start() {
  checkMainThread()
  backgroundListener.install(application)
}

先检测是否为主线程,不是会抛出异常,接下来调用backgroundListenerinstall()办法,咱们先看下backgroundListener是个啥:

肢解LeakCanary:精通BackgroundTrigger的“狠活”

能够看到backgroundListener便是一个BackgroundListener类型,而且完成了ActivityLifecycleCallbacks接口:

肢解LeakCanary:精通BackgroundTrigger的“狠活”

接下来就要剖析BackgroundListenerinstall()办法:

fun install(application: Application) {
  application.registerActivityLifecycleCallbacks(this)
  ...
  checkAppInBackground.run()
}

因为BackgroundListener完成了ActivityLifecycleCallbacks,所以这儿经过传递过来的Application注入到监听运用所有Activity生命周期回调的一个回调集合中,这个大家狠熟悉了。

从前我说过BackgroundTrigger并不是彻底经过registerActivityLifecycleCallbacks完成了,可是仍是凭借了其完成。

下面咱们直接看下BackgroundTrigger完成重写的onActivityPaused()监听办法:

override fun onActivityPaused(activity: Activity) {
  mainHandler.removeCallbacks(checkAppInBackground)
  //BACKGROUND_DELAY_MS为1s
  mainHandler.postDelayed(checkAppInBackground, BACKGROUND_DELAY_MS)
}

经过Hanlder推迟1s履行一个checkAppInBackground

private val checkAppInBackground: Runnable = object : Runnable {
  override fun run() {
    val appInBackgroundNow = processInfo.isImportanceBackground
    updateBackgroundState(appInBackgroundNow)
    if (!appInBackgroundNow) {
      mainHandler.removeCallbacks(this)
      mainHandler.postDelayed(this, BACKGROUND_REPEAT_DELAY_MS)
    }
  }
}

要害代码便是processInfo.isImportanceBackground,这个便是本文要讲怎么监听运用是否已经切换到后台状况的:

override val isImportanceBackground: Boolean
  get() {
    ActivityManager.getMyMemoryState(memoryOutState)
    return memoryOutState.importance >= RunningAppProcessInfo.IMPORTANCE_BACKGROUND
  }
  1. 调用ActivityManagergetMyMemoryState()办法,memoryOutState是一个RunningAppProcessInfo类型:

    肢解LeakCanary:精通BackgroundTrigger的“狠活”

    经过注释能够看到获取当时这个运用进程的内存状况信息,并填充信息到当时传入的RunningAppProcessInfo对现象中,并指明晰填充的哪些字段(没指明的不进行填充):

    肢解LeakCanary:精通BackgroundTrigger的“狠活”

  2. 然后运用被填充的RunningAppProcessInfoimportance判别,如果大于等于RunningAppProcessInfo.IMPORTANCE_BACKGROUND(400)就认为处于后台状况:

    这个400的状况代表着当时运用进程运用不再积极活泼的运转咱们相关的组件了:

    肢解LeakCanary:精通BackgroundTrigger的“狠活”

    也就意味着并不是当咱们home掉运用,就认为其处于后台状况,只要其处于一个十分不活泼的响应状况才确定其处于后台进程状况,这也便是最初说的那两种办法监听的后台状况和BackgroundTrigger监听到的后台状况不同的当地。

    后者这样完成的目的,便是为了尽可能削减dump heap对咱们运用进程的影响。

咱们回到checkAppInBackground,能够看到其履行isImportanceBackground后台状况检测后,会再次推迟5s再去经过postDelay()去调用自身检测是否处于后台状况。

动态代理优化接口办法的重写

之前有写过这样的一篇文章:接口运用额外重写的无关办法太多?优化它,便是为了处理,当咱们完成某个接口时只想重写其中的一个必须的办法,而不是重写所有的办法(包含无关的)。

然后BackgroundTrigger来了一个狠活,经过动态代理的办法创建一个接口的完成类目标并托付给要完成这个接口的类BackgroundListener便是一个典型的例子:

肢解LeakCanary:精通BackgroundTrigger的“狠活”

上面说过,BackgroundListener完成了ActivityLifecycleCallbacks接口,这个接口要重写的办法可是十分多,可是咱们看看BackgroundListener重写了几个办法:

肢解LeakCanary:精通BackgroundTrigger的“狠活”

就完成了其中的两个办法,其他的一个都没完成 ,要害便是BackgroundListener即将完成的ActivityLifecycleCallbacks接口托付给了noOpDelegate回来的完成类,咱们看下这个办法:

internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
internal inline fun <reified T : Any> noOpDelegate(): T {
  val javaClass = T::class.java
  return Proxy.newProxyInstance(
    javaClass.classLoader, arrayOf(javaClass), NO_OP_HANDLER
  ) as T
}
private val NO_OP_HANDLER = InvocationHandler { _, _, _ ->
  // no op
}

这便是科技与狠话啊,真是万万没想到哈。使用内联函数的特性,凭借于泛型实化,经过动态代理的办法创建一个ActivityLifecycleCallbacks的完成类。

总结

本篇文章主要是讲解了BackgroundTrigger怎么监听后台状况的,相比较于传统的办法,这种监听后台状况的办法更加严厉,进行dump heap时会大大削减对咱们运用的影响,以及最终面的黑科技:动态代理的办法创建一个接口的完成类目标并托付给要完成这个接口的类,削减接口不需要的办法的重写。