腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 架构解析

版本

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;//已释放
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容