Android-性能优化-02-内存优化-LeakCanary 源码解析

153 阅读19分钟

LeakCanary 是一个轻量级、易用的 Android 内存泄露检测库,其源码设计清晰且架构合理。以下是 LeakCanary 源码的详细解析,从核心模块到关键流程。


1. 源码结构概览

LeakCanary 的源码主要分为以下几个核心模块:

(1) LeakCanary Entry Point

  • AppWatcher:初始化入口,监控组件(如 Activity、Fragment)的生命周期。
  • ObjectWatcher:核心类,用于监控对象并检测是否发生泄露。

(2) 内存泄露检测

  • HeapAnalyzer:用于解析堆转储文件(.hprof)。
  • Shark:LeakCanary 内置的高效堆分析引擎。

(3) 内存泄露报告

  • HeapAnalysis:堆分析结果的封装类,包含泄露对象的引用链等信息。
  • HeapAnalysisSuccess / HeapAnalysisFailure:分别表示堆分析成功或失败的结果。

2. 初始化流程

LeakCanary 的入口在 AppWatcher 类。通过 AppWatcher,LeakCanary 自动注册生命周期监听器并监控特定对象。

2.1 AppWatcher 初始化

LeakCanary 不需要手动写代码初始化,只需要在 gradle 中添加依赖就好了,然后 App 在启动时就会自动运行,年轻的时候我也非常好奇是怎么实现的,其实就是通过 ContentProvider 实现的,它可能是存在感最低的四大组建,但是 ContentProvider 他有一个特点,在 App 初始化的过程中也会初始化 ContentProvider,这部分代码我就不细说了,感兴趣的同学可去网上找找相关的资料。
LeakCanary 对应的初始化的 ContentProvider 是 MainProcessAppWatcherInstaller , 对应的 manifest 配置如下:

<application>
  <provider
      android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
      android:authorities="${applicationId}.leakcanary-installer"
      android:enabled="@bool/leak_canary_watcher_auto_install"
      android:exported="false"/>
</application>

然后我们再看看 MainProcessAppWatcherInstaller 的源码:

Kotlin
代码解读
复制代码
internal class MainProcessAppWatcherInstaller : ContentProvider() {

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

  override fun query(
    uri: Uri,
    projectionArg: Array<String>?,
    selection: String?,
    selectionArgs: Array<String>?,
    sortOrder: String?
  ): Cursor? = null

  override fun getType(uri: Uri): String? = null

  override fun insert(uri: Uri, contentValues: ContentValues?): Uri? = null

  override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0

  override fun update(
    uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?
  ): Int = 0
}

在启动的时候调用了 AppWatcher#manualInstall() 方法,其他的都是模版代码。
继续看看 AppWatcher#manualInstall() 方法的实现:

Kotlin
代码解读
复制代码

@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  // 默认的 Watchers
  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"
  }
  this.retainedDelayMillis = retainedDelayMillis
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // 初始化 LeakCanaryDelegate,这个对象非常重要
  // Requires AppWatcher.objectWatcher to be set
  LeakCanaryDelegate.loadLeakCanary(application)

  // 安装每个 Watcher
  watchersToInstall.forEach {
    it.install()
  }
  // Only install after we're fully done with init.
  installCause = RuntimeException("manualInstall() first called here")
}

AppWatcher#manualInstall()主要初始化两个东西:一个是 LeakCanaryDelegate;另外一个是各种 WatcherWatcher 就是用来监控各种已经进入 destroy 生命周期的 Android 组件;当 Watcher 发现组建已经 destory 了,但是它所对应的 Java 实例并没有被销毁就表示当前已经出现了泄漏,Watcher 就会通知 LeakCanaryDelegate 去 dump 内存,并分析内存,找到泄漏对象的 gc root,锁定导致对象泄漏的真正凶手。

本篇文章主要分析 Watchers,后面再分析 LeakCanaryDelegate

通过 appDefaultWatchers() 方法获取默认的 Watchers

Kotlin
代码解读
复制代码

fun appDefaultWatchers(
  application: Application,
  deletableObjectReporter: DeletableObjectReporter = objectWatcher.asDeletableObjectReporter()
): List<InstallableWatcher> {
  // Use app context resources to avoid NotFoundException
  // https://github.com/square/leakcanary/issues/2137
  val resources = application.resources
  val watchDismissedDialogs = resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
  return listOf(
    ActivityWatcher(application, deletableObjectReporter),
    FragmentAndViewModelWatcher(application, deletableObjectReporter),
    RootViewWatcher(deletableObjectReporter, WindowTypeFilter(watchDismissedDialogs)),
    ServiceWatcher(deletableObjectReporter)
  )
}

分别包含以下几种 Watchers

  • ActivityWatcher:监控 Activity 的销毁
  • FragmentAndViewModelWatcher:监控 Fragment 和 ViewModel 的销毁
  • RootViewWatcher:监控 Window 中的 RootView 的销毁
  • ServiceWatcher:监控 Service 的销毁

Watcher 会把已经处于 destroy 的组件通知 DeletableObjectReporter,后面我们再分析这个对象。
我们先分析各种功能的 Watchers

2.2 监听器注册

监控 Activity 的销毁

我们直接看 ActivityWatcher 源码:

Kotlin
代码解读
复制代码
class ActivityWatcher(
  private val application: Application,
  private val deletableObjectReporter: DeletableObjectReporter
) : InstallableWatcher {

  // Kept for backward compatibility.
  constructor(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher
  ) : this(application, reachabilityWatcher.asDeletableObjectReporter())

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        deletableObjectReporter.expectDeletionFor(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

因为 Android 本身就提供了监控 Activity 的生命周期的方法,所以上面的代码实现非常简单,当 Activity destroy 后会通知 DeletableObjectReporter#expectDeletionFor() 方法,所有的组件销毁后都会调用这个方法。这里还有一个非常有意思的点就是通过 noOpDelegate() 方法干掉了 Java 中接口中需要强制实现的方法,我前面写文章介绍过:Kotlin 干掉接口需要强制实现的方法

监控 Fragment 和 ViewModel 的销毁

直接看 FragmentAndViewModelWatcher#install() 方法:

Kotlin
代码解读
复制代码

// ...

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)
}

// ...

监听 Activity 的生命周期,当 Activity 创建时依次通知 fragmentDestroyWatchers,我们来看看 fragmentDestroyWatchers 的实现:

Kotlin
代码解读
复制代码

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

  // Android 系统默认的 Fragment Watcher
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      AndroidOFragmentDestroyWatcher(deletableObjectReporter)
    )
  }

  // Androidx 的 Fragment Watcher
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME,
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    deletableObjectReporter
  )?.let {
    fragmentDestroyWatchers.add(it)
  }

  // TODO turn this off by default.
  // Support 的 Fragment Watcher.
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    deletableObjectReporter
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  fragmentDestroyWatchers
}

fragment 的监控分为三种,系统的 fragmentandroidx 的 fragment 和 support 的 fragment,我们只分析 androidx 的 fragment 就好,其他两种大家自行分析。

androidx 的 fragment 的监控类是 AndroidXFragmentDestroyWatcher,我们来看看它的入口函数:

Kotlin
代码解读
复制代码
override fun invoke(activity: Activity) {
  if (activity is FragmentActivity) {
    val supportFragmentManager = activity.supportFragmentManager
   //监控 Fragment 销毁
   supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
   // 监控 ViewModel 销毁
    ViewModelClearedWatcher.install(activity, deletableObjectReporter)
  }
}

这里分为两块儿逻辑,一是监控 fragment 的销毁,二是监控 ViewModel 的销毁,ViewModel 又分为 fragment 和 activity 的 ViewModel

先来看看 fragment 销毁的监控:

Kotlin
代码解读
复制代码
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

  override fun onFragmentCreated(
    fm: FragmentManager,
    fragment: Fragment,
    savedInstanceState: Bundle?
  ) {
    // 监控 ViewModel 的销毁
    ViewModelClearedWatcher.install(fragment, deletableObjectReporter)
  }

  override fun onFragmentViewDestroyed(
    fm: FragmentManager,
    fragment: Fragment
  ) {
    // 监控 Fragment 对应的 view 的销毁
    val view = fragment.view
    if (view != null) {
      deletableObjectReporter.expectDeletionFor(
        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 的销毁
    deletableObjectReporter.expectDeletionFor(
      fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
    )
  }
}

上面分为三块逻辑,分别是:执行监控 fragment 的 ViewModel,监控 fragment 的 View 的销毁,监控 fragment 的销毁。

无论是 Activity 还是 Fragment 的 ViewModel 销毁监控,都在他们创建的时候调用了 ViewModelClearedWatcher.install() 方法,我们来看看它的实现:

Kotlin
代码解读
复制代码
fun install(
  storeOwner: ViewModelStoreOwner,
  deletableObjectReporter: DeletableObjectReporter
) {
  val provider = ViewModelProvider(storeOwner, object : Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T =
      ViewModelClearedWatcher(storeOwner, deletableObjectReporter) as T
  })
  provider.get(ViewModelClearedWatcher::class.java)
}

Activity 和 Fragment 他们都是 ViewModelStoreOwner,上面的代码是创建了一个 ViewModel,实现类是 ViewModelClearedWatcher

Kotlin
代码解读
复制代码
// ...
// We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
// however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
// does not have ViewModelStore#keys. All versions currently have the mMap field.
private val viewModelMap: Map<String, ViewModel>? = try {
  val storeClass = ViewModelStore::class.java
  val mapField = try {
    storeClass.getDeclaredField("map")
  } catch (exception: NoSuchFieldException) {
    // Field name changed from mMap to map with Kotlin conversion
    // https://cs.android.com/androidx/platform/frameworks/support/+/8aa6ca1c924ab10d263b21b99b8790d5f0b50cc6
    storeClass.getDeclaredField("mMap")
  }
  mapField.isAccessible = true
  @Suppress("UNCHECKED_CAST")
  mapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
  SharkLog.d(ignored) { "Could not find ViewModelStore map of view models" }
  null
}

override fun onCleared() {
  viewModelMap?.values?.forEach { viewModel ->
    deletableObjectReporter.expectDeletionFor(
      viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
    )
  }
}
// ...

当 ViewModel 被销毁的时候,ViewModelStoreOwner 会回调 ViewModel 的 onCleared() 方法,这里它通过反射的方式去到 ViewModelStore 去拿所有的 ViewModel,对应的成员变量是 map 或者 mMap。 然后通知 DeletableObjectReporter 对应的 ViewModelStoreOwner 的所有的 ViewModel 都已经被销毁。

RootView 销毁监控

RootView 的销毁监控这段代码我认为有很大的学习价值,首先 Android 并没有直接提供 API 供我们监控 RootView 的创建和销毁。有人可能会懵逼,RootView 是什么?你所见到的所有的 UI,都会通过 WindowManager 添加,然后通过 binder 通知 WMS,最终完成 UI 的绘制。无论是 Activity (Fragment 是在 Activity 的 View 树中),Dialog 还是 PopupWindow,而且还包括自定义的悬浮窗。除了 Activity 之外,其他的 UI 组件都是没有提供全局监控的 API。但是我们通过监控 RootView 就能够做到全局监控这些组件。

为了更好的理解 RootView 监控的这段代码,先看看在 WindowManager 中添加 View 的这段代码。(基于 API 35)

先看看 Activity#getSystemService() 方法:

Java
代码解读
复制代码
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
    if (getBaseContext() == null) {
        throw new IllegalStateException(
                "System services not available to Activities before onCreate()");
    }

    if (WINDOW_SERVICE.equals(name)) {
        return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    return super.getSystemService(name);
}

我们看到如果是需要 WindowManager,直接返回的是 mWindowManager 的成员变量。

那么 mWindowManager 是在什么时候初始化的呢?我直接给结论,ActivityThread 收到 AMS 通过 binder 发送过来需要启动 Activity 时,这个时候会通过反射的方式去创建 Activity 实例,然后立即会调用 Activity#attach() 方法,在这个方法中就会完成 mWindowManager 的初始化,我们来看看这个方法:

Java
代码解读
复制代码
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
        IBinder shareableActivityToken, IBinder initialCallerInfoAccessToken) {
    // ...

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(mWindowControllerCallback);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    
    // ...

    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    
    // ...
}

我们看到 mWindowManager 是通过 PhoneWindow#getWindowManager() 方法获取的,前面还有一个关键的方法 PhoneWindow#setWindowManager(),这里又调用了参数中的 context#getSystemService() 方法获取 WindowManager。那这个 Context 是什么呢??这个是 ActivityThread 会为每一个 Activity 创建一个 ContextImpl,它就是 Activity 中的 baseContext

我们再来看看 ContextImpl#getSystemService() 方法的实现:

Java
代码解读
复制代码
@Override
public Object getSystemService(String name) {
    // ...
    return SystemServiceRegistry.getSystemService(this, name);
}

这里调用了 SystemServiceRegistry#getSystemService() 方法,在这里就注册了所有的我们需要用的 Service。我们继续看源码:

Java
代码解读
复制代码
public static Object getSystemService(@NonNull ContextImpl ctx, String name) {
    final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
    if (fetcher == null) {
        return null;
    }

    final Object ret = fetcher.getService(ctx);
    // ...
    return ret;
}

首先是获取到对应 Service 的 featcher,然后获取对应 Service 的实例,这个 fetcher 是在 SystemServiceRegistry 的 static 代码块注册的。我们来看看这块代码:

Java
代码解读
复制代码
static {
// ...
registerService(Context.WINDOW_SERVICE, WindowManager.class,
        new CachedServiceFetcher<WindowManager>() {
    @Override
    public WindowManager createService(ContextImpl ctx) {
        return new WindowManagerImpl(ctx);
    }});
// ...    
}

这里注册了所有的 Android 的 Service,如果你需要看其他的 Service 的源码,也来这里找就好了。WindowManager 的实现类是 WindowManagerImpl

我们再来看看 PhoneWindow#setWindowManager() 方法:

Java
代码解读
复制代码
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated;
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

然后我们看到 Activity 中使用的 mWindowManager 其实是调用 WindowManagerImpl#createLocalWindowManager() 生成的:

Java
代码解读
复制代码
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
}

WindowManagerImpl 其实是和 Activity 一一对应的,只是上面的方法添加了一个 parentWindow,也就是 PhoneWindow,他这样来表示 WindowManager 的子窗口的关系。顶级的 WindowManagerImpl 他的 parentWindow 就是空的。

我们要展示 UI 就需要调用 WindowManagerImpl#addView() 方法,我们来看看它的实现:

Java
代码解读
复制代码
// ...

@UnsupportedAppUsage
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
// ...

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyTokens(params);
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());
}
// ...

我们看到这里是调用了 WindowManagerGlobal#addView() 方法,WindowManagerGlobal 实例的获取是通过 getInstance() 方法:

Java
代码解读
复制代码
@UnsupportedAppUsage
public static WindowManagerGlobal getInstance() {
    synchronized (WindowManagerGlobal.class) {
        if (sDefaultWindowManager == null) {
            sDefaultWindowManager = new WindowManagerGlobal();
        }
        return sDefaultWindowManager;
    }
}

从这里可以看出 WindowManagerGlobal 也是单例的。

继续看看 WindowManagerGlobal#addView() 方法的实现:

Java
代码解读
复制代码

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    
    // ...
    synchronized (mLock) {
        // ...

        if (windowlessSession == null) {
            root = new ViewRootImpl(view.getContext(), display);
        } else {
            root = new ViewRootImpl(view.getContext(), display,
                    windowlessSession, new WindowlessWindowLayout());
        }

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        
        // ...
    }
}

我这里省略了部分逻辑,我们看到 view 被添加到成员变量 mViews 中,这里还会创建一个 ViewRootImpl 实例,并被添加到 mRoots 变量中。

Android 的源码我们就看到这里,到这里我们知道所有的 UI 的 root view 都被添加到了 WindowManagerGlobal#mView 中,而它也是一个单例.LeakCanary 也是反射的这个变量,我们来看 LeakCanary 的代码。

首先看 RootViewWatcher#install() 方法:

Kotlin
代码解读
复制代码

class WindowTypeFilter(private val watchDismissedDialogs: Boolean) : Filter {
  override fun shouldExpectDeletionOnDetached(rootView: View): Boolean {
    return when (rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          // Activities are already tracked by ActivityWatcher
          is Activity -> false
          is Dialog -> watchDismissedDialogs
          // Probably a DreamService
          else -> true
        }
      }
      // Android widgets keep detached popup window instances around.
      POPUP_WINDOW -> false
      TOOLTIP, TOAST, UNKNOWN -> true
    }
  }
}

private val listener = OnRootViewAddedListener { rootView ->
  if (rootViewFilter.shouldExpectDeletionOnDetached(rootView)) {
    rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

      val watchDetachedView = Runnable {
        deletableObjectReporter.expectDeletionFor(
          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
}

首先在 Curtains 中添加对 root view 添加时的监听,当有 root view 被添加时来通过 WindowTypeFilter 来过滤需要处理的 root view,如果需要处理的话,会想 root view 中添加一个 attach/detach 的监听,当 root view 被移除时就表示已经回收了,然后通知 DeletableObjectReporter

我们再简单看看他是怎么判断 root view 是属于哪个组件的:

Kotlin
代码解读
复制代码
class WindowTypeFilter(private val watchDismissedDialogs: Boolean) : Filter {
  override fun shouldExpectDeletionOnDetached(rootView: View): Boolean {
    return when (rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          // Activities are already tracked by ActivityWatcher
          is Activity -> false
          is Dialog -> watchDismissedDialogs
          // Probably a DreamService
          else -> true
        }
      }
      // Android widgets keep detached popup window instances around.
      POPUP_WINDOW -> false
      TOOLTIP, TOAST, UNKNOWN -> true
    }
  }
}


val View.windowType: WindowType
  get() {
    val rootView = rootView
    if (WindowSpy.attachedToPhoneWindow(rootView)) {
      return PHONE_WINDOW
    }
    val windowLayoutParams = rootView.layoutParams as? WindowManager.LayoutParams
    return if (windowLayoutParams == null) {
      UNKNOWN
    } else {
      val title = windowLayoutParams.title
      when {
        title == "Toast" -> TOAST
        title == tooltipString -> TOOLTIP
        // App compat tooltip uses the class simple name.
        title == "TooltipPopup" -> TOOLTIP
        title.startsWith("PopupWindow:") -> POPUP_WINDOW
        else -> UNKNOWN
      }
    }
  }
  

private val decorViewClass by lazy(NONE) {
  val sdkInt = Build.VERSION.SDK_INT
  val decorViewClassName = when {
    sdkInt >= 24 -> "com.android.internal.policy.DecorView"
    sdkInt == 23 -> "com.android.internal.policy.PhoneWindow$DecorView"
    else -> "com.android.internal.policy.impl.PhoneWindow$DecorView"
  }
  try {
    Class.forName(decorViewClassName)
  } catch (ignored: Throwable) {
    Log.d(
      "WindowSpy", "Unexpected exception loading $decorViewClassName on API $sdkInt", ignored
    )
    null
  }
} 

fun attachedToPhoneWindow(maybeDecorView: View): Boolean {
  return decorViewClass?.let { decorViewClass ->
    decorViewClass.isInstance(maybeDecorView)
  } ?: false
}

首先判断 root view 是否是通过 PhoneWindow 添加,判断方法是检查 root view 的实现类是不是 DecorView,如果是通过 PhoneWindow 添加又分为两种情况,一种是 Activity 一种是 Dialog,他们俩的区分方式是通过 wrappedCallback 来判断。
其他的情况就通过 LayoutParams 的 title 来判断,具体看代码。

继续看看 Curtains.onRootViewsChangedListeners 是如何监听 root view 的添加的:

Kotlin
代码解读
复制代码

private val rootViewsSpy by lazy(NONE) {
  RootViewsSpy.install()
}

@JvmStatic
val onRootViewsChangedListeners: MutableList<OnRootViewsChangedListener>
  get() {
    return rootViewsSpy.listeners
  }

这里的又是从 RootViewSpy 中获取的,RootViewSpy#install() 方法返回了 RootViewSpy 实例。

Kotlin
代码解读
复制代码
internal class RootViewsSpy private constructor() {

  val listeners = CopyOnWriteArrayList<OnRootViewsChangedListener>()

  fun copyRootViewList(): List<View> {
    return if (Build.VERSION.SDK_INT >= 19) {
      delegatingViewList.toList()
    } else {
      WindowManagerSpy.windowManagerMViewsArray().toList()
    }
  }

  private val delegatingViewList = object : ArrayList<View>() {
    override fun add(element: View): Boolean {
      listeners.forEach { it.onRootViewsChanged(element, true) }
      return super.add(element)
    }

    override fun removeAt(index: Int): View {
      val removedView = super.removeAt(index)
      listeners.forEach { it.onRootViewsChanged(removedView, false) }
      return removedView
    }
  }

  companion object {
    fun install(): RootViewsSpy {
      return RootViewsSpy().apply {
        WindowManagerSpy.swapWindowManagerGlobalMViews { mViews ->
          delegatingViewList.apply { addAll(mViews) }
        }
      }
    }
  }
}

我们看到 RootViewSpy 创建的时候又调用了 WindowManagerSpy.swapWindowManagerGlobalMViews() 方法,这个方法中就把上面我们提到的 WindowManagerGlobal 中的成员变量 mViews 替换成了 delegatingViewList,然后 delegatingViewList 是继承于 ArrayList 不过在它的 add() 和 removeAt() 中添加了 listener 的回调。

继续看 WindowManagerSpy.swapWindowManagerGlobalMViews() 是如何完成 hook 的。

Kotlin
代码解读
复制代码

private val windowManagerClass by lazy(NONE) {
  val className = if (SDK_INT > 16) {
    "android.view.WindowManagerGlobal"
  } else {
    "android.view.WindowManagerImpl"
  }
  try {
    Class.forName(className)
  } catch (ignored: Throwable) {
    Log.w("WindowManagerSpy", ignored)
    null
  }
}

private val windowManagerInstance by lazy(NONE) {
  windowManagerClass?.let { windowManagerClass ->
    val methodName = if (SDK_INT > 16) {
      "getInstance"
    } else {
      "getDefault"
    }
    windowManagerClass.getMethod(methodName).invoke(null)
  }
}

private val mViewsField by lazy(NONE) {
  windowManagerClass?.let { windowManagerClass ->
    windowManagerClass.getDeclaredField("mViews").apply { isAccessible = true }
  }
}

@SuppressLint("PrivateApi", "ObsoleteSdkInt", "DiscouragedPrivateApi")
fun swapWindowManagerGlobalMViews(swap: (ArrayList<View>) -> ArrayList<View>) {
  if (SDK_INT < 19) {
    return
  }
  try {
    windowManagerInstance?.let { windowManagerInstance ->
      mViewsField?.let { mViewsField ->
        @Suppress("UNCHECKED_CAST")
        val mViews = mViewsField[windowManagerInstance] as ArrayList<View>
        mViewsField[windowManagerInstance] = swap(mViews)
      }
    }
  } catch (ignored: Throwable) {
    Log.w("WindowManagerSpy", ignored)
  }
}

上面的代码也是朴实无华,就不多说了。

Service 销毁监控

同样的 Android 也没有提供 API 去全局监控 Service 的生命周期,又只有用 hook 的方法了。

Service 我们在开发中使用的量不是很多,我们就简单看看吧,就不像 root view 一样讲得那么详细了。它的 hook 分为两块儿。
首先是 hook ActivityThread 的命名为 mH 的 Handler 成员变量。然后 hook Hander 的 callback,在收到 STOP_SERVICE 时就表示 Service 要被销毁了,然后对应的 msg 的 waht 就是对应 Service 的 token,然后再 hook ActivityThread 中的 mServices 成员变量,它表示当前所有存活的 Service,通过上面的 token 找到对应的 Service,然后把它添加到即将销毁的 Service 集合中。

我们来看看这部分代码:

Kotlin
代码解读
复制代码
// Hook ActivityThread#mServices 拿到所有存活的 Services
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() {
  // ... 
  
  try {
    
    // Hook ActivityThread#mH 中的 mCallback
    swapActivityThreadHandlerCallback { mCallback ->
      uninstallActivityThreadHandlerCallback = {
        swapActivityThreadHandlerCallback {
          mCallback
        }
      }
      // 替换的新的 Callback
      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 销毁的消息
        if (msg.what == STOP_SERVICE) {
          val key = msg.obj as IBinder
          activityThreadServices[key]?.let {
            // 将即将被销毁的 Service 添加集合中
            onServicePreDestroy(key, it)
          }
        }
        mCallback?.handleMessage(msg) ?: false
      }
    }
    // ...
  } catch (ignored: Throwable) {
    SharkLog.d(ignored) { "Could not watch destroyed services" }
  }
}


private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }

private val activityThreadInstance by lazy {
  activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
}

private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) {
  val mHField =
    activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
  val mH = mHField[activityThreadInstance] as Handler

  val mCallbackField =
    Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
  val mCallback = mCallbackField[mH] as Handler.Callback?
  mCallbackField[mH] = swap(mCallback)
}


private fun onServicePreDestroy(
  token: IBinder,
  service: Service
) {
  servicesToBeDestroyed[token] = WeakReference(service)
}

另外一块儿 hook 就是替换 ActivityManager,通过动态代理生成一个新的 ActivityManager 对象,所有的方法处理还是原来的 ActivityManager,只是用来监控 ActivityManager#serviceDoneExecuting() 方法的调用,这个方法调用了就表示 Service 已经寿终正寝了,然后从上面获取到即将被销毁的 Service 集合中去查找这个 Service,然后回调 Service 已经销毁给 DeletableObjectReporter

继续看代码:

Java
代码解读
复制代码

override fun install() {
  // ...
  try {
    
    // ...
    // Hook ActivityManager.
    swapActivityManager { activityManagerInterface, activityManagerInstance ->
      uninstallActivityManager = {
        swapActivityManager { _, _ ->
          activityManagerInstance
        }
      }
      // 动态代理一个新的 ActivityManager
      Proxy.newProxyInstance(
        activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
      ) { _, method, args ->
        if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
          // 这里表示对应的 Service 已经销毁
          val token = args!![0] as IBinder
          if (servicesToBeDestroyed.containsKey(token)) {
            // 通知 Service 已经销毁
            onServiceDestroyed(token)
          }
        }
        // 使用原来的 ActivityManager 实现
        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" }
  }
}


@SuppressLint("PrivateApi")
private fun swapActivityManager(swap: (Class<*>, Any) -> Any) {
  val singletonClass = Class.forName("android.util.Singleton")
  val mInstanceField =
    singletonClass.getDeclaredField("mInstance").apply { isAccessible = true }

  val singletonGetMethod = singletonClass.getDeclaredMethod("get")

  val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    "android.app.ActivityManager" to "IActivityManagerSingleton"
  } else {
    "android.app.ActivityManagerNative" to "gDefault"
  }

  val activityManagerClass = Class.forName(className)
  val activityManagerSingletonField =
    activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true }
  val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass]

  // Calling get() instead of reading from the field directly to ensure the singleton is
  // created.
  val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)

  val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
  mInstanceField[activityManagerSingletonInstance] =
    swap(iActivityManagerInterface, activityManagerInstance!!)
}


private fun onServiceDestroyed(token: IBinder) {
  servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
    serviceWeakReference.get()?.let { service ->
      deletableObjectReporter.expectDeletionFor(
        service, "${service::class.java.name} received Service#onDestroy() callback"
      )
    }
  }
}

上面的代码比较简单就不细说了。


3. 泄露检测

当所有组件销毁时都会通知 DeletableObjectReporter#expectDeletionFor() 方法,我们来看看这个对象是怎么创建的:

Java
代码解读
复制代码
fun appDefaultWatchers(
  application: Application,
  deletableObjectReporter: DeletableObjectReporter = objectWatcher.asDeletableObjectReporter()
): List<InstallableWatcher> {
  // Use app context resources to avoid NotFoundException
  // https://github.com/square/leakcanary/issues/2137
  val resources = application.resources
  val watchDismissedDialogs = resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
  return listOf(
    ActivityWatcher(application, deletableObjectReporter),
    FragmentAndViewModelWatcher(application, deletableObjectReporter),
    RootViewWatcher(deletableObjectReporter, WindowTypeFilter(watchDismissedDialogs)),
    ServiceWatcher(deletableObjectReporter)
  )
}

在获取默认 Watcher 的时候我们就看到了它的身影,希望你还没有忘记上面的代码,他是通过 ObjectWatcher#asDeletableObjectReporter() 方法获取的,ObjectWatcher 已经被标记为弃用了,看来不久的将来会移除它。

Kotlin
代码解读
复制代码
fun asDeletableObjectReporter(): DeletableObjectReporter =
  DeletableObjectReporter { target, reason ->
  // 这个 Labmbda 其实就是 expectDeletionFor() 方法
  
    expectWeaklyReachable(target, reason)
    // This exists for backward-compatibility purposes and as such is unable to return
    // an accurate [TrackedObjectReachability] implementation.
    object : TrackedObjectReachability {
      override val isStronglyReachable: Boolean
        get() = error("Use a non deprecated DeletableObjectReporter implementation instead")
      override val isRetained: Boolean
        get() = error("Use a non deprecated DeletableObjectReporter implementation instead")
    }
  }

然后主要逻辑是调用了 expectWeaklyReachable() 方法,我们来看看在 ObjectWatcher 中的实现:

Kotlin
代码解读
复制代码

override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  val retainTrigger =
    retainedObjectTracker.expectDeletionOnTriggerFor(watchedObject, description)

  checkRetainedExecutor.execute {
    retainTrigger.markRetainedIfStronglyReachable()
  }
}

调用 ReferenceQueueRetainedObjectTracker#expectDeletionOnTriggerFor() 方法,这个方法会返回一个 RetainTrigger,然后在后台线程池中再执行 RetainTrigger#markRetainedIfStronglyReachable() 方法。

我相信大部分人都使用过 WeakReference,但是我相信不是所有人都知道如何监听 WeakReference 中的对象何时被回收,那就是在创建 WeakReference 的时候传入一个 ReferenceQueue,被回收的对象会被添加到 ReferenceQueue 中,这样我们就知道哪些对象被 gc 给回收了。在 LeakCanary 中也是这么判断的,应该被回收的对象都会传给过来,这个过程中还会手动触发 gc,然后超过一段时间后还是没有被回收 LeakCanary 就认为这个对象泄漏了,然后就会采取下一步措施锁定问题。

在了解了上面的知识后我们继续看 expectDeletionOnTriggerFor() 方法:

Kotlin
代码解读
复制代码

override fun expectDeletionOnTriggerFor(
  target: Any,
  reason: String
): RetainTrigger {
  // 移除已经被回收的对象
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
    .toString()
  val watchUptime = clock.uptime()
  // 生成一个自定义的带 key 的 weak reference, 注意这里有传入 ReferenceQueue
  val reference =
    KeyedWeakReference(target, key, reason, watchUptime.inWholeMilliseconds, queue)
  SharkLog.d {
    "Watching " +
      (if (target is Class<*>) target.toString() else "instance of ${target.javaClass.name}") +
      (if (reason.isNotEmpty()) " ($reason)" else "") +
      " with key $key"
  }
  
  // 保存弱引用到成员变量
  watchedObjects[key] = reference
  // 构建一个 trigger 对象返回
  return object : RetainTrigger {
  
    // 判断强引用是否可以到达
    override val isStronglyReachable: Boolean
      get() {
        removeWeaklyReachableObjects()
        val weakRef = watchedObjects[key]
        return weakRef != null
      }
   
    // 判断当前对象是否还存在,如果这里是 true 那就表示当前对象已经泄漏了
    override val isRetained: Boolean
      get() {
        removeWeaklyReachableObjects()
        val weakRef = watchedObjects[key]
        return weakRef?.retained ?: false
      }
    
    // 触发检查当前对象是否发生泄漏
    override fun markRetainedIfStronglyReachable() {
      moveToRetained(key)
    }
  }
}

我们在前面说到会在后台线程触发 RetainTrigger#markRetainedIfStronglyReachable() 方法,这个后台线程的触发的延迟时间是 5s,内部调用的是 moveToRetained() 方法,这里简单做一个总结,也就是 Android 中的某个组件进入 destroy 生命周期,LeakCanary 会将它对应的 Java 对象创建一个 WeakReference,并添加 ReferenceQueue,用于后续检查对应的对象是否已经被回收。然后开启一个延迟 5s 的检查任务,当这个任务触发时去检查目标对象是否被回收,如果没有被回收就开始后续的证据收集任务。

我们先来简单看看 LeakCanary 自定义的 KeyedWeakReference 类:

Kotlin
代码解读
复制代码

class KeyedWeakReference(
  referent: Any,
  // 对应对象的 key
  val key: String,
  // 组件的描述
  val description: String,
  // 组件对象 destory 生命周期时的时间戳
  val watchUptimeMillis: Long,
  referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
  referent, referenceQueue
) {
  /**
   * Time at which the associated object ([referent]) was considered retained, or -1 if it hasn't
   * been yet.
   */
  // 触发回收检查时,并且没有回收时的时间戳 
  @Volatile
  var retainedUptimeMillis = -1L

  // 是否被回收
  val retained: Boolean
    get() = retainedUptimeMillis != -1L

  override fun clear() {
    super.clear()
    retainedUptimeMillis = -1L
  }

  override fun get(): Any? {
    error("Calling KeyedWeakReference.get() is a mistake as it revives the reference")
  }

  /**
   * Same as [WeakReference.get] but does not trigger an intentional crash.
   *
   * Calling this method will end up creating local references to the objects, preventing them from
   * becoming weakly reachable, and creating a leak. If you need to check for identity equality, use
   * Reference.refersTo instead.
   */
  fun getAndLeakReferent(): Any? {
    return super.get()
  }

  companion object {
    @Volatile
    @JvmStatic var heapDumpUptimeMillis = 0L
  }
}

上面的对象并没有做太多的工作,添加了关键的对象的 key,还使用了一个变量来描述对象是否被回收,或者表示成是否泄漏。

继续看看 removeWeaklyReachableObjects() 方法如何清除被回收的对象。

Kotlin
代码解读
复制代码
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?
  do {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

方法很简单,直接去读取 ReferenceQueue 中的对象,这里面的对象都是被回收了的,然后把它们从 watchedObjects 中移除。上面的方法在很多的地方都会调用。

我们继续看看 moveToRetained() 方法是如何强制检查某个对象是否泄漏了:

Kotlin
代码解读
复制代码

private fun moveToRetained(key: String) {
  // 清除已经被回收的对象
  removeWeaklyReachableObjects()
  // 获取对应对象的 WeakRef
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    // 这里就表示发生了泄漏。
    
    // 更新泄漏检查的时间
    retainedRef.retainedUptimeMillis = clock.uptime().inWholeMilliseconds
    // 通知已经发生泄漏
    onObjectRetainedListener.onObjectRetained()
  }
}

检查某个对象是否泄漏也非常简单,先清除一次被回收的对象,然后去查找对应对象是否还存在,如果存在就表示发生了泄漏,发生泄漏后会更新泄漏时的时间戳,然后通过 listener 通知别的地方发生了泄漏,那么这个 listener 是在哪儿添加的呢?
在第一篇文章中讲 LeakCannary 初始化的时候我就说过,主要初始化分为两块逻辑,一是初始化各种 Watchers 去监控 Android 组件的销毁,还有就是初始化 LeakCanaryDelegate,你可能已经忘了,我们再回忆下:

Kotlin
代码解读
复制代码
@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"
  }
  this.retainedDelayMillis = retainedDelayMillis
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // Requires AppWatcher.objectWatcher to be set
  // 初始化 LeakCanaryDelegate
  LeakCanaryDelegate.loadLeakCanary(application)
  
  // 初始化各种 watchers
  watchersToInstall.forEach {
    it.install()
  }
  // Only install after we're fully done with init.
  installCause = RuntimeException("manualInstall() first called here")
}

继续看看 LeakCanaryDelegate.loadLeakCanary() 方法的实现:

Kotlin
代码解读
复制代码
@Suppress("UNCHECKED_CAST")
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 的方法:

Kotlin
代码解读
复制代码
override fun invoke(application: Application) {
  _application = application

  checkRunningInDebuggableBuild()
  
  // 添加泄漏监听
  AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
  
  // gc 触发器
  val gcTrigger = GcTrigger.inProcess()

  val configProvider = { LeakCanary.config }

  // 后台线程初始化
  val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
  handlerThread.start()
  val backgroundHandler = Handler(handlerThread.looper)

  // 内存 dump 触发器
  heapDumpTrigger = HeapDumpTrigger(
    application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger,
    configProvider
  )
  
  // APP 可见性监听
  application.registerVisibilityListener { applicationVisible ->
    this.applicationVisible = applicationVisible
   heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
  }
  registerResumedActivityListener(application)
 
 // 添加 Android shortcut 的UI
 LeakCanaryAndroidInternalUtils.addLeakActivityDynamicShortcut(application)

  // We post so that the log happens after Application.onCreate() where
  // the config could be updated.
  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()
          )
        }
      }
    }
  }
}

首先注册最重要的泄漏监听,这也解决了我们上面提出的问题;初始化 gc 触发器;初始化内存 dump 触发器;监听 APP 的可见性;添加 Android shortcut 的 UI。

我们来看看发生泄漏后它会怎么处理:

Kotlin
代码解读
复制代码
override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}

直接调用 HeapDumpTrigger#scheduleRetainedObjectCheck() 方法:

Kotlin
代码解读
复制代码
fun scheduleRetainedObjectCheck(
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  if (checkCurrentlyScheduledAt > 0) {
    return
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    checkRetainedObjects()
  }, delayMillis)
}

直接在后台线程中调用 checkRetainedObjects() 方法,继续追踪:

Kotlin
代码解读
复制代码
private fun checkRetainedObjects() {
  // 检查当前是否可以 dump
  val iCanHasHeap = HeapDumpControl.iCanHasHeap()

  val config = configProvider()

  if (iCanHasHeap is Nope) {
    // 不能 dump
    // ...
    return
  }
  
  // 泄漏的对象数量
  var retainedReferenceCount = retainedObjectTracker.retainedObjectCount

  if (retainedReferenceCount > 0) {
    // 当泄漏的对象大于 0 时,触发 gc
    gcTrigger.runGc()
    retainedReferenceCount = retainedObjectTracker.retainedObjectCount
  }

  // 检查泄漏对象的数量是否达到配置的上限,如果没有达到上限,跳过 dump, 默认上限值是 5
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

  // 检查两次 dump 的时间间隔,如果小于最小的间隔开启一个延时任务等到间隔时间了再 dump,最小间隔是 60s,
  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  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
  }
  
  // 关闭通知 UI
  dismissRetainedCountNotification()
  val visibility = if (applicationVisible) "visible" else "not visible"
  // 执行 dump
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}
  1. 检查当前是否能够 dump,不能直接退出。
  2. 检查泄漏对象数量,如果大于 0 通过 GCTracker 触发一次 GC
  3. 检查泄漏的对象是否达到配置的泄漏对象的上限,默认是 5,如果没有达到上限跳过 dump
  4. 检查上次 dump 到现在的间隔,如果小于 60s,那么需要等到 60s 后再次执行该任务。
  5. 执行 dump

看看 GCTracker 的实现:

Kotlin
代码解读
复制代码
override fun runGc() {
  // Code taken from AOSP FinalizationTest:
  // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
  // java/lang/ref/FinalizationTester.java
  System.gc()
  enqueueReferences()
  System.runFinalization()
  System.gc()
}

private fun enqueueReferences() {
  // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
  // references to the appropriate queues.
  try {
    Thread.sleep(100)
  } catch (e: InterruptedException) {
    throw AssertionError()
  }
}

看这个描述,这段代码是在 AOSP 中的测试代码中抄过来的,我们也可以直接拿过来直接用。

我们再来看看 dumpHeap() 方法的实现:

Kotlin
代码解读
复制代码

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
  val directoryProvider =
    InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
  // 获取 dump 输出的文件  
  val heapDumpFile = directoryProvider.newHeapDumpFile()

  val durationMillis: Long
  if (currentEventUniqueId == null) {
    currentEventUniqueId = UUID.randomUUID().toString()
  }
  try {
    // 发送开始 dump 的事件 
    InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId!!))
    if (heapDumpFile == null) {
      throw RuntimeException("Could not create heap dump file")
    }
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    durationMillis = measureDurationMillis {
      // 执行 dump
      configProvider().heapDumper.dumpHeap(heapDumpFile)
    }
    if (heapDumpFile.length() == 0L) {
      throw RuntimeException("Dumped heap file is 0 byte length")
    }
    lastDisplayedRetainedObjectCount = 0
    lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
    retainedObjectTracker.clearObjectsTrackedBefore(heapDumpUptimeMillis.milliseconds)
    currentEventUniqueId = UUID.randomUUID().toString()
    // 发送 dump 成功的事件
    InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
  } catch (throwable: Throwable) {
    // 出错
    InternalLeakCanary.sendEvent(HeapDumpFailed(currentEventUniqueId!!, throwable, retry))
    if (retry) {
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
      )
    }
    // 展示 dump 出错的通知栏。
    showRetainedCountNotification(
      objectCount = retainedReferenceCount,
      contentText = application.getString(
        R.string.leak_canary_notification_retained_dump_failed
      )
    )
    return
  }
}

上面的代码非常简单,dump 开始,结束和出错都会以事件的形式通知 InternalLeakCanary,针对不同的事件做出不同的处理,dump 的具体实现是 heapDumper。 下面是它的接口:

Kotlin
代码解读
复制代码
fun interface HeapDumper {

  /**
   * Dumps the heap. The implementation is expected to be blocking until the heap is dumped
   * or heap dumping failed.
   *
   * Implementations can throw a runtime exception if heap dumping failed.
   */
  fun dumpHeap(heapDumpFile: File)

  /**
   * This allows external modules to add factory methods for implementations of this interface as
   * extension functions of this companion object.
   */
  companion object
}

fun HeapDumper.withGc(gcTrigger: GcTrigger = GcTrigger.inProcess()): HeapDumper {
  val delegate = this
  return HeapDumper { file ->
    gcTrigger.runGc()
    delegate.dumpHeap(file)
  }
}

HeapDumper 这三个实现我认为可以看看:AndroidDebugHeapDumperHotSpotHeapDumper 和 UiAutomatorShellHeapDumper

AndroidDebugHeapDumper 也就是默认 Android 中 Debug 时使用,只有 Debug 可以用:

Kotlin
代码解读
复制代码
/**
 * Dumps the Android heap using [Debug.dumpHprofData].
 *
 * Note: despite being part of the Debug class, [Debug.dumpHprofData] can be called from non
 * debuggable non profileable builds.
 */
object AndroidDebugHeapDumper : HeapDumper {
  override fun dumpHeap(heapDumpFile: File) {
    Debug.dumpHprofData(heapDumpFile.absolutePath)
  }
}

fun HeapDumper.Companion.forAndroidInProcess() = AndroidDebugHeapDumper

朴实无华的代码。

HotSpotHeapDumper HotSpot 虚拟机使用,我看到在测试的代码中有用到。

Kotlin
代码解读
复制代码
object HotSpotHeapDumper : HeapDumper {
  private val hotspotMBean: HotSpotDiagnosticMXBean by lazy {
    val mBeanServer = ManagementFactory.getPlatformMBeanServer()
    ManagementFactory.newPlatformMXBeanProxy(
      mBeanServer,
      "com.sun.management:type=HotSpotDiagnostic",
      HotSpotDiagnosticMXBean::class.java
    )
  }

  override fun dumpHeap(heapDumpFile: File) {
    val live = true
    hotspotMBean.dumpHeap(heapDumpFile.absolutePath, live)
  }
}

fun HeapDumper.Companion.forJvmInProcess() = HotSpotHeapDumper

同样朴实无华的代码。

UiAutomatorShellHeapDumper 是 Android 中通过命令行的方式进行 dump

Kotlin
代码解读
复制代码
class UiAutomatorShellHeapDumper(
  private val withGc: Boolean,
  private val dumpedAppPackageName: String
) : HeapDumper {
  override fun dumpHeap(heapDumpFile: File) {
    val instrumentation = InstrumentationRegistry.getInstrumentation()
    val device = UiDevice.getInstance(instrumentation)
    val processId = device.getPidsForProcess(dumpedAppPackageName)
      // TODO Figure out what to do when we get more than one.
      .single()

    SharkLog.d { "Dumping heap for "$dumpedAppPackageName" with pid $processId to ${heapDumpFile.absolutePath}" }

    val forceGc = if (withGc && Build.VERSION.SDK_INT >= 27) {
      "-g "
    } else {
      ""
    }

    device.executeShellCommand("am dumpheap $forceGc$processId ${heapDumpFile.absolutePath}")
    // Make the heap dump world readable, otherwise we can't read it.
    device.executeShellCommand("chmod +r ${heapDumpFile.absolutePath}")
  }

  // Based on https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt;l=467;drc=8f2ba6a5469f67b7e385878d704f97bde22419ce
  private fun UiDevice.getPidsForProcess(processName: String): List<Int> {
    if (Build.VERSION.SDK_INT >= 23) {
      return pgrepLF(pattern = processName)
        .mapNotNull { (pid, fullProcessName) ->
          if (fullProcessNameMatchesProcess(fullProcessName, processName)) {
            pid
          } else {
            null
          }
        }
    }
    val processList = executeShellCommand("ps")
    return processList.lines()
      .filter { psLineContainsProcess(it, processName) }
      .map {
        val columns = SPACE_PATTERN.split(it)
        columns[1].toInt()
      }
  }

  private fun UiDevice.pgrepLF(pattern: String): List<Pair<Int, String>> {
    return executeShellCommand("pgrep -l -f $pattern")
      .split(Regex("\r?\n"))
      .filter { it.isNotEmpty() }
      .map {
        val (pidString, process) = it.trim().split(" ")
        Pair(pidString.toInt(), process)
      }
  }

  private fun psLineContainsProcess(
    psOutputLine: String,
    processName: String
  ): Boolean {
    return psOutputLine.endsWith(" $processName") || psOutputLine.endsWith("/$processName")
  }

  private fun fullProcessNameMatchesProcess(
    fullProcessName: String,
    processName: String
  ): Boolean {
    return fullProcessName == processName || fullProcessName.endsWith("/$processName")
  }

  private companion object {
    private val SPACE_PATTERN = Regex("\s+")
  }
}

fun HeapDumper.Companion.forUiAutomatorAsShell(
  withGc: Boolean,
  dumpedAppPackageName: String = InstrumentationRegistry.getInstrumentation().targetContext.packageName
) = UiAutomatorShellHeapDumper(withGc, dumpedAppPackageName)

这里用到了 androidx 的库来执行命令行,具体的命令行是 am dumpheap -g [pid] [file], 最后通过 chmod +r [file] 让文件可读,上面的大量代码是去拿当前应用的 pid,使用的是命令 pgrep -l -f [process name]

4. 内存泄漏报告

4.1. 获取堆转储文件

  • 在检测到可能的内存泄漏后,LeakCanary 调用 Android 提供的 Debug.dumpHprofData() 方法生成堆转储文件。
  • 该文件记录了当前 JVM 堆中的对象分布、引用关系以及元数据信息。
kotlin
复制代码
Debug.dumpHprofData("path/to/heapdump.hprof")

4.2. Shark 引擎解析 .hprof 文件

LeakCanary 的 Shark 引擎是核心分析工具,其主要任务是解析 .hprof 文件,构建对象引用图,并找到导致内存泄漏的引用路径。

关键步骤

  1. 加载 .hprof 文件 Shark 引擎加载 .hprof 文件并逐行解析,提取堆中对象、类、字段和引用信息。

    kotlin
    复制代码
    val heapAnalyzer = HeapAnalyzer(SharkLog.Logger { message -> Log.d(TAG, message) })
    val heapAnalysis = heapAnalyzer.analyzeHeap(heapDumpFile)
    
  2. 构建引用图 Shark 使用解析后的数据构建引用图(Reference Graph)。这个图展示了堆中所有对象及其引用关系。

    • 每个对象是图中的一个节点。
    • 节点之间的边表示对象之间的引用。
  3. 定位 GC Roots GC Root 是 JVM 中一组特殊的对象,这些对象被认为是“活跃”的,任何与它们有路径连接的对象都不会被垃圾回收。

    常见的 GC Root 类型:

    • 静态变量(静态字段持有的对象)
    • 活跃线程中的局部变量
    • JNI 引用
  4. 追踪泄漏路径 从 GC Roots 出发,沿着引用链追踪到未被回收的对象(泄漏对象)。

    • 如果对象本应被销毁但仍可通过 GC Roots 引用链访问,则说明该对象发生泄漏。

4.3. 泄漏分析结果

引用链

Shark 引擎的输出结果包括泄漏对象及其引用链。引用链展示了泄漏对象是如何通过其他对象间接或直接与 GC Roots 连接的。

示例引用链:

plaintext
复制代码
GC Root -> Singleton -> Activity -> View

HeapAnalysisSuccess

如果堆文件解析成功,HeapAnalysisSuccess 会返回详细的泄漏信息:

  • 泄漏对象的引用路径。
  • 泄漏对象的内存占用大小。
  • 导致泄漏的源代码位置(如果可能)。
kotlin
复制代码
data class HeapAnalysisSuccess(
    val retainedObjectCount: Int,
    val leakTraces: List<LeakTrace>
)

HeapAnalysisFailure

如果解析失败,HeapAnalysisFailure 会提供失败的原因,帮助开发者排查问题。


4.4. 引用链可视化

LeakCanary 使用图形化方式显示引用链(例如在通知中展示简化后的引用路径),方便开发者快速定位问题。


示例:Activity 泄漏解析

代码示例

kotlin
复制代码
class LeakActivity : AppCompatActivity() {
    companion object {
        var leakInstance: LeakActivity? = null
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        leakInstance = this
    }
}

泄漏原因

  • 静态变量 leakInstance 持有 LeakActivity 的引用。
  • 即使 LeakActivity 被销毁,leakInstance 仍然存在,导致无法被回收。

Shark 引擎解析过程

  1. 加载 .hprof 文件

    • Shark 引擎扫描堆转储文件,找到所有对象及其引用。
  2. 构建引用图

    • 引用链:GC Root -> leakInstance -> LeakActivity
  3. 输出泄漏路径

    • 最终生成的泄漏路径为:

      plaintext
      复制代码
      GC Root
        ↓
      leakInstance (Static field)
        ↓
      LeakActivity
      

4.5. 为什么需要解析 GC Root?

GC Root 是 JVM 中垃圾回收的起点,任何从 GC Root 可达的对象都被认为是“活跃”的,不会被回收。LeakCanary 通过 Shark 引擎:

  1. 找出 GC Roots。
  2. 确定哪些对象与 GC Root 可达。
  3. 发现不应该存在的对象(如已销毁的 Activity),从而定位内存泄漏。

4.6. 结论

LeakCanary 通过 Shark 引擎解析堆转储文件,追踪泄漏对象的引用链。GC Root 的分析是整个流程的关键,因为它是垃圾回收的起点,决定了对象的存活状态。

  • 获取 .hprof 文件:调用系统方法导出内存快照。
  • 解析引用关系:构建引用图,从 GC Root 开始追踪泄漏对象。
  • 输出引用链:展示详细路径,帮助开发者快速定位泄漏原因。

这套流程极大地简化了内存泄漏的定位工作,使得 LeakCanary 成为 Android 开发中不可或缺的内存管理工具

5. 关键模块源码解析

(1) ObjectWatcher

ObjectWatcher 是 LeakCanary 的核心类,负责监控对象并检测是否发生泄露。

关键字段

kotlin
复制代码
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val referenceQueue = ReferenceQueue<Any>()
  • watchedObjects:存储所有被监控的对象。
  • referenceQueue:保存被 GC 标记的弱引用对象。

watch() 方法

ObjectWatcher 的核心方法 watch() 将目标对象包裹为 KeyedWeakReference 并添加到监控队列。

kotlin
复制代码
fun watch(watchedObject: Any, description: String) {
    val key = UUID.randomUUID().toString()
    val weakReference = KeyedWeakReference(watchedObject, key, description, referenceQueue)
    watchedObjects[key] = weakReference
    ensureReferenceQueueMonitoring()
}

(2) 检测逻辑

引用队列监控

通过一个后台线程不断检查 referenceQueue 是否有对象被回收。

kotlin
复制代码
private fun ensureReferenceQueueMonitoring() {
    referenceQueueThread = Thread {
        while (true) {
            val weakReference = referenceQueue.remove() as KeyedWeakReference
            watchedObjects.remove(weakReference.key)
        }
    }
    referenceQueueThread.start()
}

检测内存泄露

如果对象在一定时间内未被回收,则触发内存泄露分析。


(3) 堆转储与分析

Heap Dump

当确定内存泄露时,LeakCanary 触发堆转储操作:

kotlin
复制代码
Debug.dumpHprofData("path/to/heapdump.hprof")

堆分析引擎 Shark

LeakCanary 使用 Shark 引擎解析 .hprof 文件,查找泄露对象的引用链。

kotlin
复制代码
val heapAnalyzer = HeapAnalyzer(SharkLog.Logger { message -> Log.d(TAG, message) })
val analysis = heapAnalyzer.analyzeHeap(heapDumpFile)
  • analyzeHeap 方法:构建对象引用图(Reference Graph),从 GC Roots 开始查找泄露路径。

(4) 分析结果报告

堆分析结果通过 HeapAnalysis 返回:

  • HeapAnalysisSuccess:泄露对象及其引用链信息。
  • HeapAnalysisFailure:堆分析失败的原因。

引用链构建

LeakCanary 输出泄露对象的引用链:

plaintext
复制代码
GC Root -> Singleton -> Activity -> View

帮助开发者快速定位泄露源。


6. 示例流程

以下为一个 Activity 泄露的完整检测流程:

image.png

  1. Activity 监控

    • onActivityDestroyed() 中调用 ObjectWatcher.watch()
    • Activity 包装为弱引用,并放入 watchedObjects
  2. GC 触发

    • 强制调用 System.gc(),检查对象是否被回收。
  3. 泄露确认

    • 如果对象未被回收,触发堆转储。
  4. 堆分析

    • 使用 Shark 分析 .hprof 文件,构建引用链。
  5. 生成报告

    • 输出泄露对象及其引用路径,开发者可以根据路径修复问题。

7. 源码架构优势

(1) 模块化设计

  • 各模块职责分明:ObjectWatcher 负责监控对象,HeapAnalyzer 负责堆分析,Shark 提供高效的堆解析能力。

(2) 高效堆分析

  • 使用 Shark 代替传统 MAT 工具,分析效率大幅提升,且支持内存受限的移动设备。

(3) 自动化流程

  • 与应用生命周期深度集成,开发者无需额外代码即可检测内存泄露。

8. 总结

LeakCanary 的核心工作流程:

  1. 监控对象:通过弱引用和引用队列检查对象生命周期。
  2. 泄露判断:在合适时间点触发 GC,并检测对象是否被回收。
  3. 堆分析:使用 Shark 引擎分析泄露对象的引用链。
  4. 输出报告:生成详细的泄露路径报告,方便开发者定位问题。

LeakCanary 的源码设计清晰、高效、易于扩展,是学习 Android 内存管理的绝佳工具。