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


    背景

    在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.
             *
             * 

    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); } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    我们先了解下 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 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);
        }
    
    
    
    
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    可以看到其创建了一个专门 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)。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0R0D7IcK-1662434541230)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d8ed673d9e64413a8afb19502653830b~tplv-k3u1fbpfcp-zoom-1.image)]

    这里我们创建一个Java模块 free-android,在这个模块中 引入 fake-android模块的依赖,并在该模块中实现 Looper.Observer继承类 LooperObserver。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgdiuiOj-1662434541230)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4c87193edbc945a9846f4866b135d200~tplv-k3u1fbpfcp-zoom-1.image)]

    最后,在我们最终需要使用Looper.Observer的模块中比如 (apm模块) 引入 free-andorid依赖,并使用LooperObserver。

        implementation project(path:":free-android")
    
    • 1
    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) {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    然而,第一次尝试就失败了,编译时,抛出了了 ****class file for android.os.Looper$Observer not found 的异常。

    目前的工程结果如下所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6UMX3FY-1662434541231)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/80faee4bc16e41508b5097dc048daa35~tplv-k3u1fbpfcp-zoom-1.image)]

    这里对于编译失败的原因,我并没有深入编译流程进行分析,而是根据以往的经验来判断。我的分析是这样的,目前相关类的继承体系是这样的: 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在 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);
                }
            });
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    可以看到,在这个函数中,我们将Looper.Observer的相应函数回调原封不动的代理给传入的observer参数。

    此时整体工程如下所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J4uAEWPJ-1662434541231)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/75d5cdc66cdb4d26a4308c0fdea527d9~tplv-k3u1fbpfcp-zoom-1.image)]

    这样 我们的android模块中的MyLooperObserver只依赖了 LooperMessageObserver类,而LooperMessageObserver这个类并不继承android系统的Looper.Observer,从而保证了android-library工程顺利编译通过

    hidden API限制

    讲过上个小节,我们的监控代码基本实现了,但是由于虚拟机对 hidden API 的限制,在运行时,会抛出NotFound的异常

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1b5FeLv-1662434541232)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/46d755ba43664b1794191783594718ed~tplv-k3u1fbpfcp-zoom-1.image)]

    解决Hidden API的方式有很多种方式,某些方式在Android高版本系统中可能会被官方封堵。 不过由于Android系统是开源的,所以无论怎么封堵,还是有其他的方式可以绕过,毕竟系统不会限制用户修改自身进程的内存。本文使用的三方库为 FreeReflection ,还有一些其他的实现方案,可以学习下各种花式操作,如、AndroidHiddenApiByPassbypassHiddenApiRestrictionRepublic

    再绕过 Hidden API限制后,我们的demo 成功运行了

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xdp9iFar-1662434541232)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19cf789c49254a32b3c4360ee1db71d4~tplv-k3u1fbpfcp-zoom-1.image)]

    总结

    • 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方向感兴趣的同学可以关注下我的性能监控专栏: https://juejin.cn/column/7107136594582175758 ,历史文章:

    文章地址
    Android 高版本采集系统CPU使用率的方式https://juejin.cn/post/7135034198158475300
    Android 平台下的 Method Trace 实现及应用https://juejin.cn/post/7107137302043820039
    Android 如何解决使用SharedPreferences 造成的卡顿、ANR问题https://juejin.cn/post/7054766647026352158
    基于JVMTI 实现性能监控https://juejin.cn/post/6942782366993612813
  • 相关阅读:
    C++中使用递归函数
    Java后端开发(六)-- 二维码的生成
    Jenkins+GitLab远程部署SpringCloud微服务
    操作系统性能参数调优
    【2022】前端面试之vue汇总
    JavaScript对象详解,js对象属性的添加
    ipfs 分布式储存说明
    SpEl简单使用
    计算机毕业设计 基于SSM的民宿推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解
    商城小程序开发哪家好?应该怎么选择商城小程序开发系统呢?
  • 原文地址:https://blog.csdn.net/zhuoxiuwu/article/details/126721408