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

相关推荐
薛晓刚9 小时前
MySQL的replace使用分析
android·adb
DengDongQi10 小时前
Jetpack Compose 滚轮选择器
android
stevenzqzq10 小时前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停10 小时前
MySQL事务
android·数据库·mysql
朝花不迟暮10 小时前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio
yngsqq10 小时前
使用VS(.NET MAUI)开发第一个安卓APP
android·.net
Android-Flutter10 小时前
android compose LazyVerticalGrid上下滚动的网格布局 使用
android·kotlin
Android-Flutter10 小时前
android compose LazyHorizontalGrid水平滚动的网格布局 使用
android·kotlin
千里马-horse10 小时前
RK3399E Android 11 将自己的库放到系统库方法
android·so·设置系统库
美狐美颜sdk10 小时前
Android直播美颜SDK:选择指南与开发方案
android·人工智能·计算机视觉·第三方美颜sdk·视频美颜sdk·人脸美型sdk