LeakCanary源码解析

作为Android系统应用最广泛的内存泄漏管理工具,LeakCanary已经迭代了多个版本。它的设计思想很值得学习,其中应用到很多Android底层源码方面的知识。

开始前的思考

  1. 如何监控Activity、Fragment生命周期终结? ------ When
  2. 在监控到页面onDestroy()后,如何判断它的实例有没有被回收? ------ Who
  3. LeakCanary怎样实现自启动? ------ How

背景知识

首先补充JVM的相关知识引用和GC,缺少这两部分知识是无法继续展开讨论的。

强软弱虚四大引用

强引用

描述必需对象,只有当JVM停止运行时才会销毁。

软引用

描述非必需对象。

内存足够的话,GC后就不会回收。

应用场景大多为缓存,如Bitmap、Activity。

java 复制代码
val a = Object()
val softRef = SoftReference<Object>(a)
val a = softRef.get()
a = null
System.gc()
println("${softRef.get()}") // 非null

弱引用

GC时一定回收,不论内存是否充足。

java 复制代码
val s = "hello"
val weakRef = WeakReference<String>(s)
val ss = weakRef.get()
s = null
println("${weakRef.get()}") // 非null
System.gc() // System.gc() == Runtime.getRuntime().gc()
println("${weakRef.get()}") // null

虚引用

必须要结合引用队列ReferenceQueue共同使用。

跟没有被引用一样,即使不GC也会回收

通常用来跟踪对象被垃圾回收的活动。

java 复制代码
val queue = ReferenceQueue<String>()
val phantomRef = PhantomReference<String>("hello", queue)
println(phantomRef.get()) // null

引用队列

引用队列可以配合软、弱、虚引用使用,当引用的对象即将被JVM回收时,会将其加入引用队列中。

java 复制代码
private fun test() {
    val queue = ReferenceQueue<>()
    val s1 = "Hello"
    val s2 = "World"
    val weak1 = WeakReference(s1, queue)
    val weak2 = WeakReference(s2, queue)
    
    // 将s1、s2置空
    s1 = null
    s2 = null
    var weakRef = WeakReference<String>()
    while ((weakRef = queue.poll()) != null) { // 这一步不会输出任何内容
        println("已回收:$weakRef")
    }
    System.gc()
    while ((weakRef = queue.poll()) != null) { // 输出Hello World
        println("已回收:$weakRef")
    }

GC回收策略

Java虚拟机采用分代策略管理堆内存中的对象,分代的目的是优化GC性能 ,将具有不同生命周期 的对象归属于不同的年代,采取最适合它们的内存回收方式

JVM运行时内存 可以分为堆(Heap)非堆(Non-heap) 两大部分。堆在JVM启动时创建,是运行时数据区域 ,所有类实例数组 内存从堆分配。堆以外的内存称为非堆内存,方法区、类结构 等数据保存在非堆内存。简单说,堆是开发者可以触及的内存部分,非堆是JVM自留的部分

对象分代的角度,JVM内部将对象分为3类:

  • New Generation,新生代,位于堆内存,内部分为3块,其中EdenSurvivor的默认比例为8:1
    • Eden,新创建的对象都位于该分区,满时执行GC,并将仍存活的对象复制到From,GC后此区域被清空
    • From Survivor,GC时,将仍存活的对象复制到To
    • To Survivor,满时,将仍存活的对象复制到Old。GC之后会交换FromTo区,从而使新的To(也就是老的From)永远是空的
  • Old Generation,老年代,位于堆内存
  • Permanent Generation,永生代,位于非堆内存

GC分类

  • Minor GC,当Eden区满时触发,触发频率较高,回收New Generation。垃圾回收采用复制 算法,特点是简单高效 ,适用于存活对象较少 的情况,由于年轻代的对象生命周期较短 ,适用于复制算法进行快速垃圾回收。由于复制算法需要额外的空间 进行赋值操作,故处理大对象时会有一些额外开销
  • Full GC,回收Permanent, New & Old,对整个Heap区进行回收。不同的JVM实现Full GC时,可能采取不同的算法,常见的有标记-清除、标记-整理、分代收集 。如下原因会导致Full GC:
    • Old被写满
    • Permanent被写满
    • 显式调用System.gc() Runtime.getRuntime().gc() (两者等价)

GC Roots

定义:通过一系列名为GCRoots的对象作为起始点,从这个节点向下搜索,搜索走过的路径称为ReferenceChain,当一个对象到GCRoots没有任何ReferenceChain相连时,(图论:这个对象不可到达),则证明这个对象不可用。

共有4类GC Roots:

  • JavaStack中的引用的对象。
  • 方法区中静态引用指向的对象。
  • 方法区中常量引用指向的对象。
  • Native方法中JNI引用的对象。

Q1:监控生命周期

首先明确内存泄漏的概念,当对象不再使用后,理想的状况是把它占用的内存释放掉,以便其它对象新建时有充足的内存可以申请。如果连续内存不足以分配给新建的对象,就会导致OutOfMemory异常。

在Android中主要指Activity,因为Activity持有的对其他对象的引用众多,一旦Activity发生泄漏,造成的负面影响是巨大的。而且由于用户可以反复退出重进某一页面,会使泄漏的Activity不断增多。

因此,我们需要在Activity和Fragment发生onDestroy()后及时将其释放,避免泄漏。

监控Activity

先上结论,在Application.java中提供了ActivityLifecycleCallbacks的接口,通过Application.registerActivityLifecycleCallbacks()可以注册Activity生命周期的监听。

Application.java

java 复制代码
public interface ActivityLifecycleCallbacks {
    // onCreate
    default void onActivityPreCreated(@NonNull Activity activity,
            @Nullable Bundle savedInstanceState) {
    }
    void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
    default void onActivityPostCreated(@NonNull Activity activity,
            @Nullable Bundle savedInstanceState) {
    }
    // onStart
    default void onActivityPreStarted(@NonNull Activity activity) {
    }
    void onActivityStarted(@NonNull Activity activity);
    default void onActivityPostStarted(@NonNull Activity activity) {
    }
    // onResume
    default void onActivityPreResumed(@NonNull Activity activity) {
    }
    void onActivityResumed(@NonNull Activity activity);
    default void onActivityPostResumed(@NonNull Activity activity) {
    }
    // onPause
    default void onActivityPrePaused(@NonNull Activity activity) {
    }
    void onActivityPaused(@NonNull Activity activity);
    default void onActivityPostPaused(@NonNull Activity activity) {
    }
    // onStop
    default void onActivityPreStopped(@NonNull Activity activity) {
    }
    void onActivityStopped(@NonNull Activity activity);
    default void onActivityPostStopped(@NonNull Activity activity) {
    }
    // onSaveInstance
    default void onActivityPreSaveInstanceState(@NonNull Activity activity,
            @NonNull Bundle outState) {
    }
    void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
    default void onActivityPostSaveInstanceState(@NonNull Activity activity,
            @NonNull Bundle outState) {
    }
    // onDestroy
    default void onActivityPreDestroyed(@NonNull Activity activity) {
    }
    void onActivityDestroyed(@NonNull Activity activity);
    default void onActivityPostDestroyed(@NonNull Activity activity) {
    }
}

可以看到,对于每个生命周期阶段,接口都提供了 pre-on-post 三个回调。解题思路就有了:

  1. onActtivityCreated()时,将Activity注册为弱引用,并关联到引用队列
  2. onActivityDestroyed()中触发GC,然后判断弱引用对象的get()是否为null,如果非null说明发生泄漏,使用引用队列帮助我们判断,如果对象发生回收,引用队列poll()会返回非空结果

registerActivityLifecycleCallbacks

注册时,将传入的callback放在名为mActivityLifecycleCallbacks的列表中,列表的类型是ArrayList,在插入时需要对对象上锁,防止并发问题。

Application.java

java 复制代码
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
        new ArrayList<ActivityLifecycleCallbacks>();

public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
    synchronized (mActivityLifecycleCallbacks) {
        mActivityLifecycleCallbacks.add(callback);
    }
}

Activity.onDestroy()方法最后,会将destroy时间通知监听者,获取到callbacks列表后,遍历触发onActivityDestroyed()方法。

Activity.java

java 复制代码
protected void onDestroy() {
    ...
    dispatchActivityDestroyed(); // <--重点:通知监听
    notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
}

private void dispatchActivityDestroyed() {
    Object[] callbacks = collectActivityLifecycleCallbacks(); // <--重点:获取Application中的callbacks
    if (callbacks != null) {
        for (int i = callbacks.length - 1; i >= 0; i--) {
            ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityDestroyed(this);
        }
    }
    getApplication().dispatchActivityDestroyed(this);
}

监控Fragment

Activity.onCreate()中注册监听FramgentManager.registerFragmentLifecycleCallbacks(),函数实现位于FragmentManagerImpl类,同样是把callbacks加入到mLifecycleCallbacks列表。

FragmentManagerImpl.java

java 复制代码
@Override
public void registerFragmentLifecycleCallbacks(@NonNull FragmentLifecycleCallbacks cb,
                                               boolean recursive) {
    mLifecycleCallbacks.add(new FragmentLifecycleCallbacksHolder(cb, recursive));
}

与Activity略有不同的是,Fragment生命周期发生变化时,不论进入哪一个阶段,都会走统一的setState()函数,在setState()中根据下一阶段不同,分发给不同的监听者,从而触发监听者的onFragmentDestroyed()

java 复制代码
void dispatchOnFragmentDestroyed(@NonNull Fragment f, boolean onlyRecursive) {
    if (mParent != null) {
        FragmentManager parentManager = mParent.getFragmentManager();
        if (parentManager instanceof FragmentManagerImpl) {
            ((FragmentManagerImpl) parentManager)
                    .dispatchOnFragmentDestroyed(f, true);
        }
    }
    for (FragmentLifecycleCallbacksHolder holder : mLifecycleCallbacks) {
        if (!onlyRecursive || holder.mRecursive) {
            holder.mCallback.onFragmentDestroyed(this, f);
        }
    }
}

小结

以上就是Activity和Fragment的生命周期监听,通过设置相应的监听回调来实现。

Q2:在监控到页面onDestroy()后,如何判断它的实例有没有被回收? ------ Who

用一个弱引用去引用Activity实例,然后调用GC,如果get()返回null,说明它被回收了,没有泄漏。之所以使用弱引用,是为了让它与Activity在GC时同步被回收。

更进一步,引用队列可以更好地满足我们的需求,如果Activity/Fragment对象处于可回收的状态,会自动进入引用队列。

以Activity的监听过程为例,当发生onDestroy()时,会触发objectWatcher.watch()方法。而在ObjectWatcher类中,就是通过引用队列来判断Activity对象是否进行回收的。

ActivityDestroyWatcher.kt

java 复制代码
private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityDestroyed(activity: Activity) {
      if (configProvider().watchActivities) {
        objectWatcher.watch(
            activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }
  }

开始监听时会把Activity放入到watchList里,在onDestroy()时如果对象已经/可以被回收(位于引用队列),将其从watchList移除。这样剩下来的对象就是发生了泄漏。

当弱引用对象的状态已经处于"可回收"时,无需经过gc,就会将它加入到引用队列中。

java 复制代码
@Synchronized fun watch(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  removeWeaklyReachableObjects() // <--重点:找出已经加入引用队列的对象(可以被回收),然后将其从watchList中移除,这样watchList中剩下的就是还没有被回收的对象
  val key = UUID.randomUUID()
      .toString()
  val watchUptimeMillis = clock.uptimeMillis()
  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 {
    moveToRetained(key)
  }
}

private fun removeWeaklyReachableObjects() {
  // 重点:注意下面一段注释,翻译过来是:只要对象可以回收,无须经过gc,它就会被放进在引用队列中
  // 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?
  do {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

Q3:自动化启动

2.0之前需要手动启动,2.0之后自动。

Before2.0,手动

Application.java

java 复制代码
// before 2.0
public void onCreate() {
    super.onCreate()
    LeakCanary.install(this);
}

After2.0,自动

2.0之后通过ContentProvider实现自动注册,在《AMS源码分析》文中,介绍过创建Application过程中会自动加载ContentProvider。LeakCanary就是借助了这一点来实现的。

首先在Manifest文件中声明自身的provider。

Manifest.xml

xml 复制代码
<application>
    <provider
        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
        android:authorities="${applicationId}.leakcanary-installer"
        android:enabled="@bool/leak_canary_watcher_auto_install"
        android:exported="false" />
</application>

AppWatcherInstaller负责创建Activity、Fragment监听,它的onCreate()函数在集成APP时会自动调用。

java 复制代码
override fun onCreate(): Boolean {
  val application = context!!.applicationContext as Application
  AppWatcher.manualInstall(application) // <--重点:初始化
  return true
}

InternalAppWatcher.java

java 复制代码
fun install(application: Application) {
  checkMainThread()
  if (this::application.isInitialized) {
    return
  }
  InternalAppWatcher.application = application
  if (isDebuggableBuild) {
    SharkLog.logger = DefaultCanaryLog()
  }

  val configProvider = { AppWatcher.config }
  ActivityDestroyWatcher.install(application, objectWatcher, configProvider) // <--注册Activity监听
  FragmentDestroyWatcher.install(application, objectWatcher, configProvider) // <--注册Fragment监听
  onAppWatcherInstalled(application)
}

Activity注册监听

伴生对象(静态函数)中,对Application对象调用registerActivityLifecycleCallbacks()函数,完成Activity监听注册。

ActivityDestroyWatcher.kt

java 复制代码
companion object {
  fun install(
    application: Application,
    objectWatcher: ObjectWatcher,
    configProvider: () -> Config
  ) {
    val activityDestroyWatcher =
      ActivityDestroyWatcher(objectWatcher, configProvider)
    application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
  }
}

Fragment注册监听

FragmentDestroyWatcher.kt

java 复制代码
fun install(
  application: Application,
  objectWatcher: ObjectWatcher,
  configProvider: () -> AppWatcher.Config
) {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

  if (SDK_INT >= O) { // <--不低于26
    fragmentDestroyWatchers.add(
        AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
    )
  }

  getWatcherIfAvailable(
      ANDROIDX_FRAGMENT_CLASS_NAME,
      ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      objectWatcher,
      configProvider
  )?.let {
    fragmentDestroyWatchers.add(it)
  }

  getWatcherIfAvailable(
      ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
      ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      objectWatcher,
      configProvider
  )?.let {
    fragmentDestroyWatchers.add(it)
  }

  if (fragmentDestroyWatchers.size == 0) {
    return
  }

  application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityCreated(
      activity: Activity,
      savedInstanceState: Bundle?
    ) { // <--当Activity.onCreate()时,注册Fragment监听
      for (watcher in fragmentDestroyWatchers) {
        watcher(activity)
      }
    }
  })
}

以上就是Activity、Fragment生命周期监听的自动化注册过程,Fragment要比Activity多一步,无法直接通过Application进行注册。

总结

LeakCanary工作流程图可以总结如下,另外还有Dump & Analyze Heap的部分,不属于本文讨论范畴。

参考资料

相关推荐
Tans57 天前
LeakCanary 源码阅读笔记(四)
源码阅读·leakcanary
Just_Paranoid5 个月前
Android性能优化之内存泄漏优化(工具篇)
android·性能优化·leakcanary·mat·profiler
很好奇1 年前
【内存泄漏】图解 Android 内存泄漏
android·leakcanary
xiaolong6661 年前
LeakCanary浅析
leakcanary
小海编码日记1 年前
从LeakCanary看Fragment生命周期监控
android·android jetpack·leakcanary
派大星不吃蟹1 年前
面试问到:LeakCanray 2.0为啥不需要在application里调install?
android·leakcanary