简介
要进行 FPS 检测,自然要先理解 FPS 的概念。
FPS 即 Frames per second。(此处省略 1000 字,请自行百度)
在上方的概念中,60 帧每秒以及 16.67 毫秒这两个关键数据代表什么含义呢?又是怎么得来的呢?请阅读UI卡顿优化这篇文档中的「神奇的16ms」一节。
好了,假装 FPS 的基本概念我们都已理解了,下面我们看看如何用代码实现 FPS 检测吧!
实现
方案选择
adb shell dumpsys gfxinfo 【packagename】
执行以下命令,我们可以获取到下方信息:
从上图可以看到,此方案使用起来非常方便,但是遗憾的是必须是 Android M 版本以上才支持,而且需要拖动屏幕产生的数据才比较准确,对于我来说不太合适,只能 pass。
adb shell dumpsys SurfaceFlinger --latency 【packagename】/【Activity的路径】
执行以下命令,我们可以获取到下方信息:
第一行是设备的刷新周期 refresh-period,单位是纳秒,换算成毫秒就是 16666666\1000\1000≈16.66ms;这条命令的含义是获取当前 layer(窗口、图层)的最近 128 帧的信息(仅保存 128 帧),所以上面我的截图不够全面,实际上总共有128行(刨去第一行刷新率,需要按127帧来换算),三列数据的解释:
- 第一列:开始绘制图像的瞬时时间。
- 第二列:VSYNC信令将软件SF帧传递给硬件HW之前的垂直同步时间。
- 第三列:SF将帧传递给HW的瞬时时间,及完成绘制的瞬时时间。
如果是单 Activity 应用,此方案使用起来非常方便,但是遗憾的是单 Activity 的应用何其之少,此方案对于我这懒人来说不太合适,只能 pass。
Choreographer
使用 Choreographer 进行帧率的监控。
做应用层的 Android 开发,看到 Choreographer 可能会有以下疑问吧:
- Choreographer 是啥?说实话,我也不知道。
- 你不知道,怎么知道有它的存在的?查看大佬的源码(https://github.com/didi/DoraemonKit)获悉。
仔细查看了一波源码,发现使用真简单,正好适合我这懒人!
有兴趣看源码的可查看原理一节,若无想法可直接拉至文末,查看代码实现。
原理
Choreographer 主要作用是协调动画,输入和绘制的时间,它从显示子系统接收定时脉冲(例如垂直同步),然后安排渲染下一个 frame 的一部分工作。
FrameCallback 是和 Choreographer 交互,在下一个 frame 被渲染时触发的接口类。我们主要从自定义 FrameCallback 作为切入口,尝试窥探一下 Choreographer 的实现原理!
源码查看地址:https://www.androidos.net.cn/android/8.0.0_r4/xref
关键变量
构造函数
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
//创建 Handle 对象,用于处理消息
mHandler = new FrameHandler(looper);
//创建接收 VSYNC 信号的对象
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
//初始化上一次 frame 渲染的时间点
mLastFrameTimeNanos = Long.MIN_VALUE;
//帧率,也就是渲染一帧的时间,getRefreshRate 是刷新率,大部分手机是 60
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//创建回调队列
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
FrameHandler
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
//渲染下一个 frame
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//请求 VXYNC 信号
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//处理 callback
doScheduleCallback(msg.arg1);
break;
}
}
}
FrameDisplayEventReceiver
FrameDisplayEventReceiver 是 DisplayEventReceiver 的子类,我们先来看下 DisplayEventReceiver 类吧。是接收VSYNC信息的java层实现。
public abstract class DisplayEventReceiver {
//此处省略部分参数
//此处省略初始化等部分无关代码
/**
* Called when a vertical sync pulse is received.
* The recipient should render a frame and then call {@link #scheduleVsync}
* to schedule the next vertical sync pulse.
*
* @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
* timebase.
* @param builtInDisplayId The surface flinger built-in display id such as
* {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
* @param frame The frame number. Increases by one for each vertical sync interval.
*/
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
}
- VSYNC 信息一般由硬件中断产生,SurfaceFlinger 处理。
- scheduleVsync 方法用于请求 VSNYC 信号。
- Native 方法接收到 VSYNC 信息处理后会调用 java 层 dispatchVsync 方法,最终调用到 FrameDisplayEventReceiver 的 onVsync 方法,详细的请继续查看。
CallbackQueue
CallbackQueue 是个单链表实现,每种类型的 callback(CallbackRecord) 按照设置的执行时间(dueTime)顺序排序分别保存在其各自 CallbackQueue。在 Choreographer 中有四种类型 callback:
- CALLBACK_INPUT:Input callback。
- CALLBACK_ANIMATION:Animation callback。 在 traversals 之前。
- CALLBACK_TRAVERSAL:Traversal callback。 处理 layout 和 draw。
- CALLBACK_COMMIT:Commit callback。 在 traversals 完成后执行。
private final class CallbackQueue {
private CallbackRecord mHead;
public boolean hasDueCallbacksLocked(long now) {
return mHead != null && mHead.dueTime <= now;
}
//根据当前时间得到 callback
public CallbackRecord extractDueCallbacksLocked(long now) {
//此处省略具体实现
}
//根据时间添加 callback
public void addCallbackLocked(long dueTime, Object action, Object token) {
//此处省略具体实现
}
//移除 callback
public void removeCallbacksLocked(Object action, Object token) {
//此处省略具体实现
}
}
流程分析
大致分析完 Choreographer 关键的几个成员变量后,我们再回到 postFrameCallback 方法。
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//添加 CALLBACK_ANIMATION 类型的 callback 到回调队列
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
//设定的执行时间在当前时间之后,发送 MSG_DO_SCHEDULE_CALLBACK,由 FrameHanlder 执行 doScheduleCallback
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
doScheduleCallback
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
scheduleFrameLocked
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
//翻译:若当前线程是UI线程,执行 scheduleVsyncLocked 请求 VSYNC 信号
scheduleVsyncLocked();
} else {
//翻译:非UI线程,发送 MSG_DO_SCHEDULE_VSYNC 消息到主线程
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
scheduleVsyncLocked
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
调用 DisplayEventReceiver#scheduleVsync,收到 Vsync 信息后,调用 FrameDisplayEventReceiver#onVsync。
FrameDisplayEventReceiver#onVsync
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
//此处省略部分无关代码
mTimestampNanos = timestampNanos;
mFrame = frame;
//该消息的 callback 为当前对象 FrameDisplayEventReceiver,收到消息调用其 run 方法,然后调用 doFrame 方法
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
doFrame
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
//此处省略部分无关代码
// VSYNC 信号到来时间
long intendedFrameTimeNanos = frameTimeNanos;
//实际开始执行当前 frame 的时间
startNanos = System.nanoTime();
//时间差
final long jitterNanos = startNanos - frameTimeNanos;
//时间差大于帧率(也就是刷新率,大部分手机是 60),则认为是跳帧
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
scheduleVsyncLocked();
return;
}
//记录当前frame信息
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
//记录上一次frame渲染的时间点
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//执行CallBack,优先级为:CALLBACK_INPUT > CALLBACK_ANIMATION > CALLBACK_TRAVERSAL > CALLBACK_COMMIT
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//此处省略部分无关代码
}
doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 根据 callback 的类型,查找 CallbackRecord 对象
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
if (callbackType == Choreographer.CALLBACK_COMMIT) {
//此处省略部分无关代码
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
//调用 run 方法
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
//回收 callbacks
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
CallbackRecord#run
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
//调用 FrameCallback 的 doFrame 方法
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
小结
关于 Choreographer 的整个调用流程及其原理已经分析完成。至于系统某些调用,如 View 的 invalidate,触发 ViewRootImpl#scheduleTraversals,最终调用 Choreographer#postCallback(Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable, null);从方法中可以看出,这只是明确了 Callback 的类型以及回调处理 Runnable 而已,基本流程几乎一样。
既然自定义 FrameCallback 可以在下一个 frame 被渲染的时候会被回调,那我们可以根据这个原理实现应用的帧率监听。
代码实现
class FpsUtilKt private constructor() {
/**
* 当前的帧率
*/
private var mLastFrameRate = 60
private val mMainHandler = Handler(Looper.getMainLooper())
private val mRateRunnable = FrameRateRunnable()
private object Holder {
val INSTANCE = FpsUtilKt()
}
val fps: Unit
get() {
//开启定时任务
mMainHandler.postDelayed(mRateRunnable, FPS_SAMPLING_TIME.toLong())
Choreographer.getInstance().postFrameCallback(mRateRunnable)
}
/**
* 读取fps的线程
*/
private inner class FrameRateRunnable : Runnable, Choreographer.FrameCallback {
private var totalFramesPerSecond = 0
override fun run() {
mLastFrameRate = totalFramesPerSecond
println("FPS:$mLastFrameRate")
totalFramesPerSecond = 0
mMainHandler.postDelayed(this, FPS_SAMPLING_TIME.toLong())
}
override fun doFrame(frameTimeNanos: Long) {
totalFramesPerSecond++
Choreographer.getInstance().postFrameCallback(this)
}
}
companion object {
/**
* fps 采集时间
*/
private const val FPS_SAMPLING_TIME = 1000
val instance: FpsUtilKt
get() = Holder.INSTANCE
}
}
上文若存在问题,欢迎指出!