Main Branch analysis based on LeakCanary 2, 872C5567

The LeakCanary code has recently added automatic detection of RootView (usually a DecorView) and Service leaks. This article examines how these two functions are implemented.

RootView detection

To implement the detection of RootView, we just need to watch RootView in RootView detached.

RootView detached we can through View# addOnAttachStateChangeListener monitor, but when invoke addOnAttachStateChangeListener? Obviously we need to handle this when RootView will be added to the Window.

Switching, the question becomes: How do I know that RootView will be added to the Window? (Note: does not represent attached)

The listener RootView will be added to the Window

All rootviews, to render to the screen, need to call WindowManager#addView to add the View to the Window. And Windows Manager#addView actually calls Windows Manager General #addView.

// WindowManagerGlobal.java

private final ArrayList<View> mViews = new ArrayList<View>();

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {... mViews.add(view); .try {
        / / new ViewRootImpl and IWindowSession addToDisplay, gives the View associated with the Displayroot.setView(view, wparams, panelParentView); }... }Copy the code

As you can see from the code above, each addView adds RootView to mViews, which is how we can solve the first problem.

The specific practices

The complete idea of RootView detection is divided into three steps:

  1. Inherit and rewriteArrayListaddMethod and reflection set toWindowManagerGlobal.mViews
  2. whenaddCalled, set for RootViewOnAttachStateChangeListener
  3. whenOnAttachStateChangeListener#onViewDetachedFromWindowWhen called, watch RootView

Since Dialog, Toast, etc. are called to WindowManager.addView, RootView can also detect leaks of Dialog, Toast, etc

See RootViewDetachWatcher.kt for the complete code

The Service test

Similar to RootView, we need to watch Service after Service#onDestroy. How do you know that Service calls onDestroy?

Service onDestroy Application flow

Take a look at the flow of Service#onDestroy on the application side

// ActivityThread.java

final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();

class H extends Handler {
    public static final int STOP_SERVICE            = 116;

    public void handleMessage(Message msg) {
        switch (msg.what) {
            ...
            case STOP_SERVICE:/ / 1.
                handleStopService((IBinder)msg.obj);
                break; . }}}private void handleStopService(IBinder token) {
    Service s = mServices.remove(token);/ / 2.
    if(s ! =null) {
        try {
            if (localLOGV) Slog.v(TAG, "Destroying service " + s);
            s.onDestroy();/ / 3.s.detachAndCleanUp(); .try {
                / / 4.
                ActivityManager.getService().serviceDoneExecuting(
                        token, SERVICE_DONE_EXECUTING_STOP, 0.0);
            } catch (RemoteException e) {
                throwe.rethrowFromSystemServer(); }}catch(Exception e) { ... }}else {
        Slog.i(TAG, "handleStopService: token=" + token + " not found."); }}Copy the code

AMS serviceDoneExecuting must be executed after onDestroy. You can watch a Service by listening for serviceDoneExecuting to be called.

This raises two new questions:

  1. How to learnserviceDoneExecutingIs called?
  2. How to get the currentserviceDoneExecutingThe correspondingServiceThe instance?

ServiceDoneExecuting is called

Familiar with pluggable friends will know, ActivityManager getService () is actually a return through a global singleton IActivityManager, we set up for its dynamic proxy and modify mInstance field in the singleton, ServiceDoneExecuting is a way of telling a Service destroy whenever a serviceDoneExecuting call is made.

Obtain serviceDoneExecuting Service instance

In the above code, we can obtain the token (msg.obj) corresponding to the Service. The Service has been removed from the mServices at ②, so we can only obtain the Service instance at ①. ServiceDoneExecuting is a token. You can tell which Service onDestroy is a Service onDestroy by executing a Service token.

The specific practices

Sorted out the above ideas, can be divided into the following steps:

  1. Hook ActivityManagerIActivityManagerCorresponding singleton (IActivityManagerSingleton), set up a dynamic proxy for it to listenserviceDoneExecuting
  2. Hook ActivityThread.H.mCallbackIn order to monitorSTOP_SERVICE
  3. receivedSTOP_SERVICEIs converted to msg.obj (token)IBindermServicesTo get the correspondingServiceInstance, and willIBinderServiceStored in theMap
  4. serviceDoneExecutingIs called with the argument one (IBinderToken) fromMapTo obtain the correspondingServiceFor example, call it Watch

See ServiceDestroyWatcher.kt for the complete code

other

Similar methods can be used to monitor broadcast leaks. And the authors of LeakCanary are also learning about this, as shown here. If you are interested, you can try it and maybe the author will accept you.