从LeakCanary看Service生命周期监控

大家都知道使用LeakCanary可以监控项目中存在的内存泄漏问题,那么LeakCanary是怎么实现的呢?LeakCanary通过检测程序中对象的引用关系,收集应该被回收的对象并标记,随后等待GC后,检查该对象是否按预期回收即可,目前LeakCanary支持Service,Activity,Fragment,ViewModel以及View的泄漏检测,接下来我们一起来看下Service的关联部分。

首先,我们考虑如果要认定一个Service对象可以被回收,前提条件是什么?没错,当然是这个Service执行了onDestroyed方法,也就意味着我们要检测Service的内存泄漏情况,首先要实现Service的生命周期监控,这样的话当Service执行了onDestroyed方法后,我们就可以对该Service对象进行标记和观察。那么如何监控Service的生命周期呢?

Service的启动过程

上图中描述的Service启动过程包含进程创建,流程很清晰,不做解释,有兴趣的同学可以跟源码看下。

Service的销毁过程

如上图当Service回调onDestroyed完成后,会通知AMS Service已经销毁。

Service生命周期监控

从前文中,我们已经基本了解了Service的启动和销毁过程,可以看出不论是Service的创建还是Service的销毁,在整个流程中都涉及到一个非常中要的Handler角色,这个Handler在ApplicationThread和ActivityThread中间充当桥梁作用,当有Service创建时,会接受并处理CREATE_SERVICE消息,当有Service销毁时,会接受并处理STOP_SERVICE消息,回调Service onDestroy方法。

没错,就是我们的mH对象,这个对象定义在ActivityThread中,如果我们能监听其内部的消息处理,自然可以实现Setvice生命周期监控的能力

监听mH接收到的STOP_SERVEICE消息(Service即将销毁)

如何监听mH Handler对象接收到的STOP_SERVICE消息呢?首先我们来看下Handler内部是如何进行消息分发的?

可以看到当Handler中的mCallback对象不为空时,消息会首先分发给mCallback对象执行,如果该函数返回false,则继续分发。这也就意味着我们可以修改mH Handler对象中的mCallback成员,通过该成员对象完成Handler中消息分发的监听,而不影响原始的消息分发逻辑。

反射mH Handler对象,设置mCallback成员

结合上文以及反射相关知识,我们可以得到下面反射并设置mH Handler对象的mCallback成员的实现,代码如下:

ini 复制代码
 try {
     Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
     Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
     currentActivityThreadMethod.setAccessible(true);
     // currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
     Object currentActivityThread = currentActivityThreadMethod.invoke(null);
 ​
     // 获取mH Handler对象
     Field mH = activityThreadClass.getDeclaredField("mH");
     mH.setAccessible(true);
     Handler handler = (Handler) mH.get(currentActivityThread);
 ​
     // 获取Handler中Callback对象并赋值
     Field callBack = Handler.class.getDeclaredField("mCallback");
     callBack.setAccessible(true);
 ​
     callBack.set(handler,new Handler.Callback(){
 ​
         @Override
         public boolean handleMessage(Message msg) {
             Log.d(TAG, "handleMessage: msg " + msg);
 ​
             return false;
         }
     });
 } catch (ClassNotFoundException | NoSuchFieldException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
     e.printStackTrace();
 }

编写一个TestService,前后分别调用startService和stopService,验证日志输出如下:

可以看到我们确实监听到了msg.what = 116的Service销毁的消息。

mH Handler对象中STOP_SERVICE取值为116

从日志可以看出,在STOP_SERVICE消息中并没有被销毁的Service信息,此时我们应该如何匹配那个Service被销毁了呢?

找出即将被销毁的Service

在STOP_SERVICE消息中没有携带被销毁的Service相关信息,那么系统是如何知道那个Service被销毁了呢?我们查看源码一探究竟:

可以看到在ActivityThread中是通过msg.obj来索引标记Service的,msg.obj是一个IBinder对象,在handleStopService中,通过Service s = mServices.remove(token);获取msg.obj对应的Service对象,并依次调用Service的onDestroy和deatchAndCleanUp方法,结合这段代码,我们不难联想到handleStopService这里引用的mServices是一个类Map类型的数据结构,当调用其remove方法时会依据key将数据结构中的数据删除,并返回该key值对应的value对象,下面我们来看下mServices的声明和初始化:

从源码上可以看到mServices是一个以IBinder对象为key,Service为值的ArrayMap,也就意味着我们只要能访问到mServices成员,就可以通过监听到的Message.obj来获取对应的Service对象。

怎么获取mServices对象呢?自然也是通过反射,相关代码如下:

ini 复制代码
 private void hookServicesInActivityThread(){
     try {
         Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
         Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
         currentActivityThreadMethod.setAccessible(true);
         // currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
         Object currentActivityThread = currentActivityThreadMethod.invoke(null);
 ​
         Field mServices = activityThreadClass.getDeclaredField("mServices");
         mServices.setAccessible(true);
         mActivityThreadServices = (Map<IBinder, Service>) mServices.get(currentActivityThread);
 ​
     } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
              InvocationTargetException | NoSuchFieldException e) {
         e.printStackTrace();
     }
 }
 ​
 private Service findServiceFromActivityThreadServices(IBinder token) {
     if (mActivityThreadServices == null) {
         hookServicesInActivityThread();
     }
     return mActivityThreadServices.get(token);
 }

运行后日志输出如下,可以看到我们确实找到了即将被销毁的Service对象

结合Service销毁过程一节中流程图和handleStopService代码可知,在我们监听到消息时,Service实际上还没有调用onDestroy方法,也就意味着mH中的STOP_SERVICE消息仅代表Service即将开始销毁,那么什么时候销毁完成呢?

没错,在流程图和handleStopService代码中均可以看出,当调用ActivityManager.getService().serviceDoneExecuting()方法时,代表Service已经销毁完成。

监听Service销毁完成

从流程图和handleStopService代码可知,如果要监听Service销毁完成,也就是要监听serviceDoneExecuting方法的调用,怎么做呢?

反射+代理,将ActivityManager.getService返回的对象使用代理包装一层后重新设置回去即可.

在Android 8.0及以后,IActivityManager是从IActivityManagerSingleton中获取的对象,代码如下:

在Android 8.0以前,IActivityManager是从ActivityManagerNative的成员gDefault中获取的,代码如下:

代码如下:

ini 复制代码
 try {
     Object defaultSingleton = null;
     if (Build.VERSION.SDK_INT >= 26) {
         Class<?> activityManageClazz =
                 Class.forName("android.app.ActivityManager");
         Field field = activityManageClazz.getDeclaredField("IActivityManagerSingleton");
         field.setAccessible(true);
         //获取activityManager中的IActivityManagerSingleton字段
         defaultSingleton = field.get(null);
     } else {
         Class<?> activityManagerNativeClazz =
                 Class.forName("android.app.ActivityManagerNative");
         //获取ActivityManagerNative中的gDefault字段
         Field field = activityManagerNativeClazz.getDeclaredField("gDefault");
         field.setAccessible(true);
         defaultSingleton = field.get(null);
     }
     
     Class<?> singletonClazz = Class.forName("android.util.Singleton");
     Field mInstanceField = singletonClazz.getDeclaredField("mInstance");
     mInstanceField.setAccessible(true);
     
     //获取iActivityManager
     Object iActivityManager = mInstanceField.get(defaultSingleton);
     Class<?> iActivityManagerClazz =
             Class.forName("android.app.IActivityManager");
     
     Object proxy = Proxy.newProxyInstance(
             Thread.currentThread().getContextClassLoader(),
             new Class<?>[]{iActivityManagerClazz},
             new IActivityManagerProxy(iActivityManager));
     
     mInstanceField.set(defaultSingleton, proxy);
 } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
     e.printStackTrace();
 }
typescript 复制代码
 public class IActivityManagerProxy implements InvocationHandler {
     private Object mActivityManager;
 ​
     public IActivityManagerProxy(Object activityManager) {
         mActivityManager = activityManager;
     }
 ​
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         Log.d("ServiceWatcher", "proxy receive method:" + method.getName());
         return method.invoke(mActivityManager, args);
     }
 }

运行,我们可以看到在IActivityManagerProxy中监听到了serviceDoneExecuting方法调用,日志如下:

综上,我们也就完成了Service销毁的监听。

虽然文中只是重点介绍了Service销毁过程的监听,但是基于文中代码结构,我们不难实现自己的全局Service生命周期监听,用于监听进程中Service的生命周期变化。leakcanary中的实现请参考:github.com/square/leak...

相关推荐
sweetying2 小时前
30了,人生按部就班
android·程序员
用户2018792831672 小时前
Binder驱动缓冲区的工作机制答疑
android
真夜3 小时前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
用户2018792831673 小时前
浅析Binder通信的三种调用方式
android
用户093 小时前
深入了解 Android 16KB内存页面
android·kotlin
火车叼位4 小时前
Android Studio与命令行Gradle表现不一致问题分析
android
前行的小黑炭6 小时前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭7 小时前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app
用户20187928316719 小时前
Android黑夜白天模式切换原理分析
android
芦半山19 小时前
「幽灵调用」背后的真相:一个隐藏多年的Android原生Bug
android