这是功能优化系列之matrix框架的第10
篇文章,我将在功能优化专栏中对matrix apm框架做一个全面的代码剖析,功能优化是Android高级工程师必知必会的点,也是面试过程中的高频标题,对功能优化感兴趣的小伙伴能够去我主页检查所有关于matrix的分享。
前言
matrix卡顿监控,下面几篇归于根底,可先行查阅,不然今日的内容读起来可能有点困难。
- Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之gradle插件- 字节码插桩代码剖析
- Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之AppMethodBeat专项剖析
- Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之LooperMonitor源码剖析
- Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析
有了上边几篇根底,咱们剖析其他类型的tracer会势如破竹。言归正传,今日咱们要剖析的是matrix卡顿监控中的另一种tracer-FrameTracer-帧率监控。为什么要监控帧率呢?根本原因是为了保证帧率的安稳。一般来讲,Android设备大多都是60fps的帧率(当然也有90fps、120fps的),也便是画面每秒更新60次,假设使用的帧率能安稳的维持在60fps的话,对用户来讲体验是最好的。而要保证帧率维持在60fps,那么就要求每次改写在16.66毫秒内完结。咱们知道Android的改写是基于VSYNV信号的,Android体系每隔16.66毫秒宣布一次VSYNC信号,触发对UI进行渲染,VSYNC机制保证了Android的改写频率维持在一个固定的距离内,有利于帧率的安稳。所以FrameTracer存在的价值便是监控帧率是否安稳这一问题,也是卡顿监控中的一环。
FrameTracer是在TracePlugin中被初始化和启动,咱们从它的几个要害办法下手深入源码探索。
- 结构办法
- onStartTrace
- onStopTrace
结构办法
传入的supportFrameMetrics是一个boolean变量,当体系版本大于等于Android O时(8.0)为true。
public FrameTracer(TraceConfig config, boolean supportFrameMetrics) {
useFrameMetrics = supportFrameMetrics;
this.config = config;
//frameIntervalNs是从Choreographer上反射获取的值,一般是16.66毫秒(这儿是纳秒为单位)
//,不过与机型的改写率有关,默许机型便是60Hz的改写率,所以这儿等于16666667L纳秒
this.frameIntervalNs = UIThreadMonitor.getMonitor().getFrameIntervalNanos();
this.timeSliceMs = config.getTimeSliceMs();
this.isFPSEnable = config.isFPSEnable();
//下边是对帧率下降程度定义的几种类型
this.frozenThreshold = config.getFrozenThreshold();
this.highThreshold = config.getHighThreshold();
this.normalThreshold = config.getNormalThreshold();
this.middleThreshold = config.getMiddleThreshold();
if (isFPSEnable) {
addListener(new FPSCollector());
}
}
onStartTrace
onStartTrace会调用到onAlive, 能够看到isFPSEnable一定要为true,FrameTracer才会生效。
这儿供给了两种监控帧率的办法,一种针对8.0以下的机型,经过UIThreadMonitor的机制来完成,UIThreadMonitor的源码完成请查阅Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析;
另一种8.0及以上经过给Application注册监听来拿到Activity生命周期的回调,这样一来,使用内每一个Activity的创建或销毁就都在监控之内。这儿咱们只需要重视onActivityResumed和onActivityDestroyed办法。
@Override
public void onAlive() {
super.onAlive();
if (isFPSEnable) {
//8.0以下的机型,经过UIThreadMonitor的机制来完成帧率的监控
if (!useFrameMetrics) {
UIThreadMonitor.getMonitor().addObserver(this);
}
Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
}
}
咱们分别来看下这两种不同的完成方式,先从8.0以下的完成看起。
8.0以下UIThreadMonitor
UIThreadMonitor经过调用addObserver将当时类添加为监听者,在Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析中对UIThreadMonitor有专项剖析,这儿主要用到了它的回调办法doFrame,doFrame中会经过对Choreographer一些操作能够直接拿到使用改写相关的参数:
- focusedActivity:当时页面activity
- startNs:一帧改写的开始时刻
- endNs:一帧改写结束的时刻
- isVsyncFrame:是否是vsync
- intendedFrameTimeNs:vsync回调的时刻
- inputCostNs:input事情处理的时刻
- animationCostNs:动画处理的时刻
- traversalCostNs:traversal事情处理的时刻
UIThreadMonitor是怎么拿到这些数据的?请查阅Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析。
在doFrame办法中,使用处于前台时,会去汇总这些参数,然后调用notifyListener,notifyListener内部逻辑见后续剖析,因为8.0以上也会用到这个办法。
@Override
public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
if (isForeground()) {
notifyListener(focusedActivity, startNs, endNs, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
}
}
8.0以上registerActivityLifecycleCallbacks
8.0以上Android体系直接暴露了相关api,addOnFrameMetricsAvailableListener,能够直接调用这个办法注册监听,去获取到相关信息(其实7.0就现已有了这个api,仅仅为什么matrix8.0才使用?咱们暂时不重视了)。
onActivityResumed
当useFrameMetrics为true时,表明当时体系为8.0及以。这儿要害的代码是addOnFrameMetricsAvailableListener,经过调用window的addOnFrameMetricsAvailableListener办法注册一个监听,从而能够收到体系的回调。
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onActivityResumed(Activity activity) {
if (useFrameMetrics) {
//拿到改写率
this.refreshRate = (int) activity.getWindowManager().getDefaultDisplay().getRefreshRate();
//1s除以改写率得到的便是每帧履行的时长,如前边提到的16.66毫秒
this.frameIntervalNs = Constants.TIME_SECOND_TO_NANO / (long) refreshRate;
Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = new Window.OnFrameMetricsAvailableListener() {
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics);
//实践的vsync到来时刻
long vsynTime = frameMetricsCopy.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
//预期的vsync到来时刻, 假设此值与 VSYNC_TIMESTAMP 不同,则表明 UI 线程上发生了堵塞,阻止了 UI 线程及时响应vsync信号
long intendedVsyncTime = frameMetricsCopy.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP);
frameMetricsCopy.getMetric(FrameMetrics.DRAW_DURATION);
notifyListener(ProcessUILifecycleOwner.INSTANCE.getVisibleScene(), intendedVsyncTime, vsynTime, true, intendedVsyncTime, 0, 0, 0);
}
};
activity.getWindow().addOnFrameMetricsAvailableListener(onFrameMetricsAvailableListener, new Handler());
}
}
这儿是取了VSYNC_TIMESTAMP和INTENDED_VSYNC_TIMESTAMP两个值,INTENDED_VSYNC_TIMESTAMP表明该帧的 vsync 信号预期应该宣布时刻,VSYNC_TIMESTAMP是该帧的vsync信号实践宣布时刻的时刻,二者的差值就能够看作中心卡顿发生的时刻。
其实经过这个接口还能够获取更多信息,咱们看下FrameMetrics中定义的几种信息类型:
//处理输入事情花费的时刻, 单位纳秒
public static final int INPUT_HANDLING_DURATION = 1;
//处理动画履行花费的时刻, 单位纳秒
public static final int ANIMATION_DURATION = 2;
//measure和layout总共花费的时刻
public static final int LAYOUT_MEASURE_DURATION = 3;
//draw制作花费的时刻
public static final int DRAW_DURATION = 4;
//DisplayLists与显现线程同步花费的时刻
public static final int SYNC_DURATION = 5;
//向 GPU 发送制作指令花费的时刻
public static final int COMMAND_ISSUE_DURATION = 6;
//帧缓冲区交换花费的时刻
public static final int SWAP_BUFFERS_DURATION = 7;
//所有操作总共花费的时刻
public static final int TOTAL_DURATION = 8;
//是否是第一帧
public static final int FIRST_DRAW_FRAME = 9;
//预期vsync信号宣布的时刻
public static final int INTENDED_VSYNC_TIMESTAMP = 10;
//实践的vsync信号宣布的时刻
public static final int VSYNC_TIMESTAMP = 11;
//GPU核算花费的时刻
public static final int GPU_DURATION = 12;
所以8.0以上的处理逻辑便是经过拿到FrameMetrics目标,FrameMetrics目标中贮存了各种时刻信息,这儿取出要害VSYNC_TIMESTAMP和INTENDED_VSYNC_TIMESTAMP两个要害信息之后,就进入了notifyListener办法,可见8.0及以上和8.0以下仅仅获取时刻长度的方式不同,终究都是进入了notifyListener办法,异曲同工。
onActivityDestroyed
移除FrameMetricsAvailableListener
public void onActivityDestroyed(Activity activity) {
if (useFrameMetrics) {
activity.getWindow().removeOnFrameMetricsAvailableListener(frameListenerMap.remove(activity.hashCode()));
}
}
后续
经过上边两项的剖析之后咱们现已拿到了帧制作相关的时刻信息,拿到信息后进行了什么处理呢,这儿阅读一下后续的完成细节。
notifyListener
第一步,假设设置了掉帧监听接口,则根据两次时刻距离核算出掉帧的数量,当数量超越设定值时回调dropFrame。
//根据两次时刻距离核算出掉帧的数量
final long jitter = endNs - intendedFrameTimeNs;
final int dropFrame = (int) (jitter / frameIntervalNs);
if (dropFrameListener != null) {
if (dropFrame > dropFrameListenerThreshold) {
if (MatrixUtil.getTopActivityName() != null) {
//当数量超越设定值时回调dropFrame
dropFrameListener.dropFrame(dropFrame, jitter, MatrixUtil.getTopActivityName(), lastResumeTime);
}
}
}
第二步,履行这儿
if (null != listener.getExecutor()) {
//FrameTracer中默许值为300,所以一定是满意大于0的
if (listener.getIntervalFrameReplay() > 0) {
listener.collect(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame,
intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
}
}
collect
第三步,调用collect办法搜集信息。
@CallSuper
public void collect(String focusedActivity, long startNs, long endNs, int dropFrame, boolean isVsyncFrame,
long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
FrameReplay replay = FrameReplay.create();
//当时可见的Activity
replay.focusedActivity = focusedActivity;
replay.startNs = startNs;
replay.endNs = endNs;
//丢帧数
replay.dropFrame = dropFrame;
//是否是vsync
replay.isVsyncFrame = isVsyncFrame;
//vsync预期抵达时刻
replay.intendedFrameTimeNs = intendedFrameTimeNs;
//输入事情处理时刻,这儿为0
replay.inputCostNs = inputCostNs;
//动画事情处理时刻,这儿为0
replay.animationCostNs = animationCostNs;
//traversal事情处理时刻,这儿为0
replay.traversalCostNs = traversalCostNs;
list.add(replay);
//intervalFrame默许为300,也便是默许搜集300条后doReplay一次
if (list.size() >= intervalFrame && getExecutor() != null) {
final List<FrameReplay> copy = new LinkedList<>(list);
list.clear();
getExecutor().execute(new Runnable() {
@Override
public void run() {
doReplay(copy);
for (FrameReplay record : copy) {
record.recycle();
}
}
});
}
}
doReplay
第四步,遍历这300条数据,对每个数据履行doReplayInner。
public void doReplay(List<FrameReplay> list) {
super.doReplay(list);
for (FrameReplay replay : list) {
doReplayInner(replay.focusedActivity, replay.startNs, replay.endNs, replay.dropFrame, replay.isVsyncFrame,
replay.intendedFrameTimeNs, replay.inputCostNs, replay.animationCostNs, replay.traversalCostNs);
}
}
doReplayInner
第五步,visibleScene表明当时可见页面,以visibleScene为key,将visibleScene相同的数据封装到到一个FrameCollectItem目标中,并存入map, 调用collect。
public void doReplayInner(String visibleScene, long startNs, long endNs, int droppedFrames,
boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs,
long animationCostNs, long traversalCostNs) {
FrameCollectItem item = map.get(visibleScene);
if (null == item) {
item = new FrameCollectItem(visibleScene);
map.put(visibleScene, item);
}
item.collect(droppedFrames);
if (item.sumFrameCost >= timeSliceMs) { // report
map.remove(visibleScene);
item.report();
}
}
collect
第六步,collect办法中将丢帧的情况按照丢帧数量等级做了区分,分别为:
- frozen等级-丢帧大于42
- high等级-丢帧介于24到42
- middle等级-丢帧介于9到24
- mormal等级-丢帧介于3到9
- best等级-丢帧小于3
void collect(int droppedFrames) {
//frameIntervalNs表明每帧之间的时刻距离,如在60hz改写率的机型上为16.66毫秒,以纳秒
//为单位表明则为16666666纳秒,TIME_MILLIS_TO_NANO是1毫秒用纳秒来表明的数字,则便是
//1000000纳秒,所以终究核算得到frameIntervalCost为16.66
float frameIntervalCost = 1f * FrameTracer.this.frameIntervalNs
/ Constants.TIME_MILLIS_TO_NANO;
//根据丢掉的帧数乘以每帧的时长,得到的便是卡顿的总时长,以毫秒为单位
sumFrameCost += (droppedFrames + 1) * frameIntervalCost;
sumDroppedFrames += droppedFrames;
sumFrame++;
//假设丢帧数大于42,则以为丢帧度为“frozen”
if (droppedFrames >= frozenThreshold) {
dropLevel[DropStatus.DROPPED_FROZEN.index]++;
dropSum[DropStatus.DROPPED_FROZEN.index] += droppedFrames;
}
//假设丢帧数大于24可是小于42,则以为丢帧度为“high”
else if (droppedFrames >= highThreshold) {
dropLevel[DropStatus.DROPPED_HIGH.index]++;
dropSum[DropStatus.DROPPED_HIGH.index] += droppedFrames;
}
//假设丢帧数大于9可是小于24,则以为丢帧度为“middle”
else if (droppedFrames >= middleThreshold) {
dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
dropSum[DropStatus.DROPPED_MIDDLE.index] += droppedFrames;
}
//假设丢帧数大于3可是小于9,则以为丢帧度为“normal”
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] += Math.max(droppedFrames, 0);
}
}
第七步,当丢帧导致的卡顿时长超越timeSliceMs(默许10s)时,陈述卡顿问题。
if (item.sumFrameCost >= timeSliceMs) {
map.remove(visibleScene);
item.report();
}
report
简单看下陈述的参数
void report() {
float fps = Math.min(refreshRate, 1000.f * sumFrame / sumFrameCost);
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);
Issue issue = new Issue();
issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS);
issue.setContent(resultObject);
plugin.onDetectIssue(issue);
}
看一下打印的示例:
{
"machine": "BAD",
"cpu_app": 0,
"mem": 1495580672,
"mem_free": 654600,
"scene": "sample.tencent.matrix.issue.IssuesListActivity",
"dropLevel": {
"DROPPED_FROZEN": 1,
"DROPPED_HIGH": 0,
"DROPPED_MIDDLE": 1,
"DROPPED_NORMAL": 0,
"DROPPED_BEST": 0
},
"dropSum": {
"DROPPED_FROZEN": 2738,
"DROPPED_HIGH": 0,
"DROPPED_MIDDLE": 10,
"DROPPED_NORMAL": 0,
"DROPPED_BEST": 0
},
"fps": 0.04363667964935303
}
至此,帧率信息监听的代码就剖析完了。
onStopTrace
主要是资源清理操作。
public void onDead() {
super.onDead();
removeDropFrameListener();
if (isFPSEnable) {
UIThreadMonitor.getMonitor().removeObserver(this);
Matrix.with().getApplication().unregisterActivityLifecycleCallbacks(this);
}
}
总结
经过今日的剖析,咱们了解到FrameTracer进行帧率监听分为两种方式。以Android 8.0为分界线,8.0以下经过UIThreadMonitor完成,经过对主线程音讯行列中音讯的履行前后进行监听,核算出一帧画面履行过程中不同类型音讯的履行的时刻,然后进行核算剖析,达到监听帧率的作用;8.0及以上是经过Android体系供给的addOnFrameMetricsAvailableListener办法完成数据的获取,addOnFrameMetricsAvailableListener注册监听之后,能够拿到丰富的时刻信息为我所用,从而使监听帧率的完成更加的方便快捷,能够想象一下,假设未来某天,Android开发适配的最低机型为7.0时,那么使用体系api就足够帮咱们完成功能监控了,这是多么夸姣的一个期待。