参考
View、Window、WindowManager—vsync信号
View、Window、WindowManager—Choreographer源码阅读
Matrix提供了fps检测的功能, 该功能由 FrameTracer
完成, 这里围绕FrameTracer分析
/**
* 何时被回调?
* visibleScene: 当前Activity名
* taskCost: 整个任务耗时
* frameCostMs: 该帧耗时
* droppedFrames: 消耗帧数
* isContainsFrame: 是否属于帧刷新
*/
public void doFrameAsync(String visibleScene,
long taskCost,
long frameCostMs,
int droppedFrames,
boolean isContainsFrame);
涉及到的主要method:
1. new Matrix: Matric初始化
-> 1 Plugin.init: 插件初始化
2. Plugin.init: 插件初始化
-> 1. new AnrTracer
-> 2. new FrameTracer
-> 3. new EvilMethodTracer
-> 4. new StartupTracer
3. TracePlugin.start: 启动TracePlugin插件
-> 1. UIThreadMonitor.getMonitor().init: UIThreadMonitor初始化
-> 2. UIThreadMonitor.getMonitor().onStart()
-> 3. frameTracer.onStartTrace()
4. UIThreadMonitor.init: UIThreadMonitor初始化
-> 1. LooperMonitor初始化
-> 2. 获取Choreographer实例
-> 3. 反射获取Choreographer.mLock
-> 4. 返回获取Choreographer.mCallbackQueues
-> 5. 反射获取addInputQueue = Choreographer.callbackQueues[0]
-> 6. 反射获取addAnimationQueue = Choreographer.callbackQueues[1]
-> 7. 反射获取addTraversalQueue = Choreographer.callbackQueues[2]
-> 8. 返回获取Choreographer.mFrameIntervalNanos
-> 9. LooperMonitor.register注册Message事件分发的监听
5. LooperMonitor初始化
-> 1. LooperMonitor.resetPrinter: hook Looper中的Printer
-> 2. LooperMonitor.dispatch: Message事件分发
-> 3. UIThreadMonitor.dispatchBegin: Message事件开始分发
-> 4. UIThreadMonitor.dispatchEnd: Message事件结束分发
6. UIThreadMonitor.onStart
-> 1. callbackExist = new boolean[CALLBACK_LAST + 1]
-> 2. queueStatus = new int[CALLBACK_LAST + 1]
-> 3. queueCost = new long[CALLBACK_LAST + 1]
-> 4. UIThreadMonitor.addFrameCallback
-> 5. UIThreadMonitor.run
-> 6. UIThreadMonitor.doFrameBegin
-> 7. UIThreadMonitor.doQueueBegin
-> 8. UIThreadMonitor.doQueueEnd
7. frameTracer.onStartTrace
-> 1. onAlive -> UIThreadMonitor.getMonitor().addObserver(this);
8. 帧刷新回调, Message事件回调
-> 1. FrameTracer.dispatchBegin
-> 2. FrameTracer.doFrame
-> 3. FrameTracer.dispatchEnd
9. FrameTracer.doFrame
-> 1. FrameTracer.notifyListener
-> 2. FPSCollector.doFrameAsync
接下来围绕这些方法进行分析
private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
this.application = app;
this.pluginListener = listener;
this.plugins = plugins;
AppActiveMatrixDelegate.INSTANCE.init(application);
for (Plugin plugin : plugins) {
// 各自插件的初始化, 这里主要分析TracePlugin.init的初始化
plugin.init(application, pluginListener);
pluginListener.onInit(plugin);
}
}
@Override
public void init(Application app, PluginListener listener) {
super.init(app, listener);
// 1.获取AnrTracer
anrTracer = new AnrTracer(traceConfig);
// 2.获取FrameTracer
frameTracer = new FrameTracer(traceConfig);
// 3.获取EvilMethodTracer
evilMethodTracer = new EvilMethodTracer(traceConfig);
// 4.获取StartupTracer
startupTracer = new StartupTracer(traceConfig);
}
public FrameTracer(TraceConfig config) {
this.config = config;
// 1.一帧的耗时16ms
this.frameIntervalMs = TimeUnit.MILLISECONDS.convert(UIThreadMonitor.getMonitor().getFrameIntervalNanos(), TimeUnit.NANOSECONDS) + 1;
// 2.fps上报时间阈值
this.timeSliceMs = config.getTimeSliceMs();
// 3.fps监控是否打开
this.isFPSEnable = config.isFPSEnable();
// 4.一秒钟掉帧数量: 42帧为FROZEN
this.frozenThreshold = config.getFrozenThreshold();
// 5.一秒钟掉帧数量: 24帧为HIGH
this.highThreshold = config.getHighThreshold();
// 6.一秒钟掉帧数量: 3帧为NORMAL
this.normalThreshold = config.getNormalThreshold();
// 7.一秒钟掉帧数量: 9帧为MIDDLE
this.middleThreshold = config.getMiddleThreshold();
MatrixLog.i(TAG, "[init] frameIntervalMs:%s isFPSEnable:%s", frameIntervalMs, isFPSEnable);
if (isFPSEnable) {
// 添加FPS收集器
addListener(new FPSCollector());
}
}
(TracePlugin初始化时会初始化FrameTracer)
, 之后通过TracePlugin.start开始帧率的检测@Override
public void start() {
super.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
if (!UIThreadMonitor.getMonitor().isInit()) {
// 1.初始化UIThreadMonitor
UIThreadMonitor.getMonitor().init(traceConfig);
}
AppMethodBeat.getInstance().onStart();
// 2.启动UIThreadMonitor
UIThreadMonitor.getMonitor().onStart();
anrTracer.onStartTrace();
// 3.启动FrameTracer
frameTracer.onStartTrace();
evilMethodTracer.onStartTrace();
startupTracer.onStartTrace();
}
};
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
runnable.run();
} else {
MatrixHandlerThread.getDefaultMainHandler().post(runnable);
}
}
public void init(TraceConfig config) {
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
throw new AssertionError("must be init in main thread!");
}
this.config = config;
// 1.获取主线程的Choreographer
choreographer = Choreographer.getInstance();
// 2.反射获取Choreographer mLock变量
callbackQueueLock = reflectObject(choreographer, "mLock");
// 3.反射获取Choreographer mCallbackQueues变量
callbackQueues = reflectObject(choreographer, "mCallbackQueues");
// 4.反射获取处理input事件的CallbackQueue的addCallbackLocked方法
addInputQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
// 5.反射获取处理animation的CallbackQueue的addCallbackLocked方法
addAnimationQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
// 6.反射获取处理UI刷新的CallbackQueue的addCallbackLocked方法
addTraversalQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
// 7.反射获取frameIntervalNanos变量
frameIntervalNanos = reflectObject(choreographer, "mFrameIntervalNanos");
// 8.注册监听Message事件分发的回调
LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
@Override
public boolean isValid() {
return isAlive;
}
@Override
public void dispatchStart() {
super.dispatchStart();
UIThreadMonitor.this.dispatchBegin();
}
@Override
public void dispatchEnd() {
super.dispatchEnd();
UIThreadMonitor.this.dispatchEnd();
}
});
this.isInit = true;
}
public LooperMonitor(Looper looper) {
// 1.主线程的Looper
this.looper = looper;
// 2.向Looper中注入LooperPrinter, 获取事件执行的开始与结束的监听
resetPrinter();
// 3.添加IdleHandler
addIdleHandler(looper);
}
public void println(String x) {
if (null != origin) {
origin.println(x);
}
if (!isHasChecked) {
isValid = x.charAt(0) == '>' || x.charAt(0) == '<';
isHasChecked = true;
if (!isValid) {
MatrixLog.e(TAG, "[println] Printer is inValid! x:%s", x);
}
}
if (isValid) {
// 利用Looper.loop方法中日志的打印, >开头表示事件开始进行, <开头表示结束
dispatch(x.charAt(0) == '>', x);
}
}
public class Looper {
public static void loop() {
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
当Message事件开始和结束时会分别回调Printer.println方法, 并带上开始/结束的标识, 这里最终会调用到LooperMonitor.dispatch方法
public synchronized void onStart() {
if (!isAlive) {
this.isAlive = true;
synchronized (this) {
callbackExist = new boolean[CALLBACK_LAST + 1];
}
// 1.定义与事件相对应的数组, 记录每个事件的开始/结束
queueStatus = new int[CALLBACK_LAST + 1];
// 2.定义与事件相对应的数组, 记录每个type对应的执行时间
queueCost = new long[CALLBACK_LAST + 1];
// 3.首先回调input事件
addFrameCallback(CALLBACK_INPUT, this, true);
}
}
private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
// 1.记录事件是否存在, 默认为false
if (callbackExist[type]) {
MatrixLog.w(TAG, "[addFrameCallback] this type %s callback has exist! isAddHeader:%s", type, isAddHeader);
return;
}
// 2.
if (!isAlive && type == CALLBACK_INPUT) {
MatrixLog.w(TAG, "[addFrameCallback] UIThreadMonitor is not alive!");
return;
}
// 3.
synchronized (callbackQueueLock) {
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) {
/**
* 1.method对应CallbackQueue.addCallbackLocked方法
* 2.将callback(->UIThreadMonitor)添加到CallbackRecord队列中
*/
method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
callbackExist[type] = true;
}
}
}
UIThreadMonitor.onStart() //这个方法在稍后模块会分析
-> CallbackRecord.enqueue(UIThreadMonitor)
FrameDisplayEventReceiver.onVsync() -> FrameHandler.sendMessageAtTime()
-> Printer.println(">>>>> Dispatching to...")
-> FrameDisplayEventReceiver.run()
-> Choreographer.doFrame()
-> CallbackRecord.doCallbacks()
-> CallbackRecord.run()
-> UIThreadMonitor.run()
-> mTraversalRunnable.run()
-> ViewRootImpl.performTraversal()
-> Printer.println("<<<<< Finished to...")
// isBegin: true: 事件开始, false: 事件结束
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();
}
}
}
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
long now = System.nanoTime();
if (timestampNanos > now) {
timestampNanos = now;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
measure
、layout
、draw
绘制操作, 上层回调的入口是 FrameDisplayEventReceiver.onVsync
, 在onVsync通过Handler.sendMessageAtTime向Looper发送事件, 然后调用FrameDisplayEventReceiver.run方法hook
, 完成对FPS的检测private void dispatchBegin() {
// 1.从开机到现在的毫秒数
token = dispatchTimeMs[0] = SystemClock.uptimeMillis();
// 2.当前线程运行的毫秒数
dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();
synchronized (observers) {
for (LooperObserver observer : observers) {
if (!observer.isDispatchBegin()) {
// 这里的observer指向的就是在FrameTracer.onStartTrace中通过addObserver
// 注册回调事件
observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
}
}
}
}
public void dispatchBegin(long beginMs, long cpuBeginMs, long token) {
// 设置标识位isDispatchBegin true
isDispatchBegin = true;
}
4.3事件分发与hook的流程
事件分发开始时将标识位置为true, 并且输出日志, 当View绘制结束之后, 输出日志, 并调用事件分发结束的接口dispatchEnd
结合流程 4.3
如果当前事件(Message)对应的是vsync信号的事件, 既从FrameDisplayEventReceiver.onVsync()
发出来的消息, 则在UIThreadMonitor.dispatchEnd调用之前会先执行UIThreadMonitor.run
public void run() {
final long start = System.nanoTime();
// 1.当前事件为帧刷新事件, 将标识位isBelongFrame置为true
doFrameBegin(token);
// 2.进行View绘制时, 会依次调用input、animation、traversal事件run方法
doQueueBegin(CALLBACK_INPUT);
// 3.CallbackRecord(input)结束run之前hook一个animation的回调
addFrameCallback(CALLBACK_ANIMATION, new Runnable() {
@Override
public void run() {
// 4.CallbackRecord(animation).run执行时触发这里的回调
doQueueEnd(CALLBACK_INPUT);
doQueueBegin(CALLBACK_ANIMATION);
}
}, true);
// 4.CallbackRecord(input)结束run之前hook一个traversal的回调
addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {
@Override
public void run() {
// 5.CallbackRecord(traversal).run执行时触发这里的回调
doQueueEnd(CALLBACK_ANIMATION);
doQueueBegin(CALLBACK_TRAVERSAL);
}
}, true);
}
private void doFrameBegin(long token) {
// 置为true, 表示当前事件为帧刷新事件
this.isBelongFrame = true;
}
private void doQueueBegin(int type) {
// 1.记录事件type(input, animation, traversal)的状态
queueStatus[type] = DO_QUEUE_BEGIN;
// 2.记录事件开始时的时间
queueCost[type] = System.nanoTime();
}
private void doQueueEnd(int type) {
// 1.记录事件结束的状态
queueStatus[type] = DO_QUEUE_END;
// 2.记录事件(input、animation、traversal)结束时花费的时间
queueCost[type] = System.nanoTime() - queueCost[type];
synchronized (this) {
// 3.当前事件被移除
callbackExist[type] = false;
}
}
注:
当前执行到这里, 只记录了input、animation事件的结束状态, 并没有记录traversal结束的状态, traversal结束的状态在dispatchEnd的doFrameEnd中执行
当前Message事件执行完成, 回调dispatchEnd方法
private void dispatchEnd() {
// 1.当前事件是否属于帧刷新, true: 属于帧刷新Message
if (isBelongFrame) {
// 2.重置isBelongFrame, 记录CALLBACK_TRAVERSAL事件结束时的相关状态
doFrameEnd(token);
}
// 2.start: 开机到Message开始执行之前的时间(毫秒数)
long start = token;
// 3.end: 从开机到Message执行结束的时间(毫秒数)
long end = SystemClock.uptimeMillis();
synchronized (observers) {
for (LooperObserver observer : observers) {
if (observer.isDispatchBegin()) {
/**
* 4.具体到LooperMonitor.doFrame先不作分析, 这里只记录每个参数的含义:
* (1)token: 在dispatchBegin中被赋值, 记录的是开机到Message开始执行时的时间
* (2)SystemClock.uptimeMillis: 开机到Message执行结束的时间(毫秒数)
* (3)isBelongFrame: 这里只是一个标志位的作用
* (4)end-start: 对应一次Message消耗的时间
* (5)queueCost[INPUT]: input事件消耗的时间
* (6)queueCost[ANIMATION]: animation事件消耗的时间
* (7)queueCost[TRAVERSAL]: traversal事件消耗的时间
*/
observer.doFrame(AppMethodBeat.getVisibleScene(), token, SystemClock.uptimeMillis(), isBelongFrame ? end - start : 0, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
}
}
}
// 5.当前线程执行的时间
dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
// 6.从开机到当前Message执行完毕的时间
dispatchTimeMs[1] = SystemClock.uptimeMillis();
synchronized (observers) {
for (LooperObserver observer : observers) {
if (observer.isDispatchBegin()) {
observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isBelongFrame);
}
}
}
}
dispatchTimeMs[0]:
从开机到Message执行开始的时间
dispatchTimeMs[1]:
从开机到Message执行完毕的时间
dispatchTimeMs[2]:
Message执行开始时当前线程消耗的时间
dispatchTimeMs[3]:
Message执行结束时当前线程消耗的时间
这个问题留待手撸Matrix-FrameTracer代码之后再确定是否自己理解有误
/**
* focusedActivityName: 当前Activity
* start: 从开机到Message开始执行时的时间(毫秒数)
* end: 从开机到Message执行结束的时间(毫秒数)
* frameCostMs: end - start: 一帧消耗的时间
* inputCostNs: 一帧input事件消耗的时间
* animationCostNs: 一帧animation消耗的时间
* traversalCostNs: 一帧traversal消耗的时间
*/
public void doFrame(String focusedActivityName, long start, long end, long frameCostMs, long inputCostNs, long animationCostNs, long traversalCostNs) {
if (isForeground()) {
notifyListener(focusedActivityName, end - start, frameCostMs, frameCostMs >= 0);
}
}
/**
* 1.taskCostMs: 一次Message事件消耗的时间
* 2.frameCostMs: 一帧消耗的时间
* 3.是否为帧刷新: 结合UIThreadMonitor.doFrame可知, 如果是帧刷新, frameCostMs > 0,
* 即isContainsFrame = true
*/
private void notifyListener(final String visibleScene, final long taskCostMs, final long frameCostMs, final boolean isContainsFrame) {
long start = System.currentTimeMillis();
synchronized (listeners) {
for (final IDoFrameListener listener : listeners) {
if (config.isDevEnv()) {
listener.time = SystemClock.uptimeMillis();
}
// 1.一次Message消耗的时间, 理论上一帧需要的时间16ms, 因此这里的dropFrame是指
// 一次Message事件消耗的帧数.
final int dropFrame = (int) (taskCostMs / frameIntervalMs);
listener.doFrameSync(visibleScene, taskCostMs, frameCostMs, dropFrame, isContainsFrame);
if (null != listener.getExecutor()) {
listener.getExecutor().execute(new Runnable() {
@Override
public void run() {
// 2.这里只分析FPSCollector.doFrameAsync
listener.doFrameAsync(visibleScene, taskCostMs, frameCostMs, dropFrame, isContainsFrame);
}
});
}
}
}
}
dropFrame:
理论上一次Message需要的时间与一帧的时间相同为16ms, 因此dropFrame理论上取值应该为1, 因此dropFrame的值越大, 表明Message花费的时间越多, 掉帧也就越多.
/**
* droppedFrames: 一次Message事件消耗的帧数
*/
public void doFrameAsync(String visibleScene, long taskCost, long frameCostMs, int droppedFrames, boolean isContainsFrame) {
super.doFrameAsync(visibleScene, taskCost, frameCostMs, droppedFrames, isContainsFrame);
FrameCollectItem item = map.get(visibleScene);
if (null == item) {
item = new FrameCollectItem(visibleScene);
map.put(visibleScene, item);
}
// 1.收集当前Activity消耗的时间
item.collect(droppedFrames, isContainsFrame);
if (item.sumFrameCost >= timeSliceMs) { // report
// 2.上报之前通过remove对FrameCollectionItem进行重置
map.remove(visibleScene);
// 3.上报数据
item.report();
}
}
void collect(int droppedFrames, boolean isContainsFrame) {
// 1.一帧需要的时间16.7ms
long frameIntervalCost = UIThreadMonitor.getMonitor().getFrameIntervalNanos();
// 2.总消耗时间
sumFrameCost += (droppedFrames + 1) * frameIntervalCost / Constants.TIME_MILLIS_TO_NANO;
// 3.总丢帧率
sumDroppedFrames += droppedFrames;
// 4.doFrameAsync回调次数, 回调一次表示一帧刷新结束
sumFrame++;
if (!isContainsFrame) {
// 除过刷新帧事件外, 其他事件数
sumTaskFrame++;
}
if (droppedFrames >= frozenThreshold) {
dropLevel[DropStatus.DROPPED_FROZEN.index]++;
dropSum[DropStatus.DROPPED_FROZEN.index] += droppedFrames;
} else if (droppedFrames >= highThreshold) {
dropLevel[DropStatus.DROPPED_HIGH.index]++;
dropSum[DropStatus.DROPPED_HIGH.index] += droppedFrames;
} else if (droppedFrames >= middleThreshold) {
dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
dropSum[DropStatus.DROPPED_MIDDLE.index] += droppedFrames;
} else if (droppedFrames >= normalThreshold) {
dropLevel[DropStatus.DROPPED_NORMAL.index]++;
dropSum[DropStatus.DROPPED_NORMAL.index] += droppedFrames;
} else {
dropLevel[DropStatus.DROPPED_BEST.index]++;
dropSum[DropStatus.DROPPED_BEST.index] += (droppedFrames < 0 ? 0 : droppedFrames);
}
}
每个页面监控的总时间超过预设阈值就进行上报
void report() {
// 1.1秒内帧数
float fps = Math.min(60.f, 1000.f * sumFrame / sumFrameCost);
MatrixLog.i(TAG, "[report] FPS:%s %s", fps, toString());
try {
TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (null == plugin) {
return;
}
// 2.记录卡顿级别以及出现的次数
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]);
// 3.记录卡顿级别及掉帧总次数
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, plugin.getApplication());
resultObject.put(SharePluginInfo.ISSUE_SCENE, visibleScene);
resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject);
resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject);
resultObject.put(SharePluginInfo.ISSUE_FPS, fps);
resultObject.put(SharePluginInfo.ISSUE_SUM_TASK_FRAME, sumTaskFrame);
Issue issue = new Issue();
issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS);
issue.setContent(resultObject);
plugin.onDetectIssue(issue);
} catch (JSONException e) {
MatrixLog.e(TAG, "json error", e);
} finally {
sumFrame = 0;
sumDroppedFrames = 0;
sumFrameCost = 0;
sumTaskFrame = 0;
}
}