内存泄漏检测工具LeakCanary源码解析

目录
  • 前言
  • 使用
  • 源码解析
    • LeakCanary自动初始化
    • 如何关闭自动初始化
    • LeakCanary初始化做了什么
    • ActivityWatcher
    • FragmentAndViewModelWatcher
    • RootViewWatcher
    • ServiceWatcher
    • Leakcanary对象泄漏检查
  • 总结

前言

LeakCanary是一个简单方便的内存泄漏检测工具,它是由大名鼎鼎的Square公司出品并开源的出来的。目前大部分APP在开发阶段都会接入此工具用来检测内存泄漏问题。它让我们开发者可以在开发阶段就发现一些没有注意到或者不规范的代码导致的内存泄漏,从而就避免了因内存泄漏而最终导致OOM的问题。

使用

LeakCanary的使用非常简单,我们只需要在项目module下的build.gradle文件里dependencies里面加上依赖就行。

dependencies {
    // debugImplementation because LeakCanary should only run in debug builds.
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

只需要加上依赖就行了,它会在应用启动的时候自动进行初始化。并且由于我们使用的是debugImplementation,因此它只在debug包下有效,所以不用担心这个会在release包下造成性能影响。

源码解析

接下来将通过阅读源码,并从以下几个方面去深入Leakcanary原理,本文将以2.9.1源码为例。

  • LeakCanary自动初始化
  • LeakCanary初始化做了什么
  • LeakCanary默认的4种监听器
  • Leakcanary对象泄漏检查

LeakCanary自动初始化

我们集成的时候只需要添加依赖,不需要像其他第三方SDK那样在Application或其他地方手动调用初始化方法,其实它是借助ContentProvider来实现的,通过源码来看一下MainProcessAppWatcherInstaller这个类:

internal class MainProcessAppWatcherInstaller : ContentProvider() {
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
  ...
 }

如果对app的启动流程有了解的童鞋就清楚,APP启动时,ContentProviderApplication的顺序是:

Application.attachBaseContext() -> contentProvider.onCreate() -> Application.onCreate()

通过源码来看下,在ActivityThread类里面handleBindApplication下:

@UnsupportedAppUsage
private void handleBindApplication(AppBindData data) {
    ...
    // 这边创建application,并会调用到application的attach()方法,最终调用到attachBaseContext()方法
    app = data.info.makeApplication(data.restrictedBackupMode, null);
    ...
    // don't bring up providers in restricted mode; they may depend on the
    // app's custom Application class
    if (!data.restrictedBackupMode) {
        if (!ArrayUtils.isEmpty(data.providers)) {
            //初始化Provider并且调用了Provider的onCreate()方法
            installContentProviders(app, data.providers);
        }
    }
    ...
    try {
        //调用了Application的onCreate()方法
        mInstrumentation.callApplicationOnCreate(app);
    } catch (Exception e) {
        if (!mInstrumentation.onException(app, e)) {
            throw new RuntimeException(
              "Unable to create application " + app.getClass().getName()
              + ": " + e.toString(), e);
        }
    }
    ...
}

这样ContentProvider会在Application.onCreate()前初始化,就会调用到了LeakCanary的初始化方法,实现了自动初始化。

注意: 这样使用ContentProvider的进行初始化的写法虽然方便,方便了开发人员集成使用。但是可能会带来启动耗时的问题,并且无法控制初始化的时机。不过这对于LeakCanary这个工具库来说影响不大,因为这个也只在Debug阶段使用。

如何关闭自动初始化

如果我们想要关闭自动化初始化,自己选择在合适的地方进行初始化的话,可以通过覆盖资源文件里面的值来进行关闭

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>

并且在合适的位置自行调用 AppWatcher.manualInstall(application)进行手动初始化。

LeakCanary初始化做了什么

上面我们说到LeakCanary是使用ContentProvider进行初始化的,那么我们就从MainProcessAppWatcherInstaller开始。

internal class MainProcessAppWatcherInstaller : ContentProvider() {
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    //这边调用初始化的方法
    AppWatcher.manualInstall(application)
    return true
  }
  ...
 }

这边会在这个MainProcessAppWatcherInstalleronCreate方法里面调用AppWatcher.manualInstall(application),进行初始化。

@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
    )
  }
  //判断设置的延迟时间是否小于0,这个时间是用来延迟检测被观察的对象是否已被释放,
  //也就是说如果到了这个时间,对象仍没有被释放,那么可能出现了内存泄漏
  check(retainedDelayMillis >= 0) {
    "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
  }
  this.retainedDelayMillis = retainedDelayMillis
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  //初始化 InternalLeakCanary,并调用了InternalLeakCanary.invoke()方法,
  //这个类是用来检查判断对象是否被回收
  LeakCanaryDelegate.loadLeakCanary(application)
  //开启监听器,也就是appDefaultWatchers(application)方法里面的
  watchersToInstall.forEach {
    it.install()
  }
  // Only install after we're fully done with init.
  installCause = RuntimeException("manualInstall() first called here")
}

manualInstall方法里面先做一些判断校验合法,并执行了LeakCanaryDelegate.loadLeakCanary,这里面会调用内部的invoke()方法,对LeakCanary的检查判断泄漏的一些类进行初始化。接下来会对watchersToInstall列表里面的四种观察类型的生命周期监视器调用install()方法,开启监听。

再来看下watchersToInstall,默认情况下使用的是 appDefaultWatchers(application)返回的list集合。

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

这边会创建ReachabilityWatcher,也就是objectWatcher,这边看下他的初始化:

object AppWatcher {
 ...
/**
 * 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 }
 )
 ...
}

这边重点看下checkRetainedExecutor这个入参,它是一个Executor,执行run时,就会调用mainHandler.postDelayed,后面在对对象回收检查时,会调用run方法,通过postDelayed延时去检查。

接着上面的appDefaultWatchers方法,里面会创建四种类型的生命周期监听器,分别是Activity、Fragment、RootView和Service。下面将对这4者的源码进行分析

ActivityWatcher

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
  //回调,这个是向application.registerActivityLifecycleCallbacks注册activity生命周期回调
  //这边通过监听Activity的onDestroyed来观察它的回收状态
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        //一旦activity进入到destory,则开始通过onActivityDestroyed观察它的回收状态
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }
  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }
  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

Activity的监听比较简单,只需要注册一个回调即可,因为Android本身有提供全局监听Activity的生命周期的回调。只需要在onActivityDestroyed回调里面对对象回收情况进行观察,因为一旦进入到onActivityDestroyed就表明Activity退出了,此时可以开始观察Activity是否回收。

这边有一个知识点,就是上面的Application.ActivityLifecycleCallbacks by noOpDelegate(),这边使用到了委托noOpDelegate(),这个的作用是使得接口类可以只实现自己想要的方法,而不需要全部实现。

FragmentAndViewModelWatcher

Fragment的监听就比较麻烦了,需要对不同包的Fragment做适配处理,分别是:

  • android.app.Fragment
  • android.support.v4.app.Fragment
  • androidx.fragment.app.Fragment
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
  //添加android.app.Fragment监听,创建AndroidOFragmentDestroyWatcher,并加入到集合中
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }
  //添加 androidx.fragment.app.Fragment监听,
  //通过反射的方式创建AndroidXFragmentDestroyWatcher,并加入到集合中
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME,
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  //添加android.support.v4.app.Fragment监听
  //通过反射的方式 创建 AndroidSupportFragmentDestroyWatcher,并加入到集合中
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  fragmentDestroyWatchers
}

这边会对不同包下的Fragment创建Watcher,并把它们加入到一个List中,接下来就是当外部调用install()时,就会调用到application.registerActivityLifecycleCallbacks(lifecycleCallbacks),下面来看下这个lifecycleCallbacks

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

可以看到,这边是在onActivityCreated的时候调用各个Watcher的invoke()方法。然后在invoke()方法里面通过activity.fragmentManageractivity.supportFragmentManager调用registerFragmentLifecycleCallbacks去注册Fragment的生命周期回调。

这边就只看下AndroidX下的Fragment生命周期监听

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }
    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      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
    ) {
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }
  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}

AndroidOFragmentDestroyWatcher类里面,会创建一个Fragment的生命周期回调,也就是FragmentManager.FragmentLifecycleCallbacks,并在onFragmentViewDestroyedonFragmentDestroyed这两个回调方法里面进行对象回收检查。

在该类里面,我们还能看到install了ViewModel,对ViewModel的生命周期进行观察。这边会区分是Activity的ViewModel还是Fragment的ViewModel。

  • Activity:在invoke()方法里面调用ViewModelClearedWatcher.install,并传入Activity的ViewModelStoreOwner
  • Fragment:在onFragmentCreated回调里面调用了ViewModelClearedWatcher.install,传入Fragment的ViewModelStoreOwner

那么在ViewModelClearedWatcher里面,又是怎么对viewModel的生命周期进行观察的呢?这边就不贴代码了:

  • 在install方法里面创建一个ViewModel,也就是ViewModelClearedWatcher,加入到对应传入的ViewModelStoreOwner下的ViewModelStore里的集合里面。
  • 然后在ViewModelClearedWatcheronCleared()方法里面通过反射的方式取出ViewModelStore里面的集合,进行迭代遍历,对集合里面的所有ViewModel开启对象回收检查。

RootViewWatcher

private val listener = OnRootViewAddedListener { rootView ->
  val trackDetached = when(rootView.windowType) {
    PHONE_WINDOW -> {
      when (rootView.phoneWindow?.callback?.wrappedCallback) {
        // Activities are already tracked by ActivityWatcher
        // activity不需要重复注册
        is Activity -> false
        //监听dialog,这边还需要判断下资源文件里面的配置信息是否是打开的
        is Dialog -> {
          // Use app context resources to avoid NotFoundException
          // https://github.com/square/leakcanary/issues/2137
          val resources = rootView.context.applicationContext.resources
          resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
        }
        // Probably a DreamService
        // 其他情况都打开
        else -> true
      }
    }
    // Android widgets keep detached popup window instances around.
    POPUP_WINDOW -> false
    TOOLTIP, TOAST, UNKNOWN -> 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) {
        //当view从界面上移除时时,就执行监听,通过Handler.post()
        mainHandler.post(watchDetachedView)
      }
    })
  }
}
override fun install() {
  //使用三方库的Curtains来对view进行状态的监听
  Curtains.onRootViewsChangedListeners += listener
}

RootView这边只要监听一些dialog、tooltip、toast等,代码不长,简单说下

  • 创建一个OnRootViewAddedListener监听,使用的是第三方库Curtains,在回调里面拿到rootView;
  • 对这个rootView注册一个OnAttachStateChangeListener监听,在onViewAttachedToWindow方法里面移除对象回收检查。在onViewDetachedFromWindow里面开启对象回收检查;

ServiceWatcher

//存放即将stop的service
private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
//通过反射的方式调用ActivityThread里面的currentActivityThread方法,也就是取到ActivityThread对象
private val activityThreadInstance by lazy {
  activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
}
//通过反射方式从ActivityThread取出mServices
private val activityThreadServices by lazy {
  val mServicesField =
    activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }
  @Suppress("UNCHECKED_CAST")
  mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
override fun install() {
  //做一些检查
  checkMainThread()
  check(uninstallActivityThreadHandlerCallback == null) {
    "ServiceWatcher already installed"
  }
  check(uninstallActivityManager == null) {
    "ServiceWatcher already installed"
  }
  try {
    //hook ActivityThread里面的mH,也就是Handler,并替换里面的mCallBack
    swapActivityThreadHandlerCallback { mCallback ->
      //还原hook的mCallback,uninstall()时使用
      uninstallActivityThreadHandlerCallback = {
        swapActivityThreadHandlerCallback {
          mCallback
        }
      }
      //返回需要替换的mCallback,在这里面监听handler消息,
      Handler.Callback { msg ->
        // https://github.com/square/leakcanary/issues/2114
        // On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord
        // instead of an IBinder. This crashes on a ClassCastException. Adding a type check
        // here to prevent the crash.
        if (msg.obj !is IBinder) {
          return@Callback false
        }
        //如果收到了Service的Stop的消息,表示service要结束了,
        //此时调用onServicePreDestroy,将当前service加入到servicesToBeDestroyed集合里面去
        if (msg.what == STOP_SERVICE) {
          val key = msg.obj as IBinder
          activityThreadServices[key]?.let {
            onServicePreDestroy(key, it)
          }
        }
        //继续处理消息
        mCallback?.handleMessage(msg) ?: false
      }
    }
    //这边hook ActivityManagerService
    swapActivityManager { activityManagerInterface, activityManagerInstance ->
      uninstallActivityManager = {
        swapActivityManager { _, _ ->
          activityManagerInstance
        }
      }
      //使用动态代理的方式
      Proxy.newProxyInstance(
        activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
      ) { _, method, args ->
        //如果执行serviceDoneExecuting方法时,则调用onServiceDestroyed()
        //方法来观察service的内存回收情况
        if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
          val token = args!![0] as IBinder
          if (servicesToBeDestroyed.containsKey(token)) {
            onServiceDestroyed(token)
          }
        }
        try {
          if (args == null) {
            method.invoke(activityManagerInstance)
          } else {
            method.invoke(activityManagerInstance, *args)
          }
        } catch (invocationException: InvocationTargetException) {
          throw invocationException.targetException
        }
      }
    }
  } catch (ignored: Throwable) {
    SharkLog.d(ignored) { "Could not watch destroyed services" }
  }
}

Service的结束监听是通过hool的方式进行的,步骤如下:

  • 通过反射的方取出ActivityThread里面的mH,也就是Handler。然后再取出该Handler里面的mCallback,并通过反射,使用自己创建的callBack去替换,然后在Callback里面监听Handle消息,如果收到的消息是msg.what == STOP_SERVICE,则表示Service即将结束,此时将该service加入到待观察集合里面去。
  • 接下来通过hook的方式,hook住ActivityManagerService。使用动态代理,如果执行了serviceDoneExecuting方法,则表示service结束,此时从待观察集合里面取出当前这个service并从待观察列表里面移除,然后观察这个service对象的回收情况。

Leakcanary对象泄漏检查

在被观察类型的生命周期的结束时,会调用到reachabilityWatcher.expectWeaklyReachable这个方法

我们跟进去,并找到实现类,来到ObjectWatcher里面,找到expectWeaklyReachable方法。

ObjectWatcher:
@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  //这边在前面初始化的时候默认传进来是true,因此会继续往下执行下面的代码
  if (!isEnabled()) {
    return
  }
  //这边会先先处理下,将被回收的对象从watchedObjects这个待观察的集合里面移除
  removeWeaklyReachableObjects()
  //创建一个随机的UUID,用来当做待观察对象的key,方便从watchedObjects这个map取值
  val key = UUID.randomUUID()
    .toString()
  // 获取当前的时间,自系统开机到现在的一个时间
  val watchUptimeMillis = clock.uptimeMillis()
  //创建弱引用对象,也就是WeakReference
  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
  //执行延时操作,默认延时5秒,去检查对象是否回收
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

在这个方法里面会先执行一次removeWeaklyReachableObjects(),将已被回收的对象从待观察的集合watchedObjects里面移除,然后创建弱引用对象,接着开启延时检查,默认等待5秒,使用的是mainHandler.postDelayed(it, retainedDelayMillis)去做延时的。

先看下removeWeaklyReachableObjects()这个方法:

private fun removeWeaklyReachableObjects() {
  // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
  // reachable. This is before finalization or garbage collection has actually happened.
  var ref: KeyedWeakReference?
  //通过迭代遍历的方式,判断queue里面时候有被回收的对象
  do {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      //如果对象被回收了 ,则讲弱引用从待观察的map里面移除
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

这个方法里面代码很少,这里面做的工作就是判断queue里面有没有被回收的对象,如果有则将改对象的弱引用从待观察的集合里面移除。

然后在来看下延时处理里面做了什么事:

@Synchronized private fun moveToRetained(key: String) {
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}

可以看到里面一开始又调用了一次removeWeaklyReachableObjects()方法。这边是第二次调用了,尝试移除这5秒内已经被回收的对象。如果此时对象仍没有被回收,也就是还在待观察集合里面,那么就开始进入到回调里面去,也就是OnObjectRetainedListener.onObjectRetained()里面去,这边这个onObjectRetainedListeners列表里面目前就只有一个, 它是在InternalLeakCanaryinvoke里面调用的。

AppWatcher
  -> manualIstall()
    -> LeakCanaryDelegate.loadLeakCanary(application)
      -> InternalLeakCanary
       -> invoke()
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    ...
    override fun invoke(application: Application) {
      _application = application
      checkRunningInDebuggableBuild()
      //添加监听,因为InternalLeakCanary实现了OnObjectRetainedListener接口,因此直接传this
      AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
      ...
    }
    ...
}

因此最终是回调到InternalLeakCanaryonObjectRetained()方法里面,然后在调用到scheduleRetainedObjectCheck()。最终进入到heapDumpTrigger.scheduleRetainedObjectCheck()方法里面

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    ...
    //回调到这个方法,然后又调用了scheduleRetainedObjectCheck
    override fun onObjectRetained() = scheduleRetainedObjectCheck()
    fun scheduleRetainedObjectCheck() {
      //先判断heapDumpTrigger是否有初始化了,
      if (this::heapDumpTrigger.isInitialized) {
        heapDumpTrigger.scheduleRetainedObjectCheck()
      }
    }
    ...
}

接下来在进入到heapDumpTrigger.scheduleRetainedObjectCheck()方法里面

fun scheduleRetainedObjectCheck(
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  //如果之前的子线程任务来没开始执行则返回,也就是就是下面的backgroundHandler.postDelayed
  if (checkCurrentlyScheduledAt > 0) {
    return
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  //让子线程延时去delayMillis执行,通过上面的调用链进入此方法则delayMillis为0,因此会马上执行
  backgroundHandler.postDelayed({
    //子线程任务开始执行,就将checkScheduledAt设置为0,以便下一次出现内存泄漏时能还能进来
    checkScheduledAt = 0
    checkRetainedObjects()
  }, delayMillis)
}

接下来在进入到checkRetainedObjects()这个方法里面去。由于上面是通过子线程Handler去post,因此该方法是运行在子线程里面

private fun checkRetainedObjects() {
  ...
  //看下此时待观察的集合里面还有多少个没有被分析的弱引用对象
  //也就是retainedUptimeMillis != -1L 的对象的个数
  //并且在调用retainedObjectCount.get()的时候,还会再调用一次removeWeaklyReachableObjects(),尝试再次移除一遍
  var retainedReferenceCount = objectWatcher.retainedObjectCount
  //如果此时还有泄漏对象,则调用gc,并重新再获取一次数量
  if (retainedReferenceCount > 0) {
    gcTrigger.runGc()
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }
  //这边判断应用是出于前台还是后台,
  //如果是在前台,则个数达到5个时才dump,如果应用在后台则只要有一个都会进行dump
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  //如果这个时间与上次dump的时间过短,则会重新调用scheduleRetainedObjectCheck,
  //并延迟执行,这个延迟的时间 = 60s - 时间差,避免两次dump的时间过短,影响使用
  if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
    onRetainInstanceListener.onEvent(DumpHappenedRecently)
    showRetainedCountNotification(
      objectCount = retainedReferenceCount,
      contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
    )
    scheduleRetainedObjectCheck(
      delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
    )
    return
  }
  dismissRetainedCountNotification()
  val visibility = if (applicationVisible) "visible" else "not visible"
  //这边开始进行dump
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}

在这个checkRetainedObjects()方法里面执行的步骤:

  • 先获取待观察集合里面被还没有被分析的疑似泄漏的弱引用对象的个数,通过retainedUptimeMillis != -1L这个判断,因为当对象被通过dump分析之后,会将retainedUptimeMillis这个时间改为-1。而且在objectWatcher.retainedObjectCount这个get的方法里面还会执行一次removeWeaklyReachableObjects(),尝试再次移除被回收的弱引用对象。
  • 如果此时还是有泄漏对象,也就是retainedReferenceCount > 0时,则会调用一次gcTrigger.runGc()进行一次GC,然后在重新获取下个数。
  • 接着判断应用是在前台还是后台,如果应用此时是在前台,则只有当个数达到5个时才进行dump。如果是在后台,则只要有一个就会进行dump。
  • 接下来在判断下当前时间距离上一次dump的时间,如果时间过短(小于60S),则不进行dump,而是重新调用scheduleRetainedObjectCheck并延时执行,延迟时间为:60秒 - 时间差。并且这边会返回,不再执行之后的dump
  • 最后执行dump,将内存泄漏的案发现场保存下来,并解析。

接下来来看下dumpHeap这个方法,这边就不分析dump的源码了,主要看下dump后执行操作。

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
    ...
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    ...
    //执行dump,并计算dump的时间
    durationMillis = measureDurationMillis {
      //执行dump,这边就不分析了,感兴趣可以自行点进去看下
      configProvider().heapDumper.dumpHeap(heapDumpFile)
    }
    ...
    //dump之后处理以分析的对象
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
}

这边注意下:objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)这个方法,上面我们说到当对象被通过dump分析之后,会将retainedUptimeMillis这个时间改为-1。现在我们来看下这个方法里面的源码:

/**
 * Clears all [KeyedWeakReference] that were created before [heapDumpUptimeMillis] (based on
 * [clock] [Clock.uptimeMillis])
 */
@Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
  //从待观察集合里面过滤出时间小于等于当前dump时间的弱引用
  val weakRefsToRemove =
    watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
  //遍历执行clear,将时间设置为-1,这个在下次触发执行checkRetainedObjects,里面不会重复判断
  weakRefsToRemove.values.forEach { it.clear() }
  //然后从待观察列表里面移除
  watchedObjects.keys.removeAll(weakRefsToRemove.keys)
}

这个方法里面会过滤出已经被dump过的弱引用对象,因为当它的watchUptimeMillis <= heapDumpUptimeMillis时,dump会将内存中当前时间点之前的疑似泄漏的对象都列举出来,所以只要是在当前dump时间点之前加入的都可以认为是已经dump了。

然后接下调用过滤出来的弱引用对象的claer()方法,将watchUptimeMillis时间设置为-1,并且从待观察集合里面移除。

总结

经过了上面的源码分析,接下来来总结下:

  • 使用ContentProvider进行自动初始化,在ContentProvider的onCreate()方法里面调用AppWatcher.manualInstall(application)进行LeakCanary的初始化;
  • AppWatcher.manualInstall(application)里面先做了一些合法性校验,然后初始化初始化了InternalLeakCanary和构建了4种生命周期监听器Watcher,分别是Activity、Fragment、Service和RootView。
  • 在构建4种Watcher时,顺便创建了objectWatcher,用于之后延时检查对象是否被回收。当被被观察的类型在生命周期进入结束时,会调用到objectWatcher.expectWeaklyReachable()方法里面。
  • objectWatcher.expectWeaklyReachable()方法里面会先将被回收的对象移除待观察集合,然后创建一个弱引用放入到待观察列表里面,最后在开启延时检查对象是否被回收,默认延时5秒。
  • 延时计时到了之后会调用objectWatcher.moveToRetained方法,在该方法内会再次尝试移除这5秒内已经被回收的对象,然后调用InternalLeakCanary.onObjectRetained()方法,跟着调用栈进入到heapDumpTrigger.scheduleRetainedObjectCheck()方法,最终到了checkRetainedObjects()这个方法里面。
  • checkRetainedObjects()这个方法里面,会先判断待观察结合里面是否还有疑似泄漏的弱引用对象,如果有则尝试进行一次GC操作。然后判断应用是在前台还是后台,如果实在前台则当疑似泄漏的弱引用对象数量达到5个时才进行dump。如果是在后台,则只要有一个就会进行dump。
  • 然后判断当前时间是否距离上次dump的时间小于60秒,如果小于60秒则重新进入到heapDumpTrigger.scheduleRetainedObjectCheck()方法,并延时开启任务,延时时间为:60秒 - 两次dump的时间差。
  • 最后进行dump,和dump文件的数据分析。dump完成后,会将待观察列表里面的已被分析过的弱引用对象的watchUptimeMillis时间设置为-1,并移除。

以上就是LeakCanary 2.9.1的源码解析,更多关于内存泄漏检测LeakCanary的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android中的LeakCanary的原理详解

    场景:最新的leakCanary2.8.1: debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' 原理:首先就是我们在引入最新的依赖包,什么都不用干了,因为他的初始化在清单文件中注册了contentProvider(),把初始化放到了这里面的onCreate()去初始化了,在初始化的过程中,他会用application监听观察对象activity.fragment等对象的生命周期的变化,当执行销毁的生命周期

  • 详解LeakCanary分析内存泄露如何实现

    目录 前言 LeakCanary的使用 LeakCanary原理 源码浅析 初始化 使用 总结 前言 平时我们都有用到LeakCanary来分析内存泄露的情况,这里可以来看看LeakCanary是如何实现的,它的内部又有哪些比较有意思的操作. LeakCanary的使用 官方文档:square.github.io/leakcanary/… 引用方式 dependencies { // debugImplementation because LeakCanary should only run i

  • Android LeakCanary检测内存泄露原理

    以LeakCanary2.6源码分析LeakCanary检测内存泄露原理,为减少篇幅长度,突出关键点,不粘贴大量源码,阅读时需搭配源码食用. 如何获取context LeakCanary只需引入依赖,不需要初始化代码,就能执行内存泄漏检测了,它是通过ContentProvider获取应用的context.这种获取context方式在开源第三方库中十分流行.如下AppWatcherInstaller在LeakCanary的aar包中manifest文件中注册. internal sealed cl

  • Android中LeakCanary检测内存泄漏的方法

    最近要对产品进行内存泄漏的检查,最后选择了使用Square公司开源的一个检测内存泄漏的函数库LeakCanary,在github上面搜索了一下竟然有1.6w个star,并且Android大神JakeWharton也是这个开源库的贡献者.那么就赶快拿来用吧. 先说一下我遇到的坑,我当时是直接google的,然后就直接搜索到稀土掘金的一篇关于LeakCanary的介绍,我就按照他们的文章一步步的操作,到最后才发现,他们那个build.gradle中导入的库太老了,会报这样的错误Closed Fail

  • Android性能优化之利用强大的LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来.所以决定抽空学习总结一下这方面的知识,以及分享一下我们是如何检测内存泄漏的.我们公司使用开源框架LeakCanary来检测内存泄漏. 什么是内存泄漏? 有些对象只有有限的生命周期.当它们的任务完成之后,它们将被垃圾回收.如果在对象的生命周期本该结束的时候,这个对象还被一系列的引用,这就会导致内存泄漏

  • Android 进阶实现性能优化之OOM与Leakcanary详解原理

    目录 Android内存泄漏常见场景以及解决方案 资源性对象未关闭 注册对象未注销 类的静态变量持有大数据 单例造成的内存泄漏 非静态内部类的静态实例 Handler临时性内存泄漏 容器中的对象没清理造成的内存泄漏 WebView 使用ListView时造成的内存泄漏 Leakcanary leakcanary 导入 leakcanary 是如何安装的 leakcanary 如何监听Activity.Fragment销毁 RefWatcher 核心原理 流程图 本文主要探讨以下几个问题: And

  • Android LeakCanary的使用方法介绍

    目录 1.LeakCanary 如何自动初始化 2.LeakCanary如何检测内存泄漏 2.1LeakCanary初始化时做了什么 2.2LeakCanary如何触发检测 2.3LeakCanary如何检测泄漏的对象 2.4弱引用 WeakReference 1.LeakCanary 如何自动初始化 LeakCanary只需添加依赖就可以实现自动初始化.LeakCanary是通过ContentProvider实现初始化的,在ContentProvider 的 onCreate方法中初始化Lea

  • Unix下C程序内存泄漏检测工具Valgrind的安装与使用详解

    Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Google-O'Reilly开源代码奖. Valgrind遵守GNU通用公共许可证条款,是一款自由软件. 官网http://www.valgrind.org 下载与安装#wget http://www.valgrind.org/downloads/valgrind-3.8.1.tar.bz2#tar x

  • nginx内存池源码解析

    目录 内存池概述 一.nginx数据结构 二.nginx向OS申请空间ngx_create_pool 三.nginx向内存池申请空间 四.大块内存的分配与释放 五.关于小块内存不释放 六.销毁和清空内存池 七.编译测试内存池接口功能 内存池概述 内存池是在真正使用内存之前,预先申请分配一定数量的.大小相等(一般情况下)的内存块留作备用.当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存. 内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的

  • skywalking源码解析javaAgent工具ByteBuddy应用

    目录 前言 Agent模块源码分析 第一步,加载配置信息: 第二步,加载需要被Agent的插件: 第三步,加载Agent端所需要的服务: 第四步,使用ByteBuddy增强插件定义的所有class: javaAgent的应用 BYTEBUDDY应用 通过委托实现Instrumentation 实现方法级别的安全性 实现安全功能的JAVAAGENT 前言 关于skywalking请看我上一篇博文,skywalking分布式服务调用链路追踪APM应用监控 其使用javaAgent技术,使得应用接入监

  • Android图片加载利器之Picasso源码解析

    看到了这里,相信大家对Picasso的使用已经比较熟悉了,本篇博客中将从基本的用法着手,逐步的深入了解其设计原理. Picasso的代码量在众多的开源框架中算得上非常少的一个了,一共只有35个class文件,但是麻雀虽小,五脏俱全.好了下面跟随我的脚步,出发了. 基本用法 Picasso.with(this).load(imageUrl).into(imageView); with(this)方法 public static Picasso with(Context context) { if

  • JetCache 缓存框架的使用及源码解析(推荐)

    目录 一.简介 为什么使用缓存? 使用场景 使用规范 二.如何使用 引入maven依赖 添加配置 配置说明 注解说明 @EnableCreateCacheAnnotation @EnableMethodCache @CacheInvalidate @CacheUpdate @CacheRefresh @CachePenetrationProtect @CreateCache 三.源码解析 项目的各个子模块 常用注解与变量 缓存API Cache接口 AbstractCache抽象类 Abstra

  • Netty分布式ByteBuf缓冲区分配器源码解析

    目录 缓冲区分配器 以其中的分配ByteBuf的方法为例, 对其做简单的介绍 跟到directBuffer()方法中 我们回到缓冲区分配的方法 然后通过validate方法进行参数验证 缓冲区分配器 顾明思议就是分配缓冲区的工具, 在netty中, 缓冲区分配器的顶级抽象是接口ByteBufAllocator, 里面定义了有关缓冲区分配的相关api 抽象类AbstractByteBufAllocator实现了ByteBufAllocator接口, 并且实现了其大部分功能 和AbstractByt

  • Pytorch搭建YoloV4目标检测平台实现源码

    目录 什么是YOLOV4 YOLOV4结构解析 1.主干特征提取网络Backbone 2.特征金字塔 3.YoloHead利用获得到的特征进行预测 4.预测结果的解码 5.在原图上进行绘制 YOLOV4的训练 1.YOLOV4的改进训练技巧 a).Mosaic数据增强 b).Label Smoothing平滑 c).CIOU d).学习率余弦退火衰减 2.loss组成 a).计算loss所需参数 b).y_pre是什么 c).y_true是什么. d).loss的计算过程 训练自己的YoloV4

  • vue3 keepalive源码解析解决线上问题

    目录 引言 1.keepalive功能 2.keepalive使用场景 3.在项目中的使用过程 4.vue3 keepalive源码调试 5.vue3 keealive源码粗浅分析 6.总结 引言 1.通过本文可以了解到vue3 keepalive功能 2.通过本文可以了解到vue3 keepalive使用场景 3.通过本文可以学习到vue3 keepalive真实的使用过程 4.通过本文可以学习vue3 keepalive源码调试 5.通过本文可以学习到vue3 keepalive源码的精简分

  • Mysql锁内部实现机制之C源码解析

    目录 概述 行锁结构 表锁结构 事务中锁的描述 概述 虽然现在关系型数据库越来越相似,但其背后的实现机制可能大相径庭.实际使用方面,因为SQL语法规范的存在使得我们熟悉多种关系型数据库并非难事,但是有多少种数据库可能就有多少种锁的实现方法. Microsoft Sql Server2005之前只提供页锁,直到2005版本才开始支持乐观并发.悲观并发,乐观模式下允许实现行级别锁,在Sql Server的设计中锁是一种稀缺资源,锁的数量越多,开销就越大,为了避免因为锁的数量快速攀升导致性能断崖式下跌

  • jq源码解析之绑在$,jQuery上面的方法(实例讲解)

    1.当我们用$符号直接调用的方法.在jQuery内部是如何封装的呢?有没有好奇心? // jQuery.extend 的方法 是绑定在 $ 上面的. jQuery.extend( { //expando 用于决定当前页面的唯一性. /\D/ 非数字.其实就是去掉小数点. expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), // Assume jQuery is ready wit

随机推荐

其他