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

6 篇文章 0 订阅

背景

在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)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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")
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 的异常。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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);
}

在 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参数。

此时整体工程如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卓修武

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值