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:
- Inherit and rewrite
ArrayList
的add
Method and reflection set toWindowManagerGlobal.mViews
- when
add
Called, set for RootViewOnAttachStateChangeListener
- when
OnAttachStateChangeListener#onViewDetachedFromWindow
When 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:
- How to learn
serviceDoneExecuting
Is called? - How to get the current
serviceDoneExecuting
The correspondingService
The 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:
- Hook
ActivityManager
中IActivityManager
Corresponding singleton (IActivityManagerSingleton
), set up a dynamic proxy for it to listenserviceDoneExecuting
- Hook
ActivityThread.H.mCallback
In order to monitorSTOP_SERVICE
- received
STOP_SERVICE
Is converted to msg.obj (token)IBinder
从mServices
To get the correspondingService
Instance, and willIBinder
及Service
Stored in theMap
serviceDoneExecuting
Is called with the argument one (IBinder
Token) fromMap
To obtain the correspondingService
For 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.