布景

在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 直接报红了。

监控Android Looper Message调度的另一种姿势

这里需求知道一个小知识,hidden api 进行API拜访 约束分为两部分,一部分为了防止开发者在编码阶段运用 hidden api 其会在IDEA上下功夫,在开发阶段就约束咱们的拜访 ,另一部分是在虚拟机层面,在运行时约束拜访。 在开发阶段有些时候咱们能够经过反射进行相应API的调用,然而,咱们现在遇到的问题是 Looper.Observer是个接口,咱们需求编写相应的完结类,这可没法经过反射的方法进行调用。 但其实,这些开发阶段的约束都只是个“障眼法”,咱们只需经过其他方法能够提早编译出 契合承继 Looper.Observer的类的字节码即可。

归根结底,Looper.Observer也只是一个普通的Java类,咱们能够在一个 Java模块中 创立一个同名的类,然后编写相应承继这个类的完结,提早编译好相应的jar(aar) 就能够了。

那么马上实践一下,咱们首先创立一个 纯java模块 fake-android,并在其模块中创立 同名的Looper类及Observer抽象接口

监控Android Looper Message调度的另一种姿势

要注意,咱们并不希望把这个 fake-android终究引入到咱们的apk中,因而需求再创立另一个模块,并依靠fake-android模块 (依靠方法为 compileOnly,这样在其maven pom依靠关系上,并不存在 fake-android)。

监控Android Looper Message调度的另一种姿势

这里咱们创立一个Java模块 free-android,在这个模块中 引入 fake-android模块的依靠,并在该模块中完结 Looper.Observer承继类 LooperObserver。

监控Android Looper Message调度的另一种姿势

最后,在咱们终究需求运用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 的反常。

现在的工程成果如下所示:

监控Android Looper Message调度的另一种姿势

这里关于编译失利的原因,我并没有深入编译流程进行剖析,而是依据以往的经历来判别。我的剖析是这样的,现在相关类的承继体系是这样的: 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 Looper Message调度的另一种姿势

这样 咱们的android模块中的MyLooperObserver只依靠了 LooperMessageObserver类,而LooperMessageObserver这个类并不承继android体系的Looper.Observer,然后确保了android-library工程顺畅编译经过

hidden API约束

讲过上个小节,咱们的监控代码根本完结了,可是因为虚拟机对 hidden API 的约束,在运行时,会抛出NotFound的反常

监控Android Looper Message调度的另一种姿势

处理Hidden API的方法有很多种方法,某些方法在Android高版别体系中或许会被官方封堵。 不过因为Android体系是开源的,所以不管怎样封堵,仍是有其他的方法能够绕过,毕竟体系不会约束用户修改自身进程的内存。本文运用的三方库为 FreeReflection ,还有一些其他的完结方案,能够学习下各种花式操作,如、AndroidHiddenApiByPass 、bypassHiddenApiRestriction 、Republic

再绕过 Hidden API约束后,咱们的demo 成功运行了

监控Android Looper Message调度的另一种姿势

总结

  • 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…