Android 如何监控 Loop Message 长消息
引言忙忙碌碌又一天,最近学习了一个新项目,每天都是吭哧吭哧的去看项目中的各种源码,都来自于各路大神的贡献。每看到一块地方的时候都感觉触到了自己的知识边界,然后又得吭哧吭哧的去搜索。不知道大家的搜索路径是什么,我的搜索路径从以前的 Google - 掘金 - 简书 - Other(Baidu) 变成了 ChatGPT - Google - 其他。
最近在排查主线程耗时的一个任务,既然在主线程了,那还不好办,直接上 Trace 分析主线程中的耗时任务都有哪些不就完了。完了分析完一波后又无从下手啦。接下来考虑从 MainLooper 下手吧,看看哪些是长消息,执行任务又比较耗时。
HandlerLooper那么如何做长消息的耗时监控呐?这又遇到了一个问题,在以往很多都是通过 Looper 的 Printer 进行控制长消息的检查,但是 Printer 也是有缺点的,就是无法知道它的 callback 来自于哪里,及时统计出来了时间,也不满足需求。
一切的源头都应该从源码进行着手,通过阅读源码发现,在 Looper 中发现了 Observer,它是什么鬼?
publicfinalclassLooper{
privatestaticObserversObserver;
/**
*SetthetransactionobserverforallLoopersinthisprocess.
*
*@hide
*/
publicstaticvoidsetObserver(@NullableObserverobserver){
sObserver=observer;
}
publicstaticvoidloop(){
//...此处省略无关紧要的code
for){
//...此处省略无关紧要的code
//Thismustbeinalocalvariable,incaseaUIeventsetsthelogger
finalPrinterlogging=me.mLogging;
if(logging!=null){
logging.println("Dispatchingto"+msg.target+""+
msg.callback+":"+msg.what);
}
//Makesuretheobserverwon'tchangewhileprocessingatransaction.
finalObserverobserver=sObserver;
//...此处省略无关紧要的code
Objecttoken=null;
//在执行消息执行消息通知
if(observer!=null){
token=observer.messageDispatchStarting();
}
longorigWorkSource=ThreadLocalWorkSource.setUid(msg.workSourceUid);
try{
msg.target.dispatchMessage(msg);
//在消息执行完毕后又执行了消息通知
if(observer!=null){
observer.messageDispatched(token,msg);
}
dispatchEnd=needEndTime?SystemClock.uptimeMillis():0;
}catch(Exceptionexception){
//消息执行的过程中发生了异常的回调
if(observer!=null){
observer.dispatchingThrewException(token,msg,exception);
}
throwexception;
}finally{
ThreadLocalWorkSource.restore(origWorkSource);
if(traceTag!=0){
Trace.traceEnd(traceTag);
}
}
}
}
}
根据阅读源码来看,我觉得可以从这个地方下手搞事情。
/**{@hide}*/
publicinterfaceObserver{
/**
*Calledrightbeforeamessageisdispatched.
*
*pThetokentypeisnotspecifiedtoallowtheimplementationtospecifyitsowntype.
*
*@returnatokenusedforcollectingtelemetrywhendispatchingasinglemessage.
*Thetokentokenmustbepassedbackexactlyoncetoeither
*{@linkObserver#messageDispatched}or{@linkObserver#dispatchingThrewException}
*andmustnotbereusedagain.
*
*/
ObjectmessageDispatchStarting();
/**
*CalledwhenamessagewasprocessedbyaHandler.
*
*@paramtokenTokenobtainedbypreviouslycalling
*{@linkObserver#messageDispatchStarting}onthesameObserverinstance.
*@parammsgThemessagethatwasdispatched.
*/
voidmessageDispatched(Objecttoken,Messagemsg);
/**
*Calledwhenanexceptionwasthrownwhileprocessingamessage.
*
*@paramtokenTokenobtainedbypreviouslycalling
*{@linkObserver#messageDispatchStarting}onthesameObserverinstance.
*@parammsgThemessagethatwasdispatchedandcausedanexception.
*@paramexceptionTheexceptionthatwasthrown.
*/
voiddispatchingThrewException(Objecttoken,Messagemsg,Exceptionexception);
}
Observer 是 Looper 的一个内部接口类,用来做事件的回调处理的。主要包含了三个函数 messageDispatchStarting、messageDispatched、dispatchingThrewException,分别会在某条消息调度前、调度处理后、调度处理过程中发生异常时回调。需要注意的是 messageDispatchStarting 要求返回一个 Object 对象,对类型不做任何限制,每个消息类型都对应一个 token,并且理应为独立且唯一。
于是乎,我兴高采烈的开始码起来了。
开搞开搞先来介绍一下子思路,我们是不是可以直接通过反射调用 setObserver 的方法,是不是直接就可以设置Observer,既然都到了反射,为啥不直接操作 sObserver,说到这就开干。这里使用的动态代理进行 hook Observer。
publicclassHandlerLoopHookHelper{
/**
*HookLooper的sObserver观察消息耗时
*
*@paramcontext上下文
*/
publicstaticvoidhookLooperObserver(@NullableContextcontext){
Log.d(TAG,"hookLoopObserver:"+Build.VERSION.SDK_INT);
try{
@SuppressLint("PrivateApi")finalClasssObserverClass=Class.forName("android.os.Looper$Observer");
finalObserverInvocationinvocation=newObserverInvocation();
finalObjecto=sObserverClass.cast(Proxy.newProxyInstance(sObserverClass.getClassLoader(),newClass[]{sObserverClass},invocation));
finalClasslooperClass=Class.forName("android.os.Looper");
@SuppressLint("BlockedPrivateApi")finalFieldsObserver=looperClass.getDeclaredField("sObserver");
sObserver.setAccessible(true);
sObserver.set(getMainLooper(),
getMainLooper().setMessageLogging(invocation.printer);
}catch(Throwablee){
e.printStackTrace();
}
}
}
publicclassObserverInvocationimplementsInvocationHandler{
privatelongdispatchStart=0;
privatelongdispatchEnd=0;
privatestaticfinallongMESSAGE_WORK_TIME_300=300;
privatestaticfinallongMESSAGE_WORK_TIME_100=100;
privatestaticfinallongMESSAGE_WORK_TIME_50=50;
privatestaticfinallongMESSAGE_WORK_TIME_10=10;
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
if(Looper.getMainLooper().isCurrentThread()){
if("messageDispatchStarting".equals(method.getName())){
returnmessageDispatchStarting();
}elseif("messageDispatched".equals(method.getName())){
messageDispatched((long)args[0],(Message)args[1]);
}elseif("dispatchingThrewException".equals(method.getName())){
dispatchingThrewException((long)args[0],(Message)args[1],(Exception)args[2]);
}
}
returnnull;
}
publicObjectmessageDispatchStarting(){
dispatchStart=SystemClock.uptimeMillis();
finallongtoken=atomicLong.getAndIncrement();
returntoken;
}
publicvoidmessageDispatched(Objecttoken,Messagemsg){
dispatchEnd=SystemClock.uptimeMillis();
getTime(msg,(long)token);
}
publicvoiddispatchingThrewException(Objecttoken,Messagemsg,Exceptionexception){
}
/**
*计时,可根据自己的需求进行详细的计
*/
privatevoidgetTime(Messagemessage,longtoken){
//message指定开始时间,基于开机时间
finallongwhen=message.getWhen();
//等待的时长:开始执行-指定开始时间
finallongwait=dispatchStart-when;
//执行的时长:执行结束时间-执行开始时间
finallongwork=dispatchEnd-dispatchStart;
printRecord(message,token,wait,work);
}
privatevoidprintRecord(Messagemessage,longtoken,longwait,longwork){
finalStringBuilderstringBuilder=newStringBuilder();
if(work=MESSAGE_WORK_TIME_10){
stringBuilder.append("(可忽略)").append(dispatchStart).append("-").append(dispatchEnd)
.append("token:").append(token).append(message.toString())
.append("wait:").append(wait).append("ms")
.append("work:").append(work).append("ms");
Log.v(TAG,stringBuilder.toString());
}elseif(work=MESSAGE_WORK_TIME_50){
stringBuilder.append("(看看就好)").append(dispatchStart).append("-").append(dispatchEnd)
.append("token:").append(token).append(message.toString())
.append("wait:").append(wait).append("ms")
.append("work:").append(work).append("ms");
Log.i(TAG,stringBuilder.toString());
}elseif(work=MESSAGE_WORK_TIME_100){
stringBuilder.append("(需要关注一下)").append(dispatchStart).append("-").append(dispatchEnd)
.append("token:").append(token).append(message.toString())
.append("wait:").append(wait).append("ms")
.append("work:").append(work).append("ms");
Log.w(TAG,stringBuilder.toString());
}elseif(work=MESSAGE_WORK_TIME_300){
stringBuilder.append("(需要处理啦~)").append(dispatchStart).append("-").append(dispatchEnd)
.append("token:").append(token).append(message.toString())
.append("wait:").append(wait).append("ms")
.append("work:").append(work).append("ms");
Log.d(TAG,stringBuilder.toString());
}else{
stringBuilder.append("(这个就超级严重啦~)").append(dispatchStart).append("-").append(dispatchEnd)
.append("token:").append(token).append(message.toString())
.append("wait:").append(wait).append("ms")
.append("work:").append(work).append("ms");
Log.e(TAG,stringBuilder.toString());
}
}
}
Coding 完毕啦,这里面的输出监控建议不要搞到线上,可能会比较的耗时,根据实际情况来确认如何使用,定制监控的策略,可以考虑超过多长时间来进行报警,或者上报等操作。废话不多说,run 一下子吧,结果gg。
日志的大致意思是,这个类是被 @hide 的,对开发者是屏蔽调用及使用的。查阅相关代码得知,基于 Android 10 版本增加的 Observer,在设计之初就只是为了观察并统计系统服务的Looper消息调度性能(可以查阅LooperStats与LooperStatsService),所以这个API只给自己内部使用。
只要是代码总会有解决的方案,能不能绕过去隐藏 API 呐?能,下来就看看看一下如何如绕开隐藏API
。
绕开 Observer 隐藏 API解决 Hidden API 的方式有很多种,可能某些在 Android 高版本系统重被官方封堵。不过由于 Android 系统是开源的,所以无论怎么封堵,还是可以通过其他的方式绕过,毕竟系统是不会限制用户修改自身进程的内存。
在这里我使用的方案是 FreeReflection,大致的思路是将自己伪装成系统类,然后就可以调用这些私有 API 了。具体的内容可以参考作者博客里面的介绍。
另一种绕过 Android P以上非公开API限制的办法
Android 14 无法加载.dex文件在这里提个醒,在Android 14及以上版本,对于动态代码的加载发生了一些更改,否则会发生如下内容错误。
通过阅读 DexClassLoader 与 更安全的动态代码加载 发现 Android 14 对于动态加载 .dex 文件做了安全策略的限制,Android 14 对于动态加载(DCL)功能,必须将所有动态加载的文件标记为只读。否则,系统将会抛出异常。
正确操作姿势,就是将 FreeReflection 三方库 Copy 下来,然后修改源码,完美解决在 Android 14 上面不能动态加载 .dex 问题。
然后再跑一下子。完美解决。
正确的打开姿势publicclassHandlerLoopHookHelper{
privatestaticvolatilebooleanisHooked=false;
privatestaticfinalStringTAG="HandlerLoopHookHelper";
privatestaticvoidcheckHooked(Contextcontext){
if(!isHooked){
Reflection.unseal(context);
isHooked=true;
}
}
/**
*HookLooper的sObserver观察消息耗时
*
*@paramcontext上下文
*/
publicstaticvoidhookLooperObserver(@NullableContextcontext){
Log.d(TAG,"hookLoopObserver:"+Build.VERSION.SDK_INT);
try{
checkHooked(context);
@SuppressLint("PrivateApi")finalClasssObserverClass=Class.forName("android.os.Looper$Observer");
finalObserverInvocationinvocation=newObserverInvocation();
finalObjecto=sObserverClass.cast(Proxy.newProxyInstance(sObserverClass.getClassLoader(),newClass[]{sObserverClass},invocation));
finalClasslooperClass=Class.forName("android.os.Looper");
@SuppressLint("BlockedPrivateApi")finalFieldsObserver=looperClass.getDeclaredField("sObserver");
sObserver.setAccessible(true);
sObserver.set(getMainLooper(),
getMainLooper().setMessageLogging(invocation.printer);
}catch(Throwablee){
e.printStackTrace();
}
}
/**
*钩针活套观察者
*
*@paramcontext上下文
*/
publicstaticvoidunHookLooperObserver(@NullableContextcontext){
Log.d(TAG,"unHookLooperObserver:"+Build.VERSION.SDK_INT);
try{
checkHooked(context);
@SuppressLint("PrivateApi")finalClasssObserverClass=Class.forName("android.os.Looper$Observer");
finalObserverInvocationinvocation=newObserverInvocation();
finalObjecto=sObserverClass.cast(Proxy.newProxyInstance(sObserverClass.getClassLoader(),newClass[]{sObserverClass},invocation));
finalClasslooperClass=Class.forName("android.os.Looper");
@SuppressLint("BlockedPrivateApi")finalFieldsObserver=looperClass.getDeclaredField("sObserver");
sObserver.setAccessible(true);
sObserver.set(getMainLooper(),null);
getMainLooper().setMessageLogging(null);
}catch(Throwablee){
e.printStackTrace();
}
}
/**
*挂钩主活套消息空闲处理程序
*
*@paramcontext上下文
*/
publicstaticvoidhookMainLooperMessageIdleHandlers(@NullableContextcontext){
Log.d(TAG,"hookMainLooperMessageIdleHandlers:"+Build.VERSION.SDK_INT);
try{
checkHooked(context);
finalClasslooperClass=Class.forName("android.os.Looper");
finalFieldmQueueF=looperClass.getDeclaredField("mQueue");
mQueueF.setAccessible(true);
finalObjectmainMessageQueue=mQueueF.get(getMainLooper());
finalClassmQueueClass=Class.forName("android.os.MessageQueue");
finalFieldmainIdleHandlerF=mQueueClass.getDeclaredField("mIdleHandlers");
mainIdleHandlerF.setAccessible(true);
finalObjecto=mainIdleHandlerF.get(mainMessageQueue);
finalArrayListMessageQueue.IdleHandlermIdleHandlers=(ArrayListMessageQueue.IdleHandler)
Log.d(TAG,"hookMainLooperMessageIdleHandlerssize:"+mIdleHandlers.size());
for(MessageQueue.IdleHandlermIdleHandler:mIdleHandlers){
Log.d(TAG,"hookMainLooperMessageIdleHandlerscontentis:"+mIdleHandler.toString());
}
}catch(Throwablee){
e.printStackTrace();
}
}
}
小结Observer 相比于 Printer 可以直接拿到 Message 对象,并且不需要设置 Printer 可以避免每个消息调度时额外拼接字符串的成本。解决开发阶段 Hidden API 访问限制,可以通过很多种方式绕过,也可以考虑通过 CompileOnly 一个 假工程 来实现,假工程 里面模拟相应的系统源码,从而实现 Observer 类的访问。不过我试过这种方法,同样也需要搞定系统的 hidden api 貌似也是绕不过去的,如果使用这个库 FreeReflection 的话,那么 假工程 的方式也是可以实现,在这里不做赘述,感兴趣的同学可以自己尝试。Observer 机制是在 Anroid 10 开始添加的,因此低版本还是需要用 Printer 的方式进行监听Android 14 版本动态加载 .dex 文件需要设置 file 为只读模式,否则将会抛出安全异常。
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线