卡顿分析
FPS帧率统计评测应用流畅度并不准确
系统获取FPS的原理:手机屏幕显示的内容是通过Android系统的SurfaceFlinger类,把当前系统里所有进程需要显示的信息合成一帧,然后提交到屏幕上显示,FPS就是1秒内SurfaceFlinger提交到屏幕的帧数,SurfaceFlinger目前的启动方式是做为init进程中的一个Service来启动。
App停止操作后,FPS还是在一直变化,这种情况是否会影响到FPS的准确度?
有的时候FPS很低,APP看起来却很流畅,是因为当前界面在1秒内只需要10帧的显示需求,当然不会卡顿,此时FPS只要高于10就可以了,如果屏幕根本没有绘制需求,那FPS的值就是0。
Vsync(垂直同步)指的是显卡的输出帧数和屏幕的垂直刷新率相同。
垂直同步的含义我们可以理解为,使得显卡生成帧的速度和屏幕刷新的速度的保持一致。举例来说,如果屏幕的刷新率为60Hz,那么生成帧的速度就应该被固定在1/60 s。
Android 4.1引入了VSync机制可以通过其Loop来了解当前App最高绘制能力。
Choreographer接收显示系统的时间脉冲(垂直同步信号-VSync信号),在下一个frame渲染时控制执行这些操作,控制同步处理输入(Input)、动画(Animation)、绘制(Draw)三个UI操作。
Choreographer类的构造方法:
1.初始化FrameHandler。接收处理消息。
2.初始化FrameDisplayEventReceiver。FrameDisplayEventReceiver用来接收垂直同步脉冲,就是VSync信号,VSync信号是一个时间脉冲,一般为60HZ,用来控制系统同步操作,怎么同ChoreoGrapher一起工作的,将在下文介绍。
3.初始化mLastFrameTimeNanos(标记上一个frame的渲染时间)以及mFrameIntervalNanos(帧率,fps,一般手机上为1s/60)。
4.初始化CallbackQueue,callback队列,将在下一帧开始渲染时回调。
一个简单的handler,处理3个类型的消息。
MSG_DO_FRAME:开始渲染下一帧的操作
MSG_DO_SCHEDULE_VSYNC:请求Vsync信号
MSG_DO_SCHEDULE_CALLBACK:请求执行callback
FrameDisplayEventReceiver继承自DisplayEventReceiver接收底层的VSync信号开始处理UI过程。VSync信号由SurfaceFlinger实现并定时发送。FrameDisplayEventReceiver收到信号后,调用onVsync方法组织消息发送到主线程处理。这个消息主要内容就是run方法里面的doFrame了,源码中mTimestampNanos是信号到来的时间参数。
1.PostCallBack,发起添加回调,这个FrameCallBack将在下一帧被渲染时执行。
2.AddToCallBackQueue,将FrameCallBack添加到回调队列里面,等待时机执行回调。每种类型的callback按照设置的执行时间(dueTime)顺序排序分别保存在一个单链表中。
3.判断FrameCallBack设定的执行时间是否在当前时间之后,若是,发送MSG_DO_SCHEDULE_CALLBACK消息到主线程,安排执行doScheduleCallback,安排执行CallBack。否则直接跳到第4步。
4.执行scheduleFrameLocked,安排执行下一帧。
5.判断上一帧是否已经执行,若未执行,当前操作直接结束。若已经执行,根据情况执行以下6、7步。
6.若使用垂直同步信号进行同步,则执行7.否则,直接跳到9。
7.若当前线程是UI线程,则通过执行scheduleVsyncLocked请求垂直同步信号。否则,送MSG_DO_SCHEDULE_VSYNC消息到主线程,安排执行doScheduleVsync,在主线程调用scheduleVsyncLocked。
8.收到垂直同步信号,调用FrameDisplayEventReceiver.onVsync(),发送消息到主线程,请求执行doFrame。
9.执行doFrame,渲染下一帧。
Choreographer中可以实现FrameCallback接口,然后实现里边的doFrame方法,可以获取到帧率等信息,通过Choreographer.getInstance().postFrameCallback(new MyFPSFrameCallback());
把你的回调添加到Choreographer之中,那么在下一个frame被渲染的时候就会回调你的callback,执行你定义的doFrame操作,这时候你就可以获取到这一帧的开始渲染时间并做一些自己想做的事情了。
开源组件Tiny Dancer就是根据这个原理获取每一帧的渲染时间,继而分析实现获取设备的当前帧率的。有兴趣的人可以查看。
private long mLastFrameTimeNanos=0; //实际执行当前frame的时间
private long mFrameIntervalNanos;
public FpsInfo(long lastFrameTimeNanos){
mLastFrameTimeNanos=lastFrameTimeNanos;
mFrameIntervalNanos=(long)(1000000000 / 60.0); // 帧率,也就是渲染一帧的时间,getRefreshRate是刷新率,一般是60
}
@Override
public void doFrame(long frameTimeNanos) { //Vsync信号到来的时间frameTimeNanos
if(mLastFrameTimeNanos==0){
mLastFrameTimeNanos=frameTimeNanos;
}
final long jitterNanos=frameTimeNanos-mLastFrameTimeNanos;
if(jitterNanos>=mFrameIntervalNanos){
final long skippedFrames=jitterNanos/mFrameIntervalNanos;
//Log.i("Testing","Skipped "+skippedFrames+" frames!");
if(skippedFrames>60){ //设定丢帧率 60
Log.i("Testing","Skipped "+skippedFrames+" frames!");
}
}
mLastFrameTimeNanos=frameTimeNanos;
//注册下一帧的回调
Choreographer.getInstance().postFrameCallback(this);
}
尽量避免在执行动画或渲染操作之后在主线程执行操作,在之前或之后都应该尽量避免发送消息到主线程looper。
流畅度衡量指标:
1.帧率FPS
2.丢帧SF(Skipped frame)
3.流畅度SM(SMoothness)。
腾讯bugly对于流畅度及丢帧的链接:http://www.csdn.net/article/2015-06-12/2824949/4
SurfaceFlinger原理及启动篇:http://gityuan.com/2017/02/11/surface_flinger/
优化流程:
1.UI层级嵌套绘制
2.静态代码扫描工具配合
3.Traceview定位主线程卡顿问题
4.Systrace分析
5.Strictmode严苛模式主线程耗时操作
6.Blockcanary非侵入式自动检测卡顿