LeakCanary 源码阅读笔记(二)

372 阅读4分钟

LeakCanary 源码阅读笔记(二)

20241102-4.jpg

在上一篇文章中我们介绍了 LeakCanary 的初始化,Activity, FragmentViewModel 的销毁监控:LeakCanary 源码阅读笔记(一)
废话少说,我们继续开始今天的内容。

RootView 销毁监控

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

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

先看看 Activity#getSystemService() 方法:

@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 的初始化,我们来看看这个方法:

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() 方法的实现:

@Override
public Object getSystemService(String name) {
    // ...
    return SystemServiceRegistry.getSystemService(this, name);
}

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

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

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

static {
// ...
registerService(Context.WINDOW_SERVICE, WindowManager.class,
        new CachedServiceFetcher<WindowManager>() {
    @Override
    public WindowManager createService(ContextImpl ctx) {
        return new WindowManagerImpl(ctx);
    }});
// ...    
}

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

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

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() 生成的:

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
}

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

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

// ...

@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() 方法:

@UnsupportedAppUsage
public static WindowManagerGlobal getInstance() {
    synchronized (WindowManagerGlobal.class) {
        if (sDefaultWindowManager == null) {
            sDefaultWindowManager = new WindowManagerGlobal();
        }
        return sDefaultWindowManager;
    }
}

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

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


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() 方法:


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 是属于哪个组件的:

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 来判断。
其他的情况就通过 LayoutParamstitle 来判断,具体看代码。

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


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

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

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

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


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 的命名为 mHHandler 成员变量。然后 hook Handercallback,在收到 STOP_SERVICE 时就表示 Service 要被销毁了,然后对应的 msgwaht 就是对应 Servicetoken,然后再 hook ActivityThread 中的 mServices 成员变量,它表示当前所有存活的 Service,通过上面的 token 找到对应的 Service,然后把它添加到即将销毁的 Service 集合中。

我们来看看这部分代码:

// 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

继续看代码:


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

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

最后

本篇文章主要介绍了监控 root viewService 的销毁,由于 Android 没有提供对应的 API 去监控他们,所以 LeakCanary 的这部分代码有很大的借鉴价值,希望对你有所帮助。