从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成员的实现,代码如下:

 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(){​@Overridepublic 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对象呢?自然也是通过反射,相关代码如下:

 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中获取的,代码如下:

代码如下:

 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);//获取iActivityManagerObject 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();}

 public class IActivityManagerProxy implements InvocationHandler {private Object mActivityManager;​public IActivityManagerProxy(Object activityManager) {mActivityManager = activityManager;}​@Overridepublic 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的生命周期变化。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/59295.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MFC第二十八天 WM_SIZE应用,CFrameWnd类LoadFrame的方法,PreCreateWindow窗口预处理,Frame-view窗口视图

文章目录 WM_SIZE应用通过WM_SIZE消息实现窗口布局管理通过控件属性实现窗口布局管理 CFrameWnd类CFrameWnd类简介OnCreate和OnCreateClient的方法注册时的要素与窗口设置PreCreateWindow创建窗口的预处理函数 附录预处理函数的结构体CFrameWnd::LoadFrame与CreateFrame WM_SIZ…

回归预测 | MATLAB实现基于SVM-RFE-BP支持向量机递归特征消除特征选择算法结合BP神经网络的多输入单输出回归预测

回归预测 | MATLAB实现基于SVM-RFE-BP支持向量机递归特征消除特征选择算法结合BP神经网络的多输入单输出回归预测 目录 回归预测 | MATLAB实现基于SVM-RFE-BP支持向量机递归特征消除特征选择算法结合BP神经网络的多输入单输出回归预测预测效果基本介绍研究内容程序设计参考资料…

2.若依前后端分离版第一个增删查改

1.介绍 若依提供了代码生成功能&#xff0c;单表的CRUD可以直接用若依框架提供的代码生成进行创建。 2.实现 2.1 在数据库创建业务表test_teacher 2.2 生成代码 运行系统&#xff0c;进入菜单[系统工具]-》[代码生成],点击导入按钮&#xff0c;选择需要生成代码的表进行导…

链表oj (环形链表oj)

文章目录 1.数组oj 2.链表oj 文章内容 1.数组oj 1. 原地移除数组中所有的元素值为val&#xff0c;要求时间复杂度为O(N)&#xff0c;空间复杂度为O(1)。力扣 int removeElement(int* nums, int numsSize, int val){int k numsSize;int a 0;int b 0;while(b<k){if(nu…

HDFS中的Trash垃圾桶回收机制

Trash垃圾桶回收机制 文件系统垃圾桶背景功能概述Trash Checkpoint Trash功能开启关闭HDFS集群修改core-site.xml删除文件到trash删除文件跳过从trash中恢复文件清空trash 文件系统垃圾桶背景 回收站&#xff08;垃圾桶&#xff09;是windows操作系统里的一个系统文件夹&#…

C++进阶 智能指针

本篇博客简介&#xff1a;介绍C中的智能指针 智能指针 为什么会存在智能指针内存泄露内存泄漏定义内存泄漏的危害如何检测内存泄漏如何避免内存泄漏 智能指针的使用及其原理RAII设计一个智能指针C官方的智能指针 定制删除器智能指针总结 为什么会存在智能指针 我们首先来看下面…

SpringBoot 底层机制分析[上]

文章目录 分析SpringBoot 底层机制【Tomcat 启动分析Spring 容器初始化Tomcat 如何关联Spring 容器】[上]搭建SpringBoot 底层机制开发环境Configuration Bean 会发生什么&#xff0c;并分析机制提出问题&#xff1a;SpringBoot 是怎么启动Tomcat &#xff0c;并可以支持访问C…

剑指Offer12.矩阵中的路径 C++

1、题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平…

配置Hive远程服务详细步骤

HiveServer2支持多客户端的并发和认证&#xff0c;为开放API客户端如JDBC、ODBC提供了更好的支持。 &#xff08;1&#xff09;修改hive-site.xml&#xff0c;在文件中添加以下内容&#xff1a; <property><name>hive.metastore.event.db.notification.api.auth&l…

药库管理指南:除了药物质量,这个技能很关键

随着医疗技术的不断进步&#xff0c;药品的质量要求也日益严格&#xff0c;药物的稳定性、疗效以及避免细菌滋生等方面都直接受到温湿度变化的影响。 药品质量和安全是医疗行业的首要任务之一&#xff0c;药库温湿度监控在此过程中发挥着关键作用。通过科学合理地实施温湿度监控…

Q-Vision+Kvaser CAN/CAN FD/LIN总线解决方案

智能联网技术在国内的发展势头迅猛&#xff0c;随着汽车智能化、网联化发展大潮的到来&#xff0c;智能网联汽车逐步成为汽车发展的主要趋势。越来越多整车厂诉求&#xff0c;希望可以提供本土的测量软件&#xff0c;特别是关于ADAS测试。而Softing中国推出的Q-Vision软件不仅可…

不看后悔一辈子!不看错过50K!历尽心血总结Redis全局命令

前言&#xff1a; &#x1f4d5;作者简介&#xff1a;热爱编程的敖云岚&#xff0c;致力于C、Java、Python等多编程语言&#xff0c;热爱编程和长板的运动少年&#xff01; &#x1f4d8;相关专栏&#xff1a;Java基础语法&#xff0c;JavaEE初阶&#xff0c;数据库&#xff0c…