从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...

相关推荐
沐言人生1 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
追光天使2 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru2 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数3 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数3 小时前
Android车载——VehicleHal运行流程(Android 11)
android
problc3 小时前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜4 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility
服装学院的IT男8 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms2068 小时前
android 全面屏最底部栏沉浸式
android
服装学院的IT男8 小时前
【Android 源码分析】Activity生命周期之onStop-1
android