概述
前几天腾讯将一款 Android 应用性能监控的框架 matrix 开源了, 源码地址在 https://github.com/Tencent/matrix https://github.com/Tencent/matrix , 作者是微信终端团队. matrix 到底是什么? 据官方说法如下:
Matrix 是一款微信研发并日常使用的 APM(Application Performance Manage), 当前主要运行在 Android 平台上. Matrix 的目标是建立统一的应用性能接入框架, 通过各种性能监控方案, 对性能监控项的异常数据进行采集和分析, 输出相应的问题分析, 定位与优化建议, 从而帮助开发者开发出更高质量的应用.
Matrix 当前监控范围包括: 应用安装包大小, 帧率变化, 启动耗时, 卡顿, 慢方法, SQLite 操作优化, 文件读写, 内存泄漏等等 (此段截取自 matrix 的 GitHub 介绍)
下面直接看源码:
<!-- more -->
代码的入口在 application 的 onCreate() 里进行初始化的,
- Matrix.Builder builder = new Matrix.Builder(this);
- ```
- // 省略了一部分构造器创建对象的一段代码, 这里仅说明是入口
- ```
- Matrix.init(builder.build());
和 leakcanary 等库一样在 application 初始化, Matrix 的创建采用了常用的构造器模式, 现在进入 Matrix 内部看看
- private static volatile Matrix sInstance;
- private final HashSet<Plugin> plugins;// 插件集合
- private final Application application;
- private final PluginListener pluginListener;
- private Matrix(Application App, PluginListener listener, HashSet<Plugin> plugins) {
- this.application = App;
- this.pluginListener = listener;
- this.plugins = plugins;
- for (Plugin plugin : plugins) {
- plugin.init(application, pluginListener);
- pluginListener.onInit(plugin);
- }
- }
- public static void setLogIml(MatrixLog.MatrixLogImp imp) {
- MatrixLog.setMatrixLogImp(imp);
- }
- public static boolean isInstalled() {
- return sInstance != null;
- }
- public static Matrix init(Matrix matrix) {
- if (matrix == null) {
- throw new RuntimeException("Matrix init, Matrix should not be null.");
- }
- synchronized (Matrix.class) {
- if (sInstance == null) {
- sInstance = matrix;
- } else {
- MatrixLog.e(TAG, "Matrix instance is already set. this invoking will be ignored");
- }
- }
- return sInstance;
- }
matrix 里持有一个插件的集合 plugins, 使用的是 hashSet 来保证不出现重复, 还有一个 plugin 状态的监听 pluginListener.Matrix 采用了单例模式, volatile sInstance 保证线程可见行, 初始化的时候采用了双重检查, 在构造函数里给变量赋值并遍历 plugins 集合, 并逐个调用插件的初始化方法 plugin.init().
那插件 plugin 是什么呢? 下面是 plugin 的代码:
- public abstract class Plugin implements IPlugin, IssuePublisher.OnIssueDetectListener {
- private static final String TAG = "Matrix.Plugin";
- public static final int PLUGIN_CREATE = 0x00;
- public static final int PLUGIN_INITED = 0x01;
- public static final int PLUGIN_STARTED = 0x02;
- public static final int PLUGIN_STOPPED = 0x04;
- public static final int PLUGIN_DESTROYED = 0x08;
- private PluginListener pluginListener;
- private Application application;
- private boolean isSupported = true;
- private int status = PLUGIN_CREATE;
- @Override
- public void init(Application App, PluginListener listener) {
- if (application != null || pluginListener != null) {
- throw new RuntimeException("plugin duplicate init, application or plugin listener is not null");
- }
- status = PLUGIN_INITED;
- this.application = App;
- this.pluginListener = listener;
- }
- @Override
- public void onDetectIssue(Issue issue) {
- pluginListener.onReportIssue(issue);
- }
- public Application getApplication() {
- return application;
- }
- @Override
- public void start() {
- // 省略部分代码
- pluginListener.onStart(this);
- }
- @Override
- public void stop() {
- // 省略部分代码
- pluginListener.onStop(this);
- }
- @Override
- public void destroy() {
- // 省略部分代码
- pluginListener.onDestroy(this);
- }
- }
plugin 它是个抽象类, 继承了 IPlugin 和 IssuePublisher.OnIssueDetectListener,IPlugin 包括了五种插件的状态分别是 CREATE,INITED,STARTED,STOPPED 和 DESTROYED, 当 plugin 状态发生变化时将回调交给 pluginListener 来处理. OnIssueDetectListener 接口是 IssuePublisher 类里的内部接口, IssuePublisher 具体做了两件事, 记录问题和暴露问题, 其暴露问题的方法就是空实现然后暴露接口, 交给实现 OnIssueDetectListener 接口的具体类来处理, Plugin 继承了这个 OnIssueDetectListener 接口, 但它也没自己处理, 也是同样交留 pluginListener 来处理.
第一段小结
Matrix 是个单例, 它维护着插件的集合 plugins 和插件不同状态及报错的处理接口
pluginListener, 这个 pluginListener 是 plugins 集合共有的,
matrix 初始化的时候会逐个调用 plugin 的 init 方法.
插件 Plugin 是个抽象类, 具体的插件需要实现的, matrix 框架里自带的插件有 TracePlugin,IOCanaryPlugin,SQLiteLintPlugin,ResourcePlugin.
下面会一一查看它们的作用和具体实现
TracePlugin
首先来看 TracePlugin, 它继承自 plugin, 里面包括四个维度 FrameTracer,FPSTracer, EvilMethodTracer,StartUpTracer 来分析 App 的, 初始化的方法如下:
- @Override
- public void init(Application App, PluginListener listener) {
- super.init(App, listener);
- MatrixLog.i(TAG, "trace plugin init, trace config: %s", mTraceConfig.toString());
- // 低版本不支持
- 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;
- }
- ApplicationLifeObserver.init(App);
- mFrameTracer = new FrameTracer(this);
- // 开关, 可以选择不开
- if (mTraceConfig.isMethodTraceEnable()) {
- mStartUpTracer = new StartUpTracer(this, mTraceConfig);
- }
- if (mTraceConfig.isFPSEnable()) {
- mFPSTracer = new FPSTracer(this, mTraceConfig);
- }
- if (mTraceConfig.isMethodTraceEnable()) {
- mEvilMethodTracer = new EvilMethodTracer(this, mTraceConfig);
- }
- }
ApplicationLifeObserver.init(App) 是利用了 application 的 ActivityLifecycleCallbacks 可以对每个 activity 的生命周期进行监控做了个观察者模式, 另外加了判断分析当前 App 是在前台还是后台, 具体实现方式是记录 onActivityResumed 和 onActivityPaused 的生命周期, 由于新起的 activity 的 onResume 会在底层 activity 的 onPause 之后, 如果 onActivityPaused 之后 600ms 没有执行到 onActivityResumed 则认为当前处于后台. 仔细想想这么做会有误伤, 如果有个 activity 启动特别慢, 此时超过 600ms 则判定已经处于后台了, 不过这个影响比较小, 因为 activity 启动之后到 resume 时就又恢复成正常的前台, 即使误判也不影响检测, 具体实现可以看源码.
在 TracePlugin 初始化的时候, 分别新建了 mStartUpTracer,mFPSTracer,mFrameTracer 和 mEvilMethodTracer, 其中的参数 mTraceConfig 是个简单的配置类, 只是记录了开关就不在这展开了. 查看 matrix 的 demo 开始检测的入口是 tracePlugin.start() 里, 代码如下:
- @Override
- public void start() {
- super.start();
- if (!isSupported()) {
- return;
- }
- new Handler(Looper.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- // 保证在主线程调用
- FrameBeat.getInstance().onCreate();
- }
- });
- if (null != mFPSTracer) {
- mFPSTracer.onCreate();
- }
- if (null != mEvilMethodTracer) {
- mEvilMethodTracer.onCreate();
- }
- if (null != mFrameTracer) {
- mFrameTracer.onCreate();
- }
- if (null != mStartUpTracer) {
- mStartUpTracer.onCreate();
- }
- }
在 onstart 时在主线程中调用了 FrameBeat.getInstance().onCreate(), 这里是做 UI 分析用的.
目前做 UI 性能卡顿分析一般有两种方式:
一是利用主线程 looper 的 loop 方法在寻找 msg.target.dispatchMessage(msg) 时的前后会分别打印一段 log, 可以利用 log 的内容不同或者 log 的前后次数记录两次 log 的时间差, 这样就可以大致认为是主线程处理 msg 的时间, 如果时间过长则认为卡顿;
二是利用 Choreographer,Choreographer 就是一个消息处理器, 根据 vsync 信号 来计算 frame, 在 doFrame 方法里可以收到回调的当前时间, 正常绘制两次 doFrame 的时间差应该是 1000/60=16.6666 毫秒 (每秒 60 帧), 但是遇到卡顿或过度重绘等会导致时间拉长.
这里采用的是第二种方式, 其 doFrame 的实现如下:
- @Override
- public void doFrame(long frameTimeNanos) {
- if (isPause) {
- return;
- }
- if (frameTimeNanos < mLastFrameNanos || mLastFrameNanos <= 0) {
- mLastFrameNanos = frameTimeNanos;
- if (null != mChoreographer) {
- mChoreographer.postFrameCallback(this);
- }
- return;
- }
- if (null != mFrameListeners) {
- for (IFrameBeatListener listener : mFrameListeners) {
- listener.doFrame(mLastFrameNanos, frameTimeNanos);
- }
- if (null != mChoreographer) {
- mChoreographer.postFrameCallback(this);
- }
- mLastFrameNanos = frameTimeNanos;
- }
- }
记录两次 doFrame 的时间, 交给 mFrameListeners 执行回调.
下面会分析 mFPSTracer ,mFrameTracer,mFrameTracer 和 mStartUpTracer 的 onCreate 方法的具体实现, 这四个类都继承了 BaseTracer 类, 因此在分析得前先看下 BaseTracer,
- public abstract class BaseTracer extends IssuePublisher implements ApplicationLifeObserver.IObserver, IFrameBeatListener, IMethodBeatListener {
- private final TracePlugin mPlugin;
- private static final MethodBeat sMethodBeat = new MethodBeat();
- private static final HashMap<Class<BaseTracer>, BaseTracer> sTracerMap = new HashMap<>();
- BaseTracer(TracePlugin plugin) {
- super(plugin);
- this.mPlugin = plugin;
- sTracerMap.put((Class<BaseTracer>) this.getClass(), this);
- }
- @Override
- public void doFrame(long lastFrameNanos, long frameNanos) {
- }
- public void onCreate() {
- MatrixLog.i(TAG, "[onCreate] name:%s process:%s", this.getClass().getCanonicalName(), Process.myPid());
- if (isEnableMethodBeat()) {
- if (!getMethodBeat().isHasListeners()) {
- getMethodBeat().onCreate();
- }
- getMethodBeat().registerListener(this);
- }
- ApplicationLifeObserver.getInstance().register(this);
- FrameBeat.getInstance().addListener(this);
- isCreated = true;
- }
- public void onDestroy() {
- MatrixLog.i(TAG, "[onDestroy] name:%s process:%s", this.getClass().getCanonicalName(), Process.myPid());
- if (isEnableMethodBeat()) {
- getMethodBeat().unregisterListener(this);
- if (!getMethodBeat().isHasListeners()) {
- getMethodBeat().onDestroy();
- }
- }
- ApplicationLifeObserver.getInstance().unregister(this);
- FrameBeat.getInstance().removeListener(this);
- isCreated = false;
- }
- protected void sendReport(JSONObject jsonObject, String tag) {
- Issue issue = new Issue();
- issue.setTag(tag);
- issue.setContent(jsonObject);
- mPlugin.onDetectIssue(issue);
- }
- }
这里截取了一部分核心的代码, BaseTracer 里有个静态的 hashMap, 类名作为 key,value 是具体的 BaseTracer 对象, 它是静态的所以只会有一份, 在 onCreate 里注册了前面说到的 ApplicationLifeObserver 和 FrameBeat 的监听, 监听 activity 的生命周期的回调和 Choreographer 两次绘制的时间的接口回调, 回调是交给自己来处理的. 另外暴露了 sendReport 方法, 方法里调用的是本地持有的 TracePlugin 对象的 onDetectIssue 来处理, 这里和前文 Matrix 里 plugins 与 pluginListener 相对应上了, plugins 里的 onDetectIssue 最终都是由 pluginListener 来处理的.
TracePlugin 初步小结
TracePlugin 分成四个部分 mStartUpTracer,mFPSTracer,mFrameTracer 和 mEvilMethodTracer, 它们都继承了 BaseTracer;
BaseTracer 里监听了 ApplicationLifeObserver, 即每个 activity 的生命周期和前后台状态的监听;
BaseTracer 监听着 FrameBeat 的每一帧刷新前后的时间即 doFrame(long lastFrameNanos, long frameNanos);
第一个纬度: mFrameTracer
下面我们来看看 mFrameTracer
mFrameTracer 的具体实现的关键方法 doFrame
- @Override
- public void doFrame(final long lastFrameNanos, final long frameNanos) {
- if (!isDrawing) {
- return;
- }
- isDrawing = false;
- final int droppedCount = (int) ((frameNanos - lastFrameNanos) / REFRESH_RATE_MS);
- if (droppedCount> 1) {
- for (final IDoFrameListener listener : mDoFrameListenerList) {
- listener.doFrameSync(lastFrameNanos, frameNanos, getScene(), droppedCount);
- if (null != listener.getHandler()) {
- listener.getHandler().post(new Runnable() {
- @Override
- public void run() {
- listener.getHandler().post(new AsyncDoFrameTask(listener,
- lastFrameNanos, frameNanos, getScene(), droppedCount));
- }
- });
- }
- }
- }
- }
在 doFrame 里根据界面绘制的时间差计算, 如果超过了正常绘制 16.67 秒就会在监听里把数据回调出去, 这个有两个回调方法 doFrameSync 和 doFrameAsync, 对应的是同步调用和异步调用, 异步的实现方式利用 handler 机制, 其中 getScene 是当前的 activity 或 fragment 的类名.
第二个纬度: EvilMethodTracer
EvilMethodTracer 顾名思义就是找到那些邪恶的方法, 也就是耗时多的方法, 具体实现来看代码,
首先 EvilMethodTracer 继承了 LazyScheduler 接口, LazyScheduler 接口是个利用 handler 实现的定时器, 代码如下:
- public class LazyScheduler {
- // 延迟即间隔时间
- private final long delay;
- private final Handler mHandler;
- private volatile boolean isSetUp = false;
- public LazyScheduler(HandlerThread handlerThread, long delay) {
- this.delay = delay;
- mHandler = new Handler(handlerThread.getLooper());
- }
- public boolean isSetUp() {
- return isSetUp;
- }
- // 开始
- public void setUp(final ILazyTask task, boolean cycle) {
- if (null != mHandler) {
- this.isSetUp = true;
- mHandler.removeCallbacksAndMessages(null);
- RetryRunnable retryRunnable = new RetryRunnable(mHandler, delay, task, cycle);
- mHandler.postDelayed(retryRunnable, delay);
- }
- }
- public void cancel() {
- if (null != mHandler) {
- this.isSetUp = false;
- mHandler.removeCallbacksAndMessages(null);
- }
- }
- public void setOff() {
- cancel();
- }
- public interface ILazyTask {
- void onTimeExpire();
- }
- static class RetryRunnable implements Runnable {
- private final Handler handler;
- private final long delay;
- private final ILazyTask lazyTask;
- private final boolean cycle;
- RetryRunnable(Handler handler, long delay, ILazyTask lazyTask, boolean cycle) {
- this.handler = handler;
- this.delay = delay;
- this.lazyTask = lazyTask;
- this.cycle = cycle;
- }
- @Override
- public void run() {
- lazyTask.onTimeExpire();
- if (cycle) {
- handler.postDelayed(this, delay);
- }
- }
- }
这个定时器的实现利用了 handler 机制, handler 的 looper 直接从参数 handlerThread 的线程里获得, 这里的代码并不复杂, 只要记住一条就可以, 一是定时器支持是否循环, 执行是会调用 ILazyTask 的 onTimeExpire 方法.
EvilMethodTracer 也是重写了定时器的 onTimeExpire 方法, 下面来看 EvilMethodTracer 的具体代码.
- public EvilMethodTracer(TracePlugin plugin, TraceConfig config) {
- super(plugin);
- this.mTraceConfig = config;
- mLazyScheduler = new LazyScheduler(MatrixHandlerThread.getDefaultHandlerThread(), Constants.DEFAULT_ANR);
- mActivityCreatedInfoMap = new HashMap<>();
- }
创建时初始化了 mLazyScheduler 和 mActivityCreatedInfoMap,mLazyScheduler, 这里的 mLazyScheduler 是个定时器 Constants.DEFAULT_ANR 默认值 5 秒, 用于记录界面 5 秒没响应及 ANR, 中间有句 MatrixHandlerThread.getDefaultHandlerThread(),MatrixHandlerThread 是个 hadlerThread 线程管理类, 它里面包含了 Matrix 默认工作线程, 主线程和一个动态创建新线程的容器, getDefaultHandlerThread() 方法获取的是默认工作线程的 handlerThread.mActivityCreatedInfoMap 是用于记录 activity 的启动耗时信息. 具体是怎么做到的呢? 让我们一步步来看, 首先启动 EvilMethodTracer 的代码如下:
- @Override
- public void onCreate() {
- super.onCreate();
- if (!getMethodBeat().isRealTrace()) {
- MatrixLog.e(TAG, "MethodBeat don't work, maybe it's wrong in trace Build!");
- onDestroy();
- return;
- }
- if (this.mAnalyseThread == null) {
- this.mAnalyseThread = MatrixHandlerThread.getNewHandlerThread("matrix_trace_analyse_thread");
- mHandler = new Handler(mAnalyseThread.getLooper());
- }
- // set up when onCreate
- mLazyScheduler.cancel();
- if (ApplicationLifeObserver.getInstance().isForeground()) {
- onFront(null);
- }
- }
这里先判断了 getMethodBeat().isRealTrace(),MethodBeat 是统计 ANR 和超时 Method 的重要类, 可以说是核心类, 一会在展开.
mAnalyseThread 是利用 MatrixHandlerThread 新起了一个线程, 将并它的 looer 交给 mHandler.EvilMethodTracer 重新了 doFrame 方法:
- @Override
- public void doFrame(long lastFrameNanos, long frameNanos) {
- //isIgnoreFrame 为 true 的时候, 一是出现了 ANR, 二是正在记录方法的缓存满了
- if (isIgnoreFrame) {
- // 清理缓存
- mActivityCreatedInfoMap.clear();
- // 改变标识
- setIgnoreFrame(false);
- // 重置编号
- getMethodBeat().resetIndex();
- return;
- }
- //index 指的是当前执行的方法对应缓存里的编号
- int index = getMethodBeat().getCurIndex();
- //hasEntered 说明 activity 已经存在;
- if (hasEntered && frameNanos - lastFrameNanos> mTraceConfig.getEvilThresholdNano()) {
- MatrixLog.e(TAG, "[doFrame] dropped frame too much! lastIndex:%s index:%s", 0, index);
- // 两帧之间相差超过一秒, 将缓存里的数据进行分析
- handleBuffer(Type.NORMAL, 0, index - 1, getMethodBeat().getBuffer(), (frameNanos - lastFrameNanos) / Constants.TIME_MILLIS_TO_NANO);
- }
- // 重置编号
- getMethodBeat().resetIndex();
- // 取消 mLazyScheduler, 即清空对应的 messageQueue
- mLazyScheduler.cancel();
- // 发送延迟消息, 默认 5 秒, 意味着五秒中之内没有回到 doframe 会执行 mLazyScheduler 里的 runnable
- mLazyScheduler.setUp(this, false);
- }
doFrame 时间差超过 1 秒会执行 handleBuffer, 时间差超过 5 秒会执行 mLazyScheduler 里的 onTimeExpire. 先来看 onTimeExpire 方法:
- public void onTimeExpire() {
- // 进到这里说明 doframe 延迟了 5 秒, 视为界面的 ANR
- // 在后台就忽略
- if (isBackground()) {
- MatrixLog.w(TAG, "[onTimeExpire] pass this time, on Background!");
- return;
- }
- // 起始时间
- long happenedAnrTime = getMethodBeat().getCurrentDiffTime();
- MatrixLog.w(TAG, "[onTimeExpire] maybe ANR!");
- // 暂停一次 Frame 捕捉
- setIgnoreFrame(true);
- // 缓存里的数据可以清空
- getMethodBeat().lockBuffer(false);
- // 分析缓存, Type 为 ANR
- handleBuffer(Type.ANR, 0, getMethodBeat().getCurIndex() - 1, getMethodBeat().getBuffer(), null, Constants.DEFAULT_ANR, happenedAnrTime, -1);
- }
当执行到 onTimeExpire 这里如果是前台就视为 ANR, 到目前 ANR 的捕捉大致知道了, 分析数据和 doframe 一样也是执行 handleBuffer, 这里也再次提到了 getMethodBeat(),getMethodBeat 返回的是本地持有的对象 sMethodBeat, 分析数据的方法 handleBuffer 也会用到 sMethodBeat 里的东西, 所以分析之前我们先查看下 MethodBeat 这个类究竟是做什么的, 分析 MethodBeat 之后再来看看 handleBuffer.
插曲 ---MethodBeat
MethodBeat 类主要做的事是用一个数组记录应用执行的每个方法和方法耗时, 用的是 long[], 使用方法 id 和耗时进行按位换算得要一个 long 值作为元素, 缓存默认长度是一百万. 在 MethodBeat 开始有这段代码:
- static {
- Hacker.hackSysHandlerCallback();
- sCurrentDiffTime = sLastDiffTime = System.nanoTime() / Constants.TIME_MILLIS_TO_NANO;
- sReleaseBufferHandler.sendEmptyMessageDelayed(RELEASE_BUFFER_MSG_ID, Constants.DEFAULT_RELEASE_BUFFER_DELAY);
- }
进入 Hacker.hackSysHandlerCallback
- public class Hacker {
- private static final String TAG = "Matrix.Hacker";
- public static boolean isEnterAnimationComplete = false;
- public static long sApplicationCreateBeginTime = 0L;
- public static int sApplicationCreateBeginMethodIndex = 0;
- public static long sApplicationCreateEndTime = 0L;
- public static int sApplicationCreateEndMethodIndex = 0;
- public static int sApplicationCreateScene = -100;
- public static void hackSysHandlerCallback() {
- try {
- sApplicationCreateBeginTime = System.currentTimeMillis();
- sApplicationCreateBeginMethodIndex = MethodBeat.getCurIndex();
- Class<?> forName = Class.forName("android.app.ActivityThread");
- Field field = forName.getDeclaredField("sCurrentActivityThread");
- field.setAccessible(true);
- Object activityThreadValue = field.get(forName);
- Field mH = forName.getDeclaredField("mH");
- mH.setAccessible(true);
- Object handler = mH.get(activityThreadValue);
- Class<?> handlerClass = handler.getClass().getSuperclass();
- Field callbackField = handlerClass.getDeclaredField("mCallback");
- callbackField.setAccessible(true);
- Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
- HackCallback callback = new HackCallback(originalCallback);
- callbackField.set(handler, callback);
- MatrixLog.i(TAG, "hook system handler completed. start:%s", sApplicationCreateBeginTime);
- } catch (Exception e) {
- MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
- }
- }
- }
这里利用了反射机制进行了 hook, 代码比较清晰, 目的很明确就是利用自己写的 HackCallback 来替换 ActivityThread 类里的 mCallback, 达到偷梁换柱的效果, 这样做的意义就是可以拦截 mCallback 的原有的消息, 然后选择自己要处理的消息, HackCallback 里的 handleMessage 实现如下:
- public boolean handleMessage(Message msg) {
- if (msg.what == LAUNCH_ACTIVITY) {
- Hacker.isEnterAnimationComplete = false;
- } else if (msg.what == ENTER_ANIMATION_COMPLETE) {
- Hacker.isEnterAnimationComplete = true;
- }
- if (!isCreated) {
- if (msg.what == LAUNCH_ACTIVITY || msg.what == CREATE_SERVICE || msg.what == RECEIVER) {
- Hacker.sApplicationCreateEndTime = System.currentTimeMillis();
- Hacker.sApplicationCreateEndMethodIndex = MethodBeat.getCurIndex();
- Hacker.sApplicationCreateScene = msg.what;
- isCreated = true;
- }
- }
- if (null == mOriginalCallback) {
- return false;
- }
- return mOriginalCallback.handleMessage(msg);
- }
拦截到了 LAUNCH_ACTIVITY 和 ENTER_ANIMATION_COMPLETE 消息, 这样就知道当前的 activity 创建到完成的时机.
现在再回到 MethodBeat, 它继承了 IMethodBeat 和 ApplicationLifeObserver.IObserver 接口, IObserver 就是之前用于 activity 生命周期的监听, IMethodBeat 是用于方法缓存数据相关的. 在看到这里源码时, 我就有个疑惑, 相信大多数人和我一样会有这样的疑惑: 它是如何记录每个方法的, 这些方法的执行时间是怎么样计算的? 粗略看了一下 MethodBeat 并没有找到数据的来源, 但是我找到了下面这两个个方法:
- //hook method when it's called in
- public static void i(int methodId) {
- if (isBackground) {
- return;
- }
- if (!isRealTrace) {
- updateDiffTime();
- sTimeUpdateHandler.sendEmptyMessage(UPDATE_TIME_MSG_ID);
- sBuffer = new long[Constants.BUFFER_TMP_SIZE];
- }
- isRealTrace = true;
- if (isCreated && Thread.currentThread() == sMainThread) {
- if (sIsIn) {
- Android.util.Log.e(TAG, "ERROR!!! MethodBeat.i Recursive calls!!!");
- return;
- }
- sIsIn = true;
- if (sIndex>= Constants.BUFFER_SIZE) {
- for (IMethodBeatListener listener : sListeners) {
- listener.pushFullBuffer(0, Constants.BUFFER_SIZE - 1, sBuffer);
- }
- sIndex = 0;
- } else {
- mergeData(methodId, sIndex, true);
- }
- ++sIndex;
- sIsIn = false;
- } else if (!isCreated && Thread.currentThread() == sMainThread && sBuffer != null) {
- if (sIsIn) {
- Android.util.Log.e(TAG, "ERROR!!! MethodBeat.i Recursive calls!!!");
- return;
- }
- sIsIn = true;
- if (sIndex <Constants.BUFFER_TMP_SIZE) {
- mergeData(methodId, sIndex, true);
- ++sIndex;
- }
- sIsIn = false;
- }
- }
- //hook method when it's called out.
- public static void o(int methodId) {
- if (isBackground || null == sBuffer) {
- return;
- }
- if (isCreated && Thread.currentThread() == sMainThread) {
- if (sIndex < Constants.BUFFER_SIZE) {
- mergeData(methodId, sIndex, false);
- } else {
- for (IMethodBeatListener listener : sListeners) {
- listener.pushFullBuffer(0, Constants.BUFFER_SIZE - 1, sBuffer);
- }
- sIndex = 0;
- }
- ++sIndex;
- } else if (!isCreated && Thread.currentThread() == sMainThread) {
- if (sIndex < Constants.BUFFER_TMP_SIZE) {
- mergeData(methodId, sIndex, false);
- ++sIndex;
- }
- }
- }
这两个方法的注释很值得注意: hook method when it's called in 和 hook method when it's called out, 意思是通过 hook 来执行的, 于是大致猜想它实现记录方法的思路就是在应用运行的每个方法之前调用 i(), 在每个方法结尾调用方法 o(), 记录方法名称和 o-i 的时间差. 这两个方法的最后会执行 mergeData 方法:
- private static void mergeData(int methodId, int index, boolean isIn) {
- long trueId = 0L;
- if (isIn) {
- trueId |= 1L << 63;
- }
- trueId |= (long) methodId << 43;
- trueId |= sCurrentDiffTime & 0x7FFFFFFFFFFL;
- sBuffer[index] = trueId;
- }
这里的 sCurrentDiffTime 就是 o(),i() 两个方法直接的时间差, 和 methodId 一起保存在 long 里.
思路有了, 它的实现方式呢? 既然是用的 hook, 于是全局搜索了这个 class 的名称, 在 matrix-gradle-plugin 库里找到了 MethodTracer 这个类, 这个类里找到了如下这段代码:
- protected TraceMethodAdapter(int API, MethodVisitor mv, int access, String name, String desc, String className,
- boolean hasWindowFocusMethod, boolean isMethodBeatClass) {
- super(API, mv, access, name, desc);
- TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
- this.methodName = traceMethod.getMethodName();
- this.isMethodBeatClass = isMethodBeatClass;
- this.hasWindowFocusMethod = hasWindowFocusMethod;
- this.className = className;
- this.name = name;
- }
- @Override
- protected void onMethodEnter() {
- TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
- if (traceMethod != null) {
- traceMethodCount.incrementAndGet();
- mv.visitLdcInsn(traceMethod.id);
- mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
- }
- }
- @Override
- protected void onMethodExit(int opcode) {
- TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
- if (traceMethod != null) {
- if (hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap)
- && mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
- TraceMethod windowFocusChangeMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
- TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
- if (windowFocusChangeMethod.equals(traceMethod)) {
- traceWindowFocusChangeMethod(mv);
- }
- }
- traceMethodCount.incrementAndGet();
- mv.visitLdcInsn(traceMethod.id);
- mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
- }
- }
- }
- private void traceApplicationContext(MethodVisitor mv, TraceMethod traceMethod) {
- mv.visitVarInsn(Opcodes.ALOAD, 0);
- mv.visitLdcInsn(traceMethod.methodName);
- mv.visitLdcInsn(traceMethod.desc);
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "trace", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V", false);
- }
- private void traceWindowFocusChangeMethod(MethodVisitor mv) {
- mv.visitVarInsn(Opcodes.ALOAD, 0);
- mv.visitVarInsn(Opcodes.ILOAD, 1);
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "at", "(Landroid/app/Activity;Z)V", false);
- }
这段代码是在内部类 TraceMethodAdapter 里的, 这个内部类又继承了 AdviceAdapter,AdviceAdapter 的实现来自引用的 org.wo2.asm 下的库, 从网上搜了一下, asm 是个 java 字节码操纵框架, 它可以直接以二进制形式动态地生成 stub 类或其他代理类, 或者在装载时动态地修改类. ASM 提供类似于 BCEL 和 SERP 之类的工具包的功能, 但是被设计得更小巧, 更快速, 这使它适用于实时代码插装. 对于 matrix 来说, 意思就是可以利用 asm 这个框架进行方法的装载, 在方法前执行 "com/tencent/matrix/trace/core/MethodBeat" 这个 class 里的 i() 方法, 在每个方法最后执行 o() 方法. 顿时觉得涨姿势了, 欲知详情可以自己这块的源码.
回到 EvilMethodTracer
methodBeat 里的方法缓存的来源终于有了, 现在回到 EvilMethodTracer 的 handleBuffer 分析来, 先看方法源码:
- private void handleBuffer(Type type, int start, int end, long[] buffer, ViewUtil.ViewInfo viewInfo, long cost, long happenTime, int subType) {
- if (null == buffer) {
- MatrixLog.e(TAG, "null == buffer");
- return;
- }
- if (cost < 0 || cost>= Constants.MAX_EVIL_METHOD_COST) {
- MatrixLog.e(TAG, "[analyse] trace cost invalid:%d", cost);
- return;
- }
- start = Math.max(0, start);
- end = Math.min(buffer.length - 1, end);
- if (start <= end) {
- long[] tmp = new long[end - start + 1];
- System.arraycopy(buffer, start, tmp, 0, end - start + 1);
- if (null != mHandler) {
- AnalyseExtraInfo info = new AnalyseExtraInfo(type, viewInfo, cost, happenTime);
- info.setSubType(subType);
- mHandler.post(new AnalyseTask(tmp, info));
- }
- }
- }
这段代码比较简单, 意思就是截图有效的那段数据交给 mHandler 所在的线程来执行, 有效的数据即 tmp, 真正执行的是 AnalyseTask 的 run 方法. AnalyseTask 的分析方法比较长, 大致的思路就是, 根据存储里的数据即每个方法 id 和执行时间, 找到时间异常的方法, 将异常的方法信息和一些基本信息保存在一个 JSONObject 里, 然后调用 plugin 的 sendReport 方法, 这里直接截取分析后发布的方法:
- try {
- JSONObject jsonObject = new JSONObject();
- jsonObject = DeviceUtil.getDeviceInfo(jsonObject, getPlugin().getApplication());
- jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, analyseExtraInfo.type.name());
- jsonObject.put(SharePluginInfo.ISSUE_SUB_TYPE, analyseExtraInfo.subType);
- jsonObject.put(SharePluginInfo.ISSUE_COST, analyseExtraInfo.cost);
- if (analyseExtraInfo.type == Type.ENTER) {
- JSONObject viewInfoJson = new JSONObject();
- ViewUtil.ViewInfo viewInfo = analyseExtraInfo.viewInfo;
- viewInfoJson.put(SharePluginInfo.ISSUE_VIEW_DEEP, null == viewInfo ? 0 : viewInfo.mViewDeep);
- viewInfoJson.put(SharePluginInfo.ISSUE_VIEW_COUNT, null == viewInfo ? 0 : viewInfo.mViewCount);
- viewInfoJson.put(SharePluginInfo.ISSUE_VIEW_ACTIVITY, null == viewInfo ? 0 : viewInfo.mActivityName);
- jsonObject.put(SharePluginInfo.ISSUE_VIEW_INFO, viewInfoJson);
- }
- jsonObject.put(SharePluginInfo.ISSUE_STACK, reportBuilder.toString());
- jsonObject.put(SharePluginInfo.ISSUE_STACK_KEY, key);
- sendReport(jsonObject);
- } catch (JSONException e) {
- MatrixLog.e(TAG, "[JSONException for stack %s, error: %s", reportBuilder.toString(), e);
- }
最终的格式如下:
- {
- "machine": 2015,
- "detail": "ENTER",
- "cost": 3205,
- "viewInfo": {
- "viewDeep": 10,
- "viewCount": 6,
- "activity": "TestFpsActivity"
- },
- "stack": "3,195,1,10\n1,33,1,58\n2,206,1,21\n3,161,1,16\n4,180,1,16\n5,169,1,16\n6,96,1,10\n7,98,1,10\n4,183,2,5\n5,211,6,0\n0,30,1,56\n",
- "stackKey": "0,30,1,56\n",
- "tag": "Trace_EvilMethod",
- "process": "sample.tencent.matrix"
- }
EvilMethodTracer 就先分析到这里, 下面来看看 StartUpTracer
StartUpTracer
StartUpTracer 是用于分析 activity 的启动时间的, 之前在分析 MethodBeat 时提到了通过 hook 记录 activity 在 LAUNCH_ACTIVITY 和 ENTER_ANIMATION_COMPLETE 两个时间点, 两个时间点在这里便是利用了起来. 实现的核心方法在 onActivityEntered() 里:
- String activityName = activity.getComponentName().getClassName();
- if (!mActivityEnteredMap.containsKey(activityName) || isFocus) {
- mActivityEnteredMap.put(activityName, System.currentTimeMillis());
- }
- if (!isFocus) {
- MatrixLog.i(TAG, "[onActivityEntered] isFocus false,activityName:%s", activityName);
- return;
- }
- if (mTraceConfig.isHasSplashActivityName() && activityName.equals(mTraceConfig.getSplashActivityName())) {
- MatrixLog.i(TAG, "[onActivityEntered] has splash activity! %s", mTraceConfig.getSplashActivityName());
- return;
- }
- getMethodBeat().lockBuffer(false);
- long activityEndTime = getValueFromMap(mActivityEnteredMap, activityName);
- long firstActivityStart = getValueFromMap(mFirstActivityMap, mFirstActivityName);
- if (activityEndTime <= 0 || firstActivityStart <= 0) {
- MatrixLog.w(TAG, "[onActivityEntered] error activityCost! [%s:%s]", activityEndTime, firstActivityStart);
- mFirstActivityMap.clear();
- mActivityEnteredMap.clear();
- return;
- }
这是方法的前一段, 这里有两个 hashmap,mFirstActivityMap 记录 activity 在 onCreate 时的时间, mActivityEnteredMap 记录 activity 在 onActivityEntered 时的时间, onActivityEntered 这个方法的调用是通过 hook 实现的在 WindowFocusChange 执行的,
OnActivityEnter() 方法后面的内容是:
- long activityCost = activityEndTime - firstActivityStart;
- //sApplicationCreateEndTime 是 methodBeat 通过 hook 得到的
- long appCreateTime = Hacker.sApplicationCreateEndTime - Hacker.sApplicationCreateBeginTime;
- long betweenCost = firstActivityStart - Hacker.sApplicationCreateEndTime;
- long allCost = activityEndTime - Hacker.sApplicationCreateBeginTime;
- if (isWarnStartUp) {
- betweenCost = 0;
- allCost = activityCost;
- }
- long splashCost = 0;
- if (mTraceConfig.isHasSplashActivityName()) {
- long tmp = getValueFromMap(mActivityEnteredMap, mTraceConfig.getSplashActivityName());
- splashCost = tmp == 0 ? 0 : getValueFromMap(mActivityEnteredMap, activityName) - tmp;
- }
- if (appCreateTime <= 0) {
- MatrixLog.e(TAG, "[onActivityEntered] appCreateTime is wrong! appCreateTime:%s", appCreateTime);
- mFirstActivityMap.clear();
- mActivityEnteredMap.clear();
- return;
- }
- if (mTraceConfig.isHasSplashActivityName() && splashCost <0) {
- MatrixLog.e(TAG, "splashCost < 0! splashCost:%s", splashCost);
- return;
- }
- EvilMethodTracer tracer = getTracer(EvilMethodTracer.class);
- if (null != tracer) {
- long thresholdMs = isWarnStartUp ? mTraceConfig.getWarmStartUpThresholdMs() : mTraceConfig.getStartUpThresholdMs();
- int startIndex = isWarnStartUp ? mFirstActivityIndex : Hacker.sApplicationCreateBeginMethodIndex;
- int curIndex = getMethodBeat().getCurIndex();
- if (allCost> thresholdMs) {
- MatrixLog.i(TAG, "appCreateTime[%s] is over threshold![%s], dump stack! index[%s:%s]", appCreateTime, thresholdMs, startIndex, curIndex);
- EvilMethodTracer evilMethodTracer = getTracer(EvilMethodTracer.class);
- if (null != evilMethodTracer) {
- evilMethodTracer.handleBuffer(EvilMethodTracer.Type.STARTUP, startIndex, curIndex, MethodBeat.getBuffer(), appCreateTime, Constants.SUBTYPE_STARTUP_APPLICATION);
- }
- }
- }
- MatrixLog.i(TAG, "[onActivityEntered] firstActivity:%s appCreateTime:%dms betweenCost:%dms activityCreate:%dms splashCost:%dms allCost:%sms isWarnStartUp:%b ApplicationCreateScene:%s",
- mFirstActivityName, appCreateTime, betweenCost, activityCost, splashCost, allCost, isWarnStartUp, Hacker.sApplicationCreateScene);
- mHandler.post(new StartUpReportTask(activityName, appCreateTime, activityCost, betweenCost, splashCost, allCost, isWarnStartUp, Hacker.sApplicationCreateScene));
- mFirstActivityMap.clear();
- mActivityEnteredMap.clear();
- isFirstActivityCreate = false;
- mFirstActivityName = null;
- onDestroy();
这段代码统计了 application 启动耗时, SplashActivity(欢迎页) 耗时, 应用和 activity 之间的耗时, 统计好的格式如下:
- {
- "machine": 4,
- "application_create": 415,
- "first_activity_create": 240,
- "stage_between_app_and_activity": 0,
- "scene": "com.tencent.mm.app.WeChatSplashActivity",
- "is_warm_start_up": false,
- "tag": "Trace_StartUp",
- "process": "com.tencent.mm",
- "time": 1528278018147
- }
StartUpTracer 先分析到这, 还剩下最后一个 FPSTracer
FPSTracer
FPSTracer 统计的是帧率, 统计对应的 activity,fragment 的掉帧水平, 利用的也是 Choreographer 的 doFrame(), 我们直接来看它的实现:
- @Override
- public void doFrame(long lastFrameNanos, long frameNanos) {
- //isInvalid 是值是否在前台, isDrawing 是否开始绘制
- if (!isInvalid && isDrawing && isEnterAnimationComplete() && mTraceConfig.isTargetScene(getScene())) {
- // 分析
- handleDoFrame(lastFrameNanos, frameNanos, getScene());
- }
- isDrawing = false;
- }
在开始 onDraw 开始后, 调用了 handleDoFrame
- private void handleDoFrame(long lastFrameNanos, long frameNanos, String scene) {
- int sceneId;
- if (mSceneToSceneIdMap.containsKey(scene)) {
- sceneId = mSceneToSceneIdMap.get(scene);
- } else {
- // 记录界面的名称和界面的编号
- sceneId = mSceneToSceneIdMap.size() + 1;
- mSceneToSceneIdMap.put(scene, sceneId);
- mSceneIdToSceneMap.put(sceneId, scene);
- }
- int trueId = 0x0;
- trueId |= sceneId;
- trueId = trueId <<22;
- // 计算此帧的耗时
- long offset = frameNanos - lastFrameNanos;
- trueId |= ((offset / FACTOR) & 0x3FFFFF);
- // 超过 5 秒
- if (offset>= 5 * 1000000000L) {
- MatrixLog.w(TAG, "[handleDoFrame] WARNING drop frame! offset:%s scene%s", offset, scene);
- }
- synchronized (this.getClass()) {
- // 记录
- mFrameDataList.add(trueId);
- }
- }
在这里只是做了记录, 内部的定时器 mLazyScheduler 在 onCreate 是启动, 时间间隔默认是 120 秒,
- public void onTimeExpire() {
- doReport();
- }
调用 doReport()
- private void doReport() {
- LinkedList<Integer> reportList;
- synchronized (this.getClass()) {
- if (mFrameDataList.isEmpty()) {
- return;
- }
- reportList = mFrameDataList;
- mFrameDataList = new LinkedList<>();
- }
- //reportList 里的元素包含了 sceneId 和帧耗时
- for (int trueId : reportList) {
- int scene = trueId>> 22;
- int durTime = trueId & 0x3FFFFF;
- LinkedList<Integer> list = mPendingReportSet.get(scene);
- if (null == list) {
- list = new LinkedList<>();
- mPendingReportSet.put(scene, list);
- }
- list.add(durTime);
- }
- reportList.clear();
- //mPendingReportSet 里取到了 scene 和其对应的帧内容
- for (int i = 0; i <mPendingReportSet.size(); i++) {
- int key = mPendingReportSet.keyAt(i);
- LinkedList<Integer> list = mPendingReportSet.get(key);
- if (null == list) {
- continue;
- }
- int sumTime = 0;
- int markIndex = 0;
- int count = 0;
- int[] dropLevel = new int[DropStatus.values().length]; // record the level of frames dropped each time
- int[] dropSum = new int[DropStatus.values().length]; // record the sum of frames dropped each time
- int refreshRate = (int) Constants.DEFAULT_DEVICE_REFRESH_RATE * OFFSET_TO_MS;
- for (Integer period : list) {
- sumTime += period;
- count++;
- int tmp = period / refreshRate - 1;
- if (tmp>= Constants.DEFAULT_DROPPED_FROZEN) {
- dropLevel[DropStatus.DROPPED_FROZEN.index]++;
- dropSum[DropStatus.DROPPED_FROZEN.index] += tmp;
- } else if (tmp>= Constants.DEFAULT_DROPPED_HIGH) {
- dropLevel[DropStatus.DROPPED_HIGH.index]++;
- dropSum[DropStatus.DROPPED_HIGH.index] += tmp;
- } else if (tmp>= Constants.DEFAULT_DROPPED_MIDDLE) {
- dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
- dropSum[DropStatus.DROPPED_MIDDLE.index] += tmp;
- } else if (tmp>= Constants.DEFAULT_DROPPED_NORMAL) {
- dropLevel[DropStatus.DROPPED_NORMAL.index]++;
- dropSum[DropStatus.DROPPED_NORMAL.index] += tmp;
- } else {
- dropLevel[DropStatus.DROPPED_BEST.index]++;
- dropSum[DropStatus.DROPPED_BEST.index] += (tmp <0 ? 0 : tmp);
- }
- if (sumTime>= mTraceConfig.getTimeSliceMs() * OFFSET_TO_MS) {
- // if it reaches report time
- float fps = Math.min(60.f, 1000.f * OFFSET_TO_MS * (count - markIndex) / sumTime);
- MatrixLog.i(TAG, "scene:%s fps:%s sumTime:%s [%s:%s]", mSceneIdToSceneMap.get(key), fps, sumTime, count, markIndex);
- try {
- JSONObject dropLevelObject = new JSONObject();
- dropLevelObject.put(DropStatus.DROPPED_FROZEN.name(), dropLevel[DropStatus.DROPPED_FROZEN.index]);
- dropLevelObject.put(DropStatus.DROPPED_HIGH.name(), dropLevel[DropStatus.DROPPED_HIGH.index]);
- dropLevelObject.put(DropStatus.DROPPED_MIDDLE.name(), dropLevel[DropStatus.DROPPED_MIDDLE.index]);
- dropLevelObject.put(DropStatus.DROPPED_NORMAL.name(), dropLevel[DropStatus.DROPPED_NORMAL.index]);
- dropLevelObject.put(DropStatus.DROPPED_BEST.name(), dropLevel[DropStatus.DROPPED_BEST.index]);
- JSONObject dropSumObject = new JSONObject();
- dropSumObject.put(DropStatus.DROPPED_FROZEN.name(), dropSum[DropStatus.DROPPED_FROZEN.index]);
- dropSumObject.put(DropStatus.DROPPED_HIGH.name(), dropSum[DropStatus.DROPPED_HIGH.index]);
- dropSumObject.put(DropStatus.DROPPED_MIDDLE.name(), dropSum[DropStatus.DROPPED_MIDDLE.index]);
- dropSumObject.put(DropStatus.DROPPED_NORMAL.name(), dropSum[DropStatus.DROPPED_NORMAL.index]);
- dropSumObject.put(DropStatus.DROPPED_BEST.name(), dropSum[DropStatus.DROPPED_BEST.index]);
- JSONObject resultObject = new JSONObject();
- resultObject = DeviceUtil.getDeviceInfo(resultObject, getPlugin().getApplication());
- resultObject.put(SharePluginInfo.ISSUE_SCENE, mSceneIdToSceneMap.get(key));
- resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject);
- resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject);
- resultObject.put(SharePluginInfo.ISSUE_FPS, fps);
- sendReport(resultObject);
- } catch (JSONException e) {
- MatrixLog.e(TAG, "json error", e);
- }
- dropLevel = new int[DropStatus.values().length];
- dropSum = new int[DropStatus.values().length];
- markIndex = count;
- sumTime = 0;
- }
- }
- // delete has reported data
- if (markIndex> 0) {
- for (int index = 0; index < markIndex; index++) {
- list.removeFirst();
- }
- }
- if (!list.isEmpty()) {
- MatrixLog.d(TAG, "[doReport] sumTime:[%sms < %sms], scene:[%s]", sumTime / OFFSET_TO_MS, mTraceConfig.getTimeSliceMs(), mSceneIdToSceneMap.get(key));
- }
- }
- }
最终解析出来的就是 activity 对应的帧耗时数据, 数据格式如下:
- {
- "machine": 2015,
- "scene": "sample.tencent.matrix.trace.TestFpsActivity",
- "dropLevel": {
- "DROPPED_HIGH": 4,
- "DROPPED_MIDDLE": 12,
- "DROPPED_NORMAL": 18,
- "DROPPED_BEST": 113
- },
- "dropSum": {
- "DROPPED_HIGH": 60,
- "DROPPED_MIDDLE": 96,
- "DROPPED_NORMAL": 51,
- "DROPPED_BEST": 6
- },
- "fps": 24.476625442504883,
- "tag": "Trace_FPS",
- "process": "sample.tencent.matrix"
- }
目前 tracePlugin 的内容分析完了, 其中有些数据的计算没有去展开, 大家可以自己查看.
tracePlugin 总结
这里主要分析得失 TracePlugin 的实现, 其中包括了 ANR 记录, 超时函数记录, 帧数统计和启动记录, 这还只是 Plugins 中的一个, 内容已经显得有点长了, 所以我决定后面的 IOCanaryPlugin,SQLiteLintPlugin,ResourcePlugin 都分成不同文章来分析.
自己才疏学浅, 肯定有很多不足的地方, 有遗漏或错误的地方欢迎指正.
来源: http://blog.51cto.com/14376323/2404426