美文网首页
云米客户端APM性能监控组件分析(二)

云米客户端APM性能监控组件分析(二)

作者: 捉影T_T900 | 来源:发表于2020-04-09 15:54 被阅读0次

    上一篇文章已经分析了Activity的生命周期耗时监控原理
    https://www.jianshu.com/p/f9a5172e858d

    现在来分析监控卡顿的原理和技巧。

    问题一:为什么手机在处理任务的时候会卡顿?

    这就涉及到Android这个手机系统的消息通知机制了,整个Android系统就想一个巨大的用不关门的邮局,里面有一个分发机制,当收到一个message会立马进行转发,如果等待分发的时间过长,就会出现我们熟知的“等待”、“关闭”弹窗,触发了ANR。要等多久?5秒。

    这个巨大的用不停歇的邮局由Looper、Handler、MessageQueue、Message这四个对象组成,这里只监听主线程的卡顿信息,不做子线程思维发散。


    机制原理.png

    具体运转原理请查阅相关资料,这里只分析应该从哪里入手监听卡顿发生的时机。

    Looper对象内部有一个mLogging的Printer对象,在loop()方法内部,消息分发前会打印一句“>>>>> Dispatching”,在消息分发后也会打印一个“<<<<< Finished”

    public static void loop() {
    ...
             for (;;) {
    ...
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    ...
               if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
              }
    ...
    }
    

    根据这个特点可以想出一个好办法。Looper对象有一个方法叫【setMessageLogging()】,可以设置自己的Log对象,然后重写println方法。以下是核心实现

        private Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (!isCanWork()) {
                    return;
                }
    
                String stackInfo = CommonUtils.getStack();
    
                if (Manager.isDebug()) {
                    ApmLogX.d(APM_TAG, SUB_TAG, "thread stack info:" + stackInfo);
                }
                saveBlockInfo(stackInfo);
            }
        };
    
                // 监听主线程的Looper分发消息入口
                Looper.getMainLooper().setMessageLogging(new Printer() {
    
                    // 这里通过设置新的Printer来hook Looper的消息遍历操作,监听分发消息开始和结束关键字
                    private static final String START = ">>>>> Dispatching";
                    private static final String END = "<<<<< Finished";
    
                    @Override
                    public void println(String x) {
                        if (x.startsWith(START)) {
                            mHandler.postDelayed(runnable, TaskConfig.DEFAULE_BLOCK_TIME);
                        }
                        if (x.startsWith(END)) {
                            mHandler.removeCallbacks(runnable);
                        }
                    }
                });
    

    通过判断start和end的字符串,判断是否需要触发runnable对象执行任务,DEFAULE_BLOCK_TIME我默认设置4.5秒,因为5秒会响应系统自己的ANR弹窗。如果两个打印时间间隔在4.5秒内,则认为不卡顿,清理runnable对象;如果4.5秒内没有收到end字符串,则认为卡顿,会触发runnable运行。在runnable内执行保存卡顿信息的操作。

    问题二:我现在知道什么时候卡顿了,但卡顿信息从哪里获取?

    Android系统对每一个APP都独立维护了一个进程,每一个进程至少有一个主线程来运行程序任务,所以可以从主线程的堆栈信息中获得卡顿时的整个方法调用链。

        public static String getStack() {
            StringBuilder sb = new StringBuilder();
            StackTraceElement[] traceElements = Looper.getMainLooper().getThread().getStackTrace();
            for (StackTraceElement element : traceElements) {
                sb.append(element.toString() + "\n");
            }
    
            return sb.toString();
        }
    

    其余的额外信息按需获取。卡顿信息获取就是这么简单。

    未完待续......

    相关文章

      网友评论

          本文标题:云米客户端APM性能监控组件分析(二)

          本文链接:https://www.haomeiwen.com/subject/novryctx.html