为了更好的理解使用
Choreographer
监控App FPS
的原理,本文先来梳理一下Choreographer
的工作原理。
Choreographer
主要是用来协调动画、输入和绘制事件运行的。它通过接收Vsync
信号来调度应用下一帧渲染时的动作。
对 Vsync 信号的监听
Choreographer
会通过Choreographer.FrameDisplayEventReceiver
来监听底层HWC
触发的Vsync
信号:
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
}
Vsync
信号可以理解为底层硬件的一个系统中断,它每16ms会产生一次。上面onVsync()
的每个参数的意义为:
timestampNanos : Vsync信号到来的时间, 这个时间使用的是底层
JVM nanoscends -> System.nanoTime
builtInDisplayId : 此时
SurfaceFlinger
内置的display id
frame : 帧号,随着
onVsync
的回调数增加
onVsync的回调逻辑
那onVsync
什么时候会调用呢?
Choreographer.scheduleVsyncLocked()
会请求下一次Vsync
信号到来时回调FrameDisplayEventReceiver.onVsync()
方法:
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
Vsync信号到来时执行callback
设置callback
Choregrapher
提供了下面方法设置callback
:
public void postCallback(int callbackType, Runnable action, Object token)
public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis)
public void postFrameCallback(FrameCallback callback)
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis)
在Choregrapher
中存在多个Callback Queue
, 常见的Callback Queue
的类型有:
Choreographer.CALLBACK_INPUT 输入事件,比如键盘
Choreographer.CALLBACK_ANIMATION 动画
Choreographer.CALLBACK_TRAVERSAL 比如`ViewRootImpl.scheduleTraversals, layout or draw`
Choreographer.CALLBACK_COMMIT
上面4个事件会在一次Vsync
信号到来时依次执行。
callback的执行逻辑
以ViewRootImpl.scheduleTraversals
为例:
void scheduleTraversals() {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
即给Choregrapher
提交了一个Choreographer.CALLBACK_TRAVERSAL
类型的callback
去执行。
postCallback()
里面的具体执行逻辑就不分析了,这里直接说一下关键逻辑:
- 切换到
Choregrapher
创建时所在的线程去调用scheduleFrameLocked()
方法,设置mFrameScheduled = true
- 调用
scheduleVsyncLocked
请求下一次Vsync
信号回调 FrameDisplayEventReceiver.onVsync()
会生成一个消息,然后发送到Choreographer.mHander
的消息队列Choreographer.mHander
取出上面onVsync
中发送的消息,执行Choreographer.doFrame()
方法,doFrame()
中判断mFrameScheduled
是否为true
,如果为true
的话就上面四种callback
综上所述Choreographer
的工作原理如下图:
doFrame() 的时间参数
我们来看一下这个方法(主要关注一下时间参数):
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
if (!mFrameScheduled) {
return; // no work to do
}
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) { //16ms
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
frameTimeNanos = startNanos - lastFrameOffset;
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
解释一下上面一些时间相关参数的含义:
intendedFrameTimeNanos: 预计这一帧开始渲染的时间
frameTimeNanos: 这一帧真正开始渲染的时间。在
startNanos - frameTimeNanos < mFrameIntervalNanos
,其实就等于intendedFrameTimeNanos
jitterNanos: 真正渲染时间点和预计渲染时间点之差
mFrameIntervalNanos: 每一帧期望渲染的时间, 固定为16ms
skippedFrames : jitterNanos总共跳过了多少帧。
mLastFrameTimeNanos : 上一次渲染一帧的时间点
那 jitterNanos > mFrameIntervalNanos
在什么时候会成立呢?
其实就是我们常说的丢帧: 比如我们连续提交了两个Choreographer.CALLBACK_TRAVERSAL callback
。如果一个callback
的执行时间大于16ms,那么就会造成:
startNanos - frameTimeNanos > mFrameIntervalNanos(16ms)
doCallback(int callbackType, long frameTimeNanos)
这个方法的逻辑并不复杂 : 获取callbackType
对应的Callback Queue
, 取出这个队列中已经过期的calllback
进行执行。
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
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));
}
c.run(frameTimeNanos);
}
}
Choreographer与主线程消息循环的关系
上面我们已经知道onVsync
把要执行doFrame
的消息放入了Choreographer.mHander
的消息队列。
这里Choreographer.mHander的消息队列其实就是主线程的消息,所以doFrame方法其实是由主线程的消息循环来调度的。
我们看一下Choreographer
实例化时的Looper
:
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
...
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
即取的是当前线程的Looper
,所以donFrame()
是在主线程的消息循环中调度的。
参考文章:
Android Choreographer 源码
Choreographer原理
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/20154,转载请注明出处。
评论0