布景
在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, Message msg, 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, Message msg, 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, Message msg, 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, Message msg, Exception exception) {
observer.dispatchingThrewException(token, msg, 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的方法进行监听
最后对APM方向感兴趣的同学能够关注下我的功能监控专栏: /column/7107… ,历史文章:
文章 | 地址 |
---|---|
监控Android Looper Message调度的另一种姿势 | /post/713974… |
Android 高版别采集体系CPU运用率的方法 | /post/713503… |
Android 平台下的 Method Trace 完结及应用 | /post/710713… |
Android 怎样处理运用SharedPreferences 形成的卡顿、ANR问题 | /post/705476… |
根据JVMTI 完结功能监控 | /post/694278… |