美文网首页
腾讯 Apm 框架 Matrix 源码阅读 - TracePlu

腾讯 Apm 框架 Matrix 源码阅读 - TracePlu

作者: 河里的枇杷树 | 来源:发表于2020-03-28 20:32 被阅读0次

    版本

    v0.6.5

    温馨提示

    TracePlugin 是比较复杂的,很多东西文章中可能讲的不是很清楚,配合 推荐 Matrix 源码完整注释
    可能会有更好的效果

    概述

    本篇文章是 腾讯开源的 APM 框架 Matrix 系列文章的第三篇,将对 matrix-trace-canary这个模块架构进行解析。这个模块中包含了帧率(FPS)检测,启动时间检测(APP启动和Activity启动),ANR检测,慢函数检测,这四个Tracer,后面我们会一一进行分析。上一篇为腾讯 Apm 框架 Matrix 源码阅读 - 架构解析

    先看一下类图及类功能的简介。

    TraceCanary架构图 .jpg
    • LooperMonitor:通过给主线程Looper设置Printer来监控 Looper分发事件的开始和结束。
    • UIThreadMonitor:反射 Choreographer并获得每一个状态的 CallbackQueue对象 和 mFrameIntervalNanos属性值,配合 LooperMonitor使 LooperObserver具有感知每帧 开始,执行,结束具体时间的能力。
    • LooperObserver:具有感知每个Message开始,执行,结束具体时间的能力。
    • AppMethodBeat:还记得在 腾讯 Apm 框架 Matrix 源码阅读 - gradle插件中说了,在编译器Matrix会对复杂方法进行字节码插桩,那插桩的是什么内容呢?就是在复杂方法的入口插入 AppMethodBeat.i(),方法的出口插入AppMethodBeat.O()方法。在Activity的中插入 AppMethodBeat.at()方法。这个类主要作用就是收集各个方法的methodId和执行时间并存放在自己的 sBuffer变量中,同时AppMethodBeat也能感知到某个activity是否获取到焦点,具体功能主要是在at方法中。
    • IAppForeground:在 腾讯 Apm 框架 Matrix 源码阅读 - 架构解析 中提到过。可以感知 APP 进入前台还是 退出到后台
    • ITracer: 继承了 IAppForeground 是所有Tracer的父接口,具有 isAlive(),onStartTrace(),onCloseTrace()这三个抽象方法。
    • Tracer:是所有Tracer的直接父类,实现了ITracer接口继承了LooperObserver类,通过上面说的Tracer天然具有 感知APP 进入前台还是 退出到后台的能力和感知每个Message 开始,执行,结束具体时间的能力。

    下面我们就从TracePlugin这个入口类开始看。

    1. TracePlugin

    先看一下TracePlugin的构造方法,没什么可以说的,就是将配置传进来并进行保存,配置中记录了那些Tracer可用那些不可用,还记录了我们设置的SplashActivity

        public TracePlugin(TraceConfig config) {
            this.traceConfig = config;
        }
    
      public class TraceConfig implements IDefaultConfig {
       ....
        public IDynamicConfig dynamicConfig;
        public boolean defaultFpsEnable;
        public boolean defaultMethodTraceEnable;
        public boolean defaultStartupEnable;
        public boolean defaultAnrEnable;
        public boolean isDebug;
        public boolean isDevEnv;
        public String splashActivities;//可配置多个用 ; 隔开,但是生效的还是只有第一个
        public Set<String> splashActivitiesSet;
       ....
    }
    

    1.1 TracePlugin.init()

    腾讯 Apm 框架 Matrix 源码阅读 - 架构解析中讲过每个Plugin都具有一些生命周期方法,那么下来就依次看看TracePlugin在这些生命周期中做了那些事情。首先是 init()方法,可以看到init()方法中首先判断当前SDK的版本是否小于16如果小于16就直接返回了,如果大于等于16就创建各个Tracer。这里顺便说一下 之所以把插件的最低支持版本设置为16 是因为Choreographer.getInstance()方法是Api16才提供的

        @Override
        public void init(Application app, PluginListener listener) {
            //APi小于16 不支持
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported");
                unSupportPlugin();
                return;
            }
    
            anrTracer = new AnrTracer(traceConfig);
    
            frameTracer = new FrameTracer(traceConfig);
    
            evilMethodTracer = new EvilMethodTracer(traceConfig);
    
            startupTracer = new StartupTracer(traceConfig);
        }
    

    1.2 TracePlugin.start()

     @Override
        public void start() {
            super.start();
           ....
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    //初始化 UIThreadMonitor
                    if (!UIThreadMonitor.getMonitor().isInit()) {
                        try {
                            //详见【2.1】
                            UIThreadMonitor.getMonitor().init(traceConfig);
                        } catch (java.lang.RuntimeException e) {
                            return;
                        }
                    }
                    //启动 AppMethodBeat 详见【3.1】
                    AppMethodBeat.getInstance().onStart();
                    //启动 UIThreadMonitor 详见【2.9】
                    UIThreadMonitor.getMonitor().onStart();
                    anrTracer.onStartTrace();
                    frameTracer.onStartTrace();
                    evilMethodTracer.onStartTrace();
                    startupTracer.onStartTrace();
                }
            };
    
            if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
                runnable.run();
            } else {
                //post到主线程启动
                MatrixHandlerThread.getDefaultMainHandler().post(runnable);
            }
    
        }
    

    start()方法中主要是在主线程中启动 UIThreadMonitor,AppMethodBeat还有各个Tracer

    1.3 TracePlugin.stop()

        @Override
        public void stop() {
            .....
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    //详见【3.2】
                    AppMethodBeat.getInstance().onStop();
                    UIThreadMonitor.getMonitor().onStop();
                    anrTracer.onCloseTrace();
                    frameTracer.onCloseTrace();
                    evilMethodTracer.onCloseTrace();
                    startupTracer.onCloseTrace();
                }
            };
    
            if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
                runnable.run();
            } else {
                MatrixHandlerThread.getDefaultMainHandler().post(runnable);
            }
        }
    

    stop()方法中主要是在主线程中停止UIThreadMonitor,AppMethodBeat还有各个Tracer

    1.4 TracePlugin.onForeground()

        @Override
        public void onForeground(boolean isForeground) {
           ....
            if (frameTracer != null) {
                frameTracer.onForeground(isForeground);
            }
            if (anrTracer != null) {
                anrTracer.onForeground(isForeground);
            }
            if (evilMethodTracer != null) {
                evilMethodTracer.onForeground(isForeground);
            }
            if (startupTracer != null) {
                startupTracer.onForeground(isForeground);
            }
        }
    

    onForeground方法就是将APP处于前台或者后台的状态分发给各个Tracer。可见TracePlugin中的工作还是蛮简单的就是初始化,启动,分发,暂停。

    2.1 UIThreadMonitor.init(traceConfig)

      public void init(TraceConfig config) {
            //不是主线程 就抛出异常
            if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
                throw new AssertionError("must be init in main thread!");
            }
            this.config = config;
            //从当前线程中获取到 Choreographer 对象
            choreographer = Choreographer.getInstance();
            // 获得 Choreographer 里的 mLock锁 对象
            callbackQueueLock = reflectObject(choreographer, "mLock");
            // 获得 Choreographer 里的 mCallbackQueues 对象
            callbackQueues = reflectObject(choreographer, "mCallbackQueues");
    
            //反射获得 callbackQueues 中第一个 CallbackQueue对象的 addCallbackLocked 的方法
            // 第一个 CallbackQueue 是处理 input事件的
            addInputQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
            //反射获得 callbackQueues 中第二个 CallbackQueue对象的 addCallbackLocked 的方法
            // 第二个 CallbackQueue 是处理 动画的
            addAnimationQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
            //反射获得 callbackQueues 中第三个 CallbackQueue对象的 addCallbackLocked 的方法
            // 第三个 CallbackQueue 是绘制完 用于回调的
            addTraversalQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
    
            // 获取 choreographer 中mFrameIntervalNanos 的值 并赋值给 frameIntervalNanos
            frameIntervalNanos = reflectObject(choreographer, "mFrameIntervalNanos");
    
            //注册一个 LooperDispatchListener 详见【2.2】
            LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
                @Override
                public boolean isValid() {
                    return isAlive;
                }
    
                @Override
                public void dispatchStart() {
                    super.dispatchStart();
                    //详见【2.7】
                    UIThreadMonitor.this.dispatchBegin();
                }
    
                @Override
                public void dispatchEnd() {
                    super.dispatchEnd();
                    //详见【2.8】
                    UIThreadMonitor.this.dispatchEnd();
                }
    
            });
            this.isInit = true;
            .....
        }
    

    UIThreadMonitor.init主要做了三件事

    1. 反射获得 CALLBACK_INPUT,CALLBACK_ANIMATION,CALLBACK_TRAVERSAL这三个状态的addCallbackLocked并记录到自己的成员变量中
    2. 反射获得Choreographer记录的mFrameIntervalNanos变量并记录到自己的成员变量中。mFrameIntervalNanos的值在现在大部分手机设备上都是16666666
    3. LooperMonitor中注册一个LooperDispatchListener监听用来监控 Looper分发事件的开始和结束。

    2.2 LooperMonitor.register()

         //饿汉式单例 详见【2.3】
        private static final LooperMonitor mainMonitor = new LooperMonitor();
    
        static void register(LooperDispatchListener listener) {
            mainMonitor.addListener(listener);
        }
    

    LooperMonitor.register没啥可讲,不过 LooperMonitor对象的创建使用了饿汉式单例的方式,我们一起看看它的构造方法中干了什么

    2.3 LooperMonitor <init>

        public LooperMonitor(Looper looper) {
            Objects.requireNonNull(looper);
            this.looper = looper;
            //添加 自定义的 Printer 详见【2.4】
            resetPrinter();
            //添加 IdleHandler
            addIdleHandler(looper);
        }
    
        private LooperMonitor() {
            this(Looper.getMainLooper());
        }
    

    可见无参构造获取到主线程Looper后调用到了有参构造

    2.4 LooperMonitor.resetPrinter()

      private synchronized void resetPrinter() {
            Printer originPrinter = null;
            try {
                if (!isReflectLoggingError) {
                    //获取之前的 Printer ,防止项目其他功能在 设置了 这个 Printer, 如果直接设置
                    //那其他 工具就不能正常运行了 大厂的程序员 还是细啊!!
                    originPrinter = ReflectUtils.get(looper.getClass(), "mLogging", looper);
                    //如果已经 hook过 就直接返回
                    if (originPrinter == printer && null != printer) {
                        return;
                    }
                }
            } 
            ....
            //设置自己的Printer 详见【2.5】
            looper.setMessageLogging(printer = new LooperPrinter(originPrinter));
            ....
        }
    

    该方法主要通过反射获取主线程LooperPrinter进行保存,然后通过Api设置自定义的LooperPrinter。这里保存Looper之前的Printer主要是防止和其他框架冲突导致其他框架不能正常工作,再感叹一次,大厂程序员还是细啊....

    2.5 LooperPrinter.println()

      @Override
            public void println(String x) {
                if (null != origin) {
                    //执行原始printer的 println方法
                    origin.println(x);
                   ...
                }
               ....
                if (isValid) {
                    //详见【2.6】
                    dispatch(x.charAt(0) == '>', x);
                }
    
            }
    

    2.6 LooperPrinter.dispatch()

     private void dispatch(boolean isBegin, String log) {
    
            for (LooperDispatchListener listener : listeners) {
                if (listener.isValid()) {
                    if (isBegin) {
                        if (!listener.isHasDispatchStart) {
                            //分发开始
                            listener.onDispatchStart(log);
                        }
                    } else {
                        if (listener.isHasDispatchStart) {
                            //分发结束
                            listener.onDispatchEnd(log);
                        }
                    }
                } else if (!isBegin && listener.isHasDispatchStart) {
                    //分发结束
                    listener.dispatchEnd();
                }
            }
    
        }
    

    通过该方法就会通知给所有的LooperDispatchListener当前是Looper刚开始分发还是已经分发完成。【2.1】中说过UIThreadMonitor.init想注册了一个LooperDispatchListener所以我们继续回到 UIThreadMonitor类中

    2.7 UIThreadMonitor.this.dispatchBegin

        private void dispatchBegin() {
            //记录 dispatch 的起始时间
            token = dispatchTimeMs[0] = SystemClock.uptimeMillis();
            // 记录 当前线程时间
            dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();
            // 调用 i 方法
            AppMethodBeat.i(AppMethodBeat.METHOD_ID_DISPATCH);
    
            synchronized (observers) {
                //回调 所有 LooperObserver 的 dispatchBegin 方法
                for (LooperObserver observer : observers) {
                    if (!observer.isDispatchBegin()) {
                        observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
                    }
                }
            }
        }
    

    该方法中记录了dispatch开始的时间并将 Looper开始分发事件 这个消息通知给了各个 LooperObserver,我们在最开始介绍过Tracer类就继承了LooperObserver这也就是所有Tracer都具有感知每个Message开始,执行,结束具体时间的能力。

    2.8 UIThreadMonitor.this.dispatchEnd

    private void dispatchEnd() {
    
            //帧刷新结束
            if (isBelongFrame) {
                 //详见【2.14】
                doFrameEnd(token);
            }
    
            //dispatch 起始时间
            long start = token;
            //dispatch 结束时间
            long end = SystemClock.uptimeMillis();
    
            synchronized (observers) {
                //回调 所有 LooperObserver 的 doFrame 方法
                for (LooperObserver observer : observers) {
                    if (observer.isDispatchBegin()) {
                        //参数含义 在 LooperObserver接口中查询
                        observer.doFrame(AppMethodBeat.getVisibleScene(), token, SystemClock.uptimeMillis(), isBelongFrame ? end - start : 0, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
                    }
                }
            }
    
            //记录 当前线程时间
            dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
            // 记录 dispatch 的结束时间
            dispatchTimeMs[1] = SystemClock.uptimeMillis();
            // 调用 o 方法
            AppMethodBeat.o(AppMethodBeat.METHOD_ID_DISPATCH);
    
            synchronized (observers) {
                // 回调 所有 LooperObserver的 dispatchEnd 方法
                for (LooperObserver observer : observers) {
                    if (observer.isDispatchBegin()) {
                        observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isBelongFrame);
                    }
                }
            }
    
        }
    

    这个方法中 记了了各个时间点并回调了LooperObserverdoFramedispatchEnd方法。LooperObserver 中各个方法的各个参数的含义见附录1

    2.9 UIThreadMonitor.onStart()

        //标识对应 type 执行 开始 或者 结束
        private int[] queueStatus = new int[CALLBACK_LAST + 1];
        //type对应的 执行时间
        private long[] queueCost = new long[CALLBACK_LAST + 1];
    
        public synchronized void onStart() {
            if (!isInit) {
                throw new RuntimeException("never init!");
            }
            if (!isAlive) {
                this.isAlive = true;
                synchronized (this) {
                    MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack());
                    callbackExist = new boolean[CALLBACK_LAST + 1];
                }
                queueStatus = new int[CALLBACK_LAST + 1];
                queueCost = new long[CALLBACK_LAST + 1];
                //详见【2.10】
                addFrameCallback(CALLBACK_INPUT, this, true);
            }
        }
    

    这个方法主要是 重置queueStatusqueueCost这两个重要的成员变量,然后调用addFrameCallback方法

    2.10 UIThreadMonitor.addFrameCallback()

     private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
            ....
            try {
                synchronized (callbackQueueLock) {//和 Choreographer 中使用相同的 锁对象 都是 mLock
                    Method method = null;
                    switch (type) {
                        case CALLBACK_INPUT:
                            method = addInputQueue;
                            break;
                        case CALLBACK_ANIMATION:
                            method = addAnimationQueue;
                            break;
                        case CALLBACK_TRAVERSAL:
                            method = addTraversalQueue;
                            break;
                    }
                    if (null != method) {
                        //反射执行 CallbackQueue 的 addCallbackLocked 方法 ,并将我们自己的 callback 添加到回到队列中,在一帧绘制完毕后
                        // 会回调callback的run()方法
                        method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
                        callbackExist[type] = true;
                    }
                }
            } catch (Exception e) {
                MatrixLog.e(TAG, e.toString());
            }
        }
    

    如果你对Choreographer这个类不太了解那你需要先看看Choreographer原理 。我们这里只对里面的vsync分类简单介绍:
    Choreographervsync回调事件分四类

    • INPUT:输入事件(输入回调,为了尽快响应用户操作)
    • ANIMATION:动画
    • TRAVERSAL:窗口刷新,执行measure/layout/draw操作(处理布局和绘图的回调)
    • COMMIT:遍历完成的提交操作,用来修正动画启动时间(绘制完成后的一些操作)

    好了,我们继续来看UIThreadMonitor.addFrameCallback,这个方法的功能就是将我们自定义的回调方法加入到Choreographer的回调队列中(回调队列有多种),当一帧刷新完成后会调用我们回调的run方法,因为【2.9】中的回调对象是this所以我们接下来进入到UIThreadMonitor.run

    2.11 UIThreadMonitor.run

      @Override
        public void run() {
            final long start = System.nanoTime();
            try {
                //详见 【2.12】
                doFrameBegin(token);
                //详见 【2.13】
                doQueueBegin(CALLBACK_INPUT);//input开始
                addFrameCallback(CALLBACK_ANIMATION, new Runnable() {
                    @Override
                    public void run() {
                        //详见 【2.14】
                        doQueueEnd(CALLBACK_INPUT);//input 结束
                        doQueueBegin(CALLBACK_ANIMATION);//animation 开始
                    }
                }, true);
                addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {
                    @Override
                    public void run() {
                        doQueueEnd(CALLBACK_ANIMATION);//animation 结束
                        doQueueBegin(CALLBACK_TRAVERSAL);//traversal 开始
                    }
                }, true);
    
            } finally {
                ....
                }
            }
        }
    

    这个方法的功能就是添加CALLBACK_ANIMATIONCALLBACK_TRAVERSAL类型的回到到Choreographer的回调队列中因为CALLBACK_INPUT类型的回调已经在UIThreadMonitor.onStart中已经添加了所以这里不需要添加。

    之所以为每种Type都添加监听应该是 Matrix 设计的时候计划将每一帧的耗时细化到每种Type。否则不用这么麻烦直接调用提供的apichoreographer.postFrameCallback就可以拿到每帧耗时

    2.12 UIThreadMonitor.doFrameBegin

        private void doFrameBegin(long token) {
            //记录帧刷新开始
            this.isBelongFrame = true;
        }
    

    2.13 UIThreadMonitor.doQueueBegin

        private void doQueueBegin(int type) {
            //标识当前 type 开始执行
            queueStatus[type] = DO_QUEUE_BEGIN;
            // 当前type 回调开始时间
            queueCost[type] = System.nanoTime();
        }
    

    2.13 UIThreadMonitor.doQueueEnd

        private void doQueueEnd(int type) {
            //标识当前 type 执行结束
            queueStatus[type] = DO_QUEUE_END;
            // 当前type 执行耗时
            queueCost[type] = System.nanoTime() - queueCost[type];
            synchronized (this) {
                // 当前type的 callback 是否还存在
                callbackExist[type] = false;
            }
        }
    

    2.14 UIThreadMonitor.doFrameEnd

      // 记录 帧结束
        private void doFrameEnd(long token) {
    
            doQueueEnd(CALLBACK_TRAVERSAL);//traversal 结束
    
            // 如果有 没有结束的回调 则报错
            for (int i : queueStatus) {
                if (i != DO_QUEUE_END) {
                    queueCost[i] = DO_QUEUE_END_ERROR;
                    if (config.isDevEnv) {
                        throw new RuntimeException(String.format("UIThreadMonitor happens type[%s] != DO_QUEUE_END", i));
                    }
                }
            }
    
            //重置 queueStatus
            queueStatus = new int[CALLBACK_LAST + 1];
    
            //继续添加  input callback
            addFrameCallback(CALLBACK_INPUT, this, true);
    
            this.isBelongFrame = false;
        }
    

    在【2.8】UIThreadMonitor.this.dispatchEnd方法中 ,也就是当一个Message分发完毕的时候调用了doFrameEnd

    该方法主要是重置 queueStatus,并给Choreographer的回调队列中重新添加CALLBACK_INPUT类型的回到方法。作为下一次记录帧刷新时间的开始。

    看到这里可能有人有疑惑,不是之前添加过了么,为啥这里还要再添加一次。是因为 这种监听是一次性的,Choreographer.doCallbacks()方法最后会将所有的监听都会移除.

    2.15 总结一下 UIThreadMonitor的工作原理

    1. 主线程Looper loop 到一个 target是 Choreographer$FrameHandler massage 是 Choreographer$FrameDisplayEventReceiver 的消息(要能触发 刷新帧)
    2. 调用 dispatchBegin() 方法
    3. 调用 run方法
    4. 回掉 doQueueBegin type=0(input)
    5. 回掉 doQueueEnd type=0(input)
    6. 回掉 doQueueBegin type=1(animation)
    7. 回掉 doQueueEnd type=1(animation)
    8. 回掉 doQueueBegin type=2(traversal)
    9. Looper中message处理完毕
    10. 调用 dispatchEnd() 方法
    11. 回掉 doQueueEnd type=2(traversal)

    这就是 UIThreadMonitor获取一帧中每种Type耗时的流程。这个过程中UIThreadMonitor会将自己的获取到的时间通过LooperObserver这个接口分发出去。

    注意:当主线程的 Looper loop 到的Message不能触发帧刷新,那么就只会执行 dispatchBegindispatchEnd这两个方法。

    3.1 AppMethodBeat.onStart()

    下来我们继续看AppMethodBeat是怎么工作的

     public void onStart() {
            synchronized (statusLock) {
                //如果没有启动 或者已经过期 则进行启动
                if (status < STATUS_STARTED && status >= STATUS_EXPIRED_START) {
                    //取消 启动过期 检查的 Runnable
                    sHandler.removeCallbacks(checkStartExpiredRunnable);
                    if (sBuffer == null) {
                        throw new RuntimeException(TAG + " sBuffer == null");
                    }
                    MatrixLog.i(TAG, "[onStart] preStatus:%s", status, Utils.getStack());
                    //标示已将 启动
                    status = STATUS_STARTED;
                } else {
                    MatrixLog.w(TAG, "[onStart] current status:%s", status);
                }
            }
        }
    

    onStart()主要是将AppMethodBeat 的当前状态设置为STATUS_STARTEDAppMethodBeat 中各个状态含义及触发时机可查看【附录.2】

    3.2 AppMethodBeat.onStop()

        public void onStop() {
            synchronized (statusLock) {
                //进行关闭
                if (status == STATUS_STARTED) {
                    MatrixLog.i(TAG, "[onStop] %s", Utils.getStack());
                    status = STATUS_STOPPED;
                } else {
                    MatrixLog.w(TAG, "[onStop] current status:%s", status);
                }
            }
        }
    

    onStop()主要是将AppMethodBeat 的当前状态设置为STATUS_STOPPED

    3.3 AppMethodBeat.i()

    在最开始描述AppMethodBeat类的时候,提到过在编译器会将AppMethodBeati(),o(),at()方法插桩到指定位置那么我们就来看看,这三个方法具体过了那些工作

       public static void i(int methodId) {
            ....
            //第一次执行该方法时 调用 realExecute 方法,并将状态切换为 STATUS_READY
            if (status == STATUS_DEFAULT) {
                synchronized (statusLock) {
                    if (status == STATUS_DEFAULT) {
                        //当 当前类 没有被启动时 执行该方法 详见【3.4】
                        realExecute();
                        //切换状态 为 STATUS_READY
                        status = STATUS_READY;
                    }
                }
            }
    
            long threadId = Thread.currentThread().getId();
            //执行回调
            if (sMethodEnterListener != null) {
                sMethodEnterListener.enter(methodId, threadId);
            }
    
            //如果是主线程
            if (threadId == sMainThreadId) {
    
                //i方法被重复执行的 提醒
                if (assertIn) {
                    android.util.Log.e(TAG, "ERROR!!! AppMethodBeat.i Recursive calls!!!");
                    return;
                }
                assertIn = true;
                if (sIndex < Constants.BUFFER_SIZE) {
                    //详见【3.9】
                    mergeData(methodId, sIndex, true);
                } else {
                    sIndex = 0;
                    mergeData(methodId, sIndex, true);
                }
                ++sIndex;
                assertIn = false;
            }
        }
    

    在第一次进入i()的时候会触发调用realExecute()方法进行一些初始化工作,然后调用mergeData()方法保持调用时的methodId和时间到sBuffer

    3.4 AppMethodBeat.realExecute()

     //这个方法只有 在第一次执行 i方法时才会被调用
        private static void realExecute() {
            MatrixLog.i(TAG, "[realExecute] timestamp:%s", System.currentTimeMillis());
    
            //当前时间减去上一个 记录的时间 (更新 sCurrentDiffTime)
            sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
    
            //清空 sHandler 所有消息
            sHandler.removeCallbacksAndMessages(null);
            //延迟5 ms 后执行 sUpdateDiffTimeRunnable ,开始刷新  sCurrentDiffTime 详见【3.5】
            sHandler.postDelayed(sUpdateDiffTimeRunnable, Constants.TIME_UPDATE_CYCLE_MS);
            //延迟15 ms 后执行 checkStartExpiredRunnable (检查 AppMethodBeat 当前状态的 runnable )
            //也就是 在 realExecute 方法之后后 如果 15ms 内 AppMethodBeat 还没有被启动(onStart)
            // 就将 AppMethodBeat的状态置为 STATUS_EXPIRED_START(启动过期)
            // 启动过期 只是一种状态,并不会影响 AppMethodBeat 的运行
            sHandler.postDelayed(checkStartExpiredRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (statusLock) {
                        MatrixLog.i(TAG, "[startExpired] timestamp:%s status:%s", System.currentTimeMillis(), status);
                        if (status == STATUS_DEFAULT || status == STATUS_READY) {
                            status = STATUS_EXPIRED_START;
                        }
                    }
                }
            }, Constants.DEFAULT_RELEASE_BUFFER_DELAY);
    
            //hook 主线程的 HandlerCallback 详见【3.6】
            ActivityThreadHacker.hackSysHandlerCallback();
            //注册 looperMonitorListener 使可以接收到looper分发massage事件 详见【3.8】
            LooperMonitor.register(looperMonitorListener);
        }
    

    3.5 AppMethodBeat.sUpdateDiffTimeRunnable

     private static Runnable sUpdateDiffTimeRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    //无限循环  当isPauseUpdateTime=false(dispatchBegin方法完成),然后更新 sCurrentDiffTime
                    while (true) {
                        while (!isPauseUpdateTime && status > STATUS_STOPPED) {
                            sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
                           SystemClock.sleep(Constants.TIME_UPDATE_CYCLE_MS);
                        }
                        synchronized (updateTimeLock) {
                            updateTimeLock.wait();
                        }
                    }
                } catch (InterruptedException e) {
                    MatrixLog.e(TAG, "" + e.toString());
                }
            }
        };
    

    对于这个Runnable Matrix的wiki中有介绍,其中这样描述到:

    考虑到每个方法执行前后都获取系统时间(System.nanoTime)会对性能影响比较大,而实际上,单个函数执行耗时小于 5ms 的情况,对卡顿来说不是主要原因,可以忽略不计,如果是多次调用的情况,则在它的父级方法中可以反映出来,所以为了减少对性能的影响,通过另一条更新时间的线程每 5ms 去更新一个时间变量,而每个方法执行前后只读取该变量来减少性能损耗。

    3.6 ActivityThreadHacker.hackSysHandlerCallback()

    public static void hackSysHandlerCallback() {
            try {
                //当前方法加载的时间 被认为是 APP启动时间,
                sApplicationCreateBeginTime = SystemClock.uptimeMillis();
                //记录这第一个方法,
                sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
    
                //替换 ActivityThread 中handler的 callBack 方法
                Class<?> forName = Class.forName("android.app.ActivityThread");
                Field field = forName.getDeclaredField("sCurrentActivityThread");
                field.setAccessible(true);
                //获得 ActivityThread 对象
                Object activityThreadValue = field.get(forName);
                Field mH = forName.getDeclaredField("mH");
                mH.setAccessible(true);
                //获得handler对象
                Object handler = mH.get(activityThreadValue);
                Class<?> handlerClass = handler.getClass().getSuperclass();
                Field callbackField = handlerClass.getDeclaredField("mCallback");
                callbackField.setAccessible(true);
                //获得 Handler.Callback 对象
                Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
                //详见【3.7】
                HackCallback callback = new HackCallback(originalCallback);
                //设置新的callback对象
                callbackField.set(handler, callback);
                MatrixLog.i(TAG, "hook system handler completed. start:%s SDK_INT:%s", sApplicationCreateBeginTime, Build.VERSION.SDK_INT);
            } catch (Exception e) {
                MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
            }
        }
    

    这个方法有两个主要作用

    1. 记录sApplicationCreateBeginTime这个时间,这个时间被认为是APP启动时间。
      因为在插桩的过程中忽略了 android包下的所有类,所以运行期起一个执行 AppMethodBeat.i方法的应该是 Application的attachBaseContext方法(如果有),下来就是onCreate()方法。所以这个时间被认为是APP启动时间是没有毛病的。
    2. hookActivityThreadmHHandler.Callback对象为自定义的HackCallback对象主要用来记录最近一个Activity被打开的时间

    3.7 ActivityThreadHacker.HackCallback.handleMessage()

     public boolean handleMessage(Message msg) {
    
                if (!AppMethodBeat.isRealTrace()) {
                    //将 handleMessage 的控制权交还给 mOriginalCallback,再一次感叹真细啊,哈哈
                    return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
                }
                //是否是打开activity的handler信息
                boolean isLaunchActivity = isLaunchActivity(msg);
                 .....
                //打开一次activity,就记录一次数据
                if (isLaunchActivity) {
                    ActivityThreadHacker.sLastLaunchActivityTime = SystemClock.uptimeMillis();
                    ActivityThreadHacker.sLastLaunchActivityMethodIndex = AppMethodBeat.getInstance().maskIndex("LastLaunchActivityMethodIndex");
                }
    
                if (!isCreated) {
                    if (isLaunchActivity || msg.what == CREATE_SERVICE || msg.what == RECEIVER) { // 如果是启动activity、service,receiver
                        //发送启动Activity等消息,认为是Application 启动的结束时间
                        ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
                        ActivityThreadHacker.sApplicationCreateScene = msg.what;
                        isCreated = true;
                    }
                }
    
                //将 handleMessage 的控制权交还给 mOriginalCallback
                return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
            }
    

    该方法的主要租用也有两个

    1. 记录最近一个Activity的启动时间
    2. 第一次进入时 记录 Application 的 启动结束时间

    3.8 AppMethodBeat.looperMonitorListener

        private static LooperMonitor.LooperDispatchListener looperMonitorListener = new LooperMonitor.LooperDispatchListener() {
            @Override
            public boolean isValid() {
                return status >= STATUS_READY;
            }
    
            @Override
            public void dispatchStart() {
                super.dispatchStart();
                AppMethodBeat.dispatchBegin();
            }
    
            @Override
            public void dispatchEnd() {
                super.dispatchEnd();
                AppMethodBeat.dispatchEnd();
            }
        };
    
     private static void dispatchBegin() {
            //更新 sCurrentDiffTime
            sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
            isPauseUpdateTime = false;
            SystemClock.uptimeMillis(),sCurrentDiffTime);
            synchronized (updateTimeLock) {
                updateTimeLock.notify();
            }
        }
    
        private static void dispatchEnd() {
            isPauseUpdateTime = true;
        }
    

    上面我们已经了解过 LooperMonitor可以感知到主线程Looper开始分发和结束分发Message的实际,AppMethodBeat注册looperMonitorListener监听到 LooperMonitor主要是 为了在主线程空闲的情况下停止更新sCurrentDiffTime在主线程开始工作时再开始更新sCurrentDiffTime以减少资源的占用。

    3.9 AppMethodBeat.mergeData()

       private static void mergeData(int methodId, int index, boolean isIn) {
            if (methodId == AppMethodBeat.METHOD_ID_DISPATCH) {//如果是 handler 的 dispatchMessage 方法
                sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
            }
            // 合并后的数据存到 trueId中
            long trueId = 0L;
            if (isIn) {//如果是 i 方法 则第63位上是1,否则为0 (就是一个标志位)
                trueId |= 1L << 63;
            }
            //43-62位 存储 methodId
            trueId |= (long) methodId << 43;
            //0-42位存储 sCurrentDiffTime
            trueId |= sCurrentDiffTime & 0x7FFFFFFFFFFL;
            //存放到 sBuffer中
            sBuffer[index] = trueId;
            checkPileup(index);
            sLastIndex = index;
        }
    

    这个方法的第三个参数isIn的含义为是否是 i()方法,i()方法中被调用为 trueo()方法中被调用为false。

    mergeData其实就做了一件事,就是将方法类型(i或o),methodId,sCurrentDiffTime存到sBuffer中的index位置

    3.10 AppMethodBeat.o()

        public static void o(int methodId) {
            //对 AppMethodBeat 状态进行检查
            if (status <= STATUS_STOPPED) {
                return;
            }
            //对 methodId进行校验
            if (methodId >= METHOD_ID_MAX) {
                return;
            }
    
            //如果是主线程
            if (Thread.currentThread().getId() == sMainThreadId) {
                if (sIndex < Constants.BUFFER_SIZE) {
                    mergeData(methodId, sIndex, false);
                } else {
                    sIndex = 0;
                    mergeData(methodId, sIndex, false);
                }
                ++sIndex;
            }
        }
    
    

    i()方法一样最终也是调用了mergeData()来存储数据到sBuffer

    3.11 AppMethodBeat.at()

        public static void at(Activity activity, boolean isFocus) {
            String activityName = activity.getClass().getName();
            if (isFocus) {
                //获取焦点的activity 添加到 sFocusActivitySet
                if (sFocusActivitySet.add(activityName)) {
                    synchronized (listeners) {
                        //广播 activityName 获取到焦点
                        for (IAppMethodBeatListener listener : listeners) {
                            listener.onActivityFocused(activityName);
                        }
                    }
                    // activity 不都有了吗 为啥还要通过 getVisibleScene() 获取?
                    MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "attach");
                }
            } else {
                //失去焦点的activity 从sFocusActivitySet移除
                if (sFocusActivitySet.remove(activityName)) {
                    MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "detach");
                }
            }
        }
    

    在刚开始我们描述AppMethodBeat这个类的时候提到过,它能感知到某个activity是否获取到焦点。这是因为at()方法会在编译器被插入到 Activity的onWindowFocusChanged()方法中。

    at()方法主要的功能是自己获得的焦点信息分发给每一个IAppMethodBeatListener

    3.12 AppMethodBeat.realRelease()

        static {
            //在 AppMethodBeat 类加载 15s 后,还没有使用(status的状态还是STATUS_DEFAULT),就清空AppMethodBeat 占用的内存
            sHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    realRelease();
                }
            }, Constants.DEFAULT_RELEASE_BUFFER_DELAY);
        }
    
        private static void realRelease() {
            synchronized (statusLock) {
                if (status == STATUS_DEFAULT) {
                    MatrixLog.i(TAG, "[realRelease] timestamp:%s", System.currentTimeMillis());
                    sHandler.removeCallbacksAndMessages(null);
                    //移除 looperMonitorListener 监听
                    LooperMonitor.unregister(looperMonitorListener);
                    //sTimerUpdateThread 退出
                    sTimerUpdateThread.quit();
                    sBuffer = null;
                    //状态改为 STATUS_OUT_RELEASE
                    status = STATUS_OUT_RELEASE;
                }
            }
        }
    

    这个方法主要是在 AppMethodBeat 类加载 15s 后,还没有使用的情况下释放各种资源,尤其是sBuffer因为它就占用了7.6M的内存。

    总结

    1. UIThreadMonitor配合LooperMonitor获得每个刷新帧的各个阶段的耗时时间
    2. AppMethodBeat中记录了Application的启动时间和结束,Activity的启动时间和结束,每个方法的耗时时间。

    系列文章

    参考资料

    附录

    1. LooperObserver中各个参数的含义
     /**
         *
         * @param beginMs dispatch 的起始时间
         * @param cpuBeginMs dispatch 的起始时 当前线程时间
         * @param token 等于 dispatch 的起始时间
         */
        @CallSuper
        public void dispatchBegin(long beginMs, long cpuBeginMs, long token) {
            isDispatchBegin = true;
        }
    
        /**
         *
         * @param focusedActivityName 当前activity的名字
         * @param start looper dispatch 的起始时间
         * @param end looper dispatch 的结束时间
         * @param frameCostMs 该帧耗时
         * @param inputCostNs input 花费 时间
         * @param animationCostNs animation 花费 时间
         * @param traversalCostNs traversal 花费 时间
         */
        public void doFrame(String focusedActivityName, long start, long end, long frameCostMs, long inputCostNs, long animationCostNs, long traversalCostNs) {
    
        }
    
        /**
         *
         * @param beginMs dispatch 的起始时间
         * @param cpuBeginMs dispatch 的起始时 当前线程时间
         * @param endMs dispatch 的结束时间
         * @param cpuEndMs dispatch 的结束时 当前线程时间
         * @param token 等于 dispatch 的起始时间
         * @param isBelongFrame 是否属于一帧? 不确定
         */
        @CallSuper
        public void dispatchEnd(long beginMs, long cpuBeginMs, long endMs, long cpuEndMs, long token, boolean isBelongFrame) {
            isDispatchBegin = false;
        }
    
    1. AppMethodBeat中各个状态含义及触发时机可查看
        //默认状态
        private static final int STATUS_DEFAULT = Integer.MAX_VALUE;
        //调用onStart()后的状态
        private static final int STATUS_STARTED = 2; //启动
        //第一次 执行 i 方法后的状态
        private static final int STATUS_READY = 1; // 准备好
        //调用onStop()后的状态
        private static final int STATUS_STOPPED = -1; //停止
        //启动已过期 当 在 realExecute 方法之后后 如果 15ms 内 AppMethodBeat 还没有被启动(onStart)就会被置为这种状态
        private static final int STATUS_EXPIRED_START = -2;
        //在 AppMethodBeat 类加载 15s 后,还没有使用(status的状态还是STATUS_DEFAULT),就会被置为这种状态
        private static final int STATUS_OUT_RELEASE = -3;//已释放
    

    相关文章

      网友评论

          本文标题:腾讯 Apm 框架 Matrix 源码阅读 - TracePlu

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