作者:卓_修武
转载地址:https://juejin.cn/post/7139741012456374279
背景
在Android 10版本,系统为Looper类添加了专门的 Observer类用来观测Looper的消息调度。因此除了通过设置Looper对象的 printer属性外,也可以通过设置Looper类的Observer属性来实现监控,然而该功能在设计之初就只是为了观测并统计系统服务的Looper消息调度性能 (其系统使用见LooperStats类 及 LooperStatsService类 ),因此其Looper.Observer类及其相关API都被标记为 @hidden ,对开发者屏蔽其相关API的使用。
本文主要分享使用Looper Observer 进行消息调度观测过程中的遇到的问题及解决方式。
Looper Observer 源码实现
class Looper{
// sObserver 为静态成员变量
private static Observer sObserver;
//mLogging 为成员变量
@UnsupportedAppUsage
private Printer mLogging;
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
// 获取消息
Message msg = queue.next(); // might block
// This must be in a local variable,in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
// logging 可以通过判断其文本开头为 >>>> 认定为消息开始处理
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
Object token = null;
if (observer != null) {
//调用messageDispatchStarting,通知observer 消息开始 某条消息开始处理
// messageDispatchStarting 需要返回一个唯一标识的token,//在消息处理结束 回调messageDispatched时,会将这个token作为参数,
// 开发者通过这个token 将Message关联起来
token = observer.messageDispatchStarting();
}
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
//通知 observer消息 调度完成,并传入 token 和msg
observer.messageDispatched(token,msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) { //通知observer消息处理发生异常
observer.dispatchingThrewException(token,msg,exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//设置logging的方式监控消息调度时,通过判断 <<<<字符 判断消息处理结束
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
/**
* Set the transaction observer for all Loopers in this process.
*
* @hide
*/
public static void setObserver(@Nullable Observer observer) {
sObserver = observer;
}
/** {@hide} */
public interface Observer {
/**
* Called right before a message is dispatched.
*
* <p> The token type is not specified to allow the implementation to specify its own type.
*
* @return a token used for collecting telemetry when dispatching a single message.
* The token token must be passed back exactly once to either
* {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
* and must not be reused again.
*
*/
Object messageDispatchStarting();
/**
* Called when a message was processed by a Handler.
*
* @param token Token obtained by previously calling
* {@link Observer#messageDispatchStarting} on the same Observer instance.
* @param msg The message that was dispatched.
*/
void messageDispatched(Object token,Message msg);
/**
* Called when an exception was thrown while processing a message.
*
* @param token Token obtained by previously calling
* {@link Observer#messageDispatchStarting} on the same Observer instance.
* @param msg The message that was dispatched and caused an exception.
* @param exception The exception that was thrown.
*/
void dispatchingThrewException(Object token,Message msg,Exception exception);
}
}
我们先了解下 Observer 接口的API设计,该接口包含三个函数 messageDispatchStarting、messageDispatched、dispatchingThrewException, 分别会在 某条消息被调度前、调度处理后、及消息调度处理过程中发生异常时回调。 注意messageDispatchingStarting 要求返回一个Object对象,对类型不做限制,这样开发者可以返回自己定义的类型,这个Object对象被当做一个token,每个消息的调度过程都应该对应一个单独的token实例。 我们可以参考下 系统的 LooperStats 在这块是如何实现的。
/**
* Collects aggregated telemetry data about Looper message dispatching.
*
* @hide Only for use within the system server.
*/
public class LooperStats implements Looper.Observer {
// DispatchSesion 缓存池,避免重复创建对象
private final ConcurrentLinkedQueue<DispatchSession> mSessionPool =
new ConcurrentLinkedQueue<>();
// Token实现
// 其记录了消息开始的 时钟时间(只能精确到秒)、thread cpu time()、及boot elapsed time
// 并在消息调度结束时,通过计算时间差 统计到消息处理的 cputime、调度延迟、时钟时间差等数据
private static class DispatchSession {
static final DispatchSession NOT_SAMPLED = new DispatchSession();
public long startTimeMicro;
public long cpuStartMicro;
public long systemUptimeMillis;
}
@Override
public Object messageDispatchStarting() {
if (deviceStateAllowsCollection() && shouldCollectDetailedData()) {
DispatchSession session = mSessionPool.poll();
session = session == null ? new DispatchSession() : session;
//记录消息开始处理的微妙时间 SystemClock.elapsedRealtimeNanos/1000
session.startTimeMicro = getElapsedRealtimeMicro();
//记录线程时间
session.cpuStartMicro = getThreadTimeMicro();
//记录当前时钟时间
session.systemUptimeMillis = getSystemUptimeMillis();
return session;
}
return DispatchSession.NOT_SAMPLED;
}
@Override
public void messageDispatched(Object token,Message msg) {
if (!deviceStateAllowsCollection()) {
return;
}
DispatchSession session = (DispatchSession) token;
Entry entry = findEntry(msg,/* allowCreateNew= */session != DispatchSession.NOT_SAMPLED);
if (entry != null) {
synchronized (entry) {
entry.messageCount++;
if (session != DispatchSession.NOT_SAMPLED) {
entry.recordedMessageCount++;
//统计消息处理的时间(微妙)
final long latency = getElapsedRealtimeMicro() - session.startTimeMicro;
//统计cpu时间
final long cpuUsage = getThreadTimeMicro() - session.cpuStartMicro;
entry.totalLatencyMicro += latency;
entry.maxLatencyMicro = Math.max(entry.maxLatencyMicro,latency);
entry.cpuUsageMicro += cpuUsage;
entry.maxCpuUsageMicro = Math.max(entry.maxCpuUsageMicro,cpuUsage);
if (msg.getWhen() > 0) {
//统计消息处理延迟
final long delay = Math.max(0L,session.systemUptimeMillis - msg.getWhen());
entry.delayMillis += delay;
entry.maxDelayMillis = Math.max(entry.maxDelayMillis,delay);
entry.recordedDelayMessageCount++;
}
}
}
}
recycleSession(session);
}
@Override
public void dispatchingThrewException(Object token,Exception exception) {
if (!deviceStateAllowsCollection()) {
return;
}
DispatchSession session = (DispatchSession) token;
Entry entry = findEntry(msg,/* allowCreateNew= */session != DispatchSession.NOT_SAMPLED);
if (entry != null) {
synchronized (entry) {
entry.exceptionCount++;
}
}
recycleSession(session);
}
}
可以看到其创建了一个专门 DispatchSession类,并通过几个字段(startTimeMicro、cpuStartMicro、systemUptimeMillis)用来记录消息开始的一些时间信息,如时钟时间、cpuTime、等,并在消息调度结束时(回调 messageDispatched),计算其相应的时间差,统计其性能。
另外需要注意的是 mLogging 是成员变量,而 sObserver是静态变量,因此设置 sObserver后 会监听到所有Looper的消息调度信息,如果只想监控主线程的消息调度,还需要判断下线程,我们可以在当前线程为主线程情况下 返回 token,而非主线程直接返回一个null,这样在相应回调函数里(messageDispatched),当发现token为null时,直接返回不做处理即可。 采用Observer的方式 可以拿到调度的Message对象,而 Printer的方式只能拿到系统拼接的字符串信息,因此从性能上来说,Observer的方式会更优一些。
编译问题解决
首先,我们要做的当然是实现一个 Observer实例,然而,由于Observer类被标记为@hidden,由于 android gradle插件的限制 我们无法直接访问该类,IDEA 直接报红了。
这里需要知道一个小知识,hidden api 进行API访问 限制分为两部分,一部分为了避免开发者在编码阶段使用 hidden api 其会在IDEA上下功夫,在开发阶段就限制我们的访问 ,另一部分是在虚拟机层面,在运行时限制访问。 在开发阶段有些时候我们可以通过反射进行相应API的调用,然而,我们目前遇到的问题是 Looper.Observer是个接口,我们需要编写相应的实现类,这可没法通过反射的方式进行调用。 但其实,这些开发阶段的限制都只是个“障眼法”,我们只要通过其他方式能够提前编译出 符合继承 Looper.Observer的类的字节码即可。
归根到底,Looper.Observer也只是一个普通的Java类,我们可以在一个 Java模块中 创建一个同名的类,然后编写相应继承这个类的实现,提前编译好相应的jar(aar) 就可以了。
那么马上实践一下,我们首先创建一个 纯java模块 fake-android,并在其模块中创建 同名的Looper类及Observer抽象接口
要注意,我们并不希望把这个 fake-android最终引入到我们的apk中,因此需要再创建另一个模块,并依赖fake-android模块 (依赖方式为 compileOnly,这样在其maven pom依赖关系上,并不存在 fake-android)。
这里我们创建一个Java模块 free-android,在这个模块中 引入 fake-android模块的依赖,并在该模块中实现 Looper.Observer继承类 LooperObserver。
最后,在我们最终需要使用Looper.Observer的模块中比如 (apm模块) 引入 free-andorid依赖,并使用LooperObserver。
implementation project(path:":free-android")
复制代码
import com.knightboost.freeandroid.LooperObserver;
public class MyLooperObserver implements LooperObserver {
@Override
public Object messageDispatchStarting() {
return null;
}
@Override
public void messageDispatched(Object token,Message msg) {
}
@Override
public void dispatchingThrewException(Object token,Exception exception) {
}
}
然而,第一次尝试就失败了,编译时,抛出了了 class file for android.os.Looper$Observer not found** 的异常。
目前的工程结果如下所示:
这里对于编译失败的原因,我并没有深入编译流程进行分析,而是根据以往的经验来判断。我的分析是这样的,目前相关类的继承体系是这样的: MyLooperObserver <- LooperObserver <- android.os.Looper.Observer ,由于MyLooperObserver还是Java源代码,因此模块编译过程需要将MyLooperObserver编译为class类,而MyLooperObserver类所属的模块是android模块,而LooperObserver是java工程模块,虽然free-android模块可以顺利编译通过,但是 APM模块并不能编译通过,因为其模块类型为 android-library,根据类的继承体系,在编译MyLooperObserver过程中,其最终还是需要查找到Looper.Observer类,而因为android工程模块对hidden class的限制,导致最终编译失败了。
那么解决这个问题的方向是,需要保证:我们的apm模块在编译过程中,不直接依赖于任何继承 android.os.Looper.Observer类。 因此我们需要将对hidden class的依赖限定在 free-android模块中,解决方案如下:
首先在 free-andorid模块中,创建一个和 android.os.Looper.Observer 相同函数的接口,但这个接口不继承 之前的 android.os.Looper.Observer
package com.knightboost.freeandroid;
import android.os.Message;
public interface LooperMessageObserver {
Object messageDispatchStarting();
void messageDispatched(Object token,Message msg);
void dispatchingThrewException(Object token,Exception exception);
}
在 free-android模块中,创建一个工具类LooperUtil,及其静态函数 setObserver(LooperMessageObserver observer),这个API 暴露给android项目工程,在这个静态函数内部实现中 实现对系统Looper类 observer的设置
package com.knightboost.freeandroid;
import android.os.Looper;
import android.os.Message;
public class LooperUtil {
public static void setObserver(final LooperMessageObserver observer) {
Looper.setObserver(new Looper.Observer() {
@Override
public Object messageDispatchStarting() {
return observer.messageDispatchStarting();
}
@Override
public void messageDispatched(Object token,Message msg) {
observer.messageDispatched(token,msg);
}
@Override
public void dispatchingThrewException(Object token,Exception exception) {
observer.dispatchingThrewException(token,exception);
}
});
}
}
可以看到,在这个函数中,我们将Looper.Observer的相应函数回调原封不动的代理给传入的observer参数。
此时整体工程如下所示:
这样 我们的android模块中的MyLooperObserver只依赖了 LooperMessageObserver类,而LooperMessageObserver这个类并不继承android系统的Looper.Observer,从而保证了android-library工程顺利编译通过
hidden API限制
讲过上个小节,我们的监控代码基本实现了,但是由于虚拟机对 hidden API 的限制,在运行时,会抛出NotFound的异常
解决Hidden API的方式有很多种方式,某些方式在Android高版本系统中可能会被官方封堵。 不过由于Android系统是开源的,所以无论怎么封堵,还是有其他的方式可以绕过,毕竟系统不会限制用户修改自身进程的内存。本文使用的三方库为 FreeReflection ,还有一些其他的实现方案,可以学习下各种花式操作,如、AndroidHiddenApiByPass 、bypassHiddenApiRestriction 、Republic
再绕过 Hidden API限制后,我们的demo 成功运行了
总结
- Observer的方式相比Printer可以直接拿到Message对象,并且不设置Printer可以避免每个消息调度时额外的字符串拼接成本,性能更优
- 解决开发阶段 hidden API的访问限制,可以通过 compile only 一个fake-andorid工程来实现,在 fake-andorid工程中模拟相应的系统源代码,从而实现对 Observer类的访问
- 在 Android模块中 ,间接继承了一个hidden class,android模块项目还是会编译失败,此时我们可以用 代理对象的方式,不直接依赖 hidden class,曲线救国,让工程顺利编译通过
- Looper的 sObserver (class Observer)是静态成员变量,这和 mLogging (class Printer)不同 ,因此注册Observer后,会接收到所有Looper实例的消息调度,因此在回调中需要进行线程判断
- Observer机制是在Anroid 10开始添加的,因此低版本还是需要用Printer的方式进行监听
为了帮助到大家更好的掌握 Framework 底层与性能优化相关知识点,这准备了 Android11.0最新Framework解析 与 性能优化知识点汇总和Android 性能监控框架 的学习文档,中间记录了 AMS、PMS、WMS、Hander、Binder、启动优化、内存优化、UI优化……等知识点,可谓是很全面了,↓↓↓
有需要的可以复制下方链接,传送直达!!!
https://qr21.cn/CaZQLo?BIZ=ECOMMERCE
内功心法不是一天两天就可以修炼出来的,而是需要每天的坚持,技术提升也是如此。所以最好的速成修炼方法就是每天学习一点,日积月累后就会发现自己进步的效果。
原文地址:https://blog.csdn.net/weixin_61845324
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。