写点什么

Choreographer 全解析

  • 2022 年 4 月 23 日
  • 本文字数:3563 字

    阅读完需:约 12 分钟




UI 变化




上期说到 app 并不是每一个 vsync 信号都能接收到的,只有当应用有绘制需求的时候,才会通过 scheduledVsync?方法申请 VSYNC 信号。


那我们就从有绘制需求开始看,当我们修改了 UI 后,都会执行 invalidate 方法进行绘制,这里我们举例 setText 方法,再回顾下修改 UI 时候的流程:



可以看到,最后会调用到父布局 ViewRootImpl 的 scheduleTraversals 方法。


public ViewRootImpl(Context context, Display display) {


//...


mChoreographer = Choreographer.getInstance();


}


void scheduleTraversals() {


if (!mTraversalScheduled) {


mTraversalScheduled = true;


mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();


mChoreographer.postCallback(


Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);


//...


}


}


为了方便查看,我只留了相关代码。可以看到,在 ViewRootImpl 构造方法中,实例化了 Choreographer 对象,并且在发现 UI 变化的时候调用的 scheduleTraversals 方法中,调用了 postSyncBarrier 方法插入了同步屏障,然后调用了 postCallback 方法,并且传入了一个 mTraversalRunnable(后面有用处,先留意一下),暂时还不知道这个方法是干嘛的。继续看看。


Choreographer 实例化




//Choreographer.java


public static Choreographer getInstance() {


return sThreadInstance.get();


}


private static final 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 ThreadLocal<Choreographer> sThreadInstance =


new ThreadLocal<Choreographer>() {


@Override


protected Choreographer initialValue() {


Looper looper = Looper.myLooper();


//...


Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);


//...


return choreographer;


}


};


private Choreographer(Looper looper, int vsyncSource) {


mLooper = looper;


mHandler = new FrameHandler(looper);


//初始化 FrameDisplayEventReceiver


mDisplayEventReceiver = USE_VSYNC


? new FrameDisplayEventReceiver(looper, vsyncSource)


: null;


mLastFrameTimeNanos = Long.MIN_VALUE;


//一帧间隔时间


mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());


mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];


for (int i = 0; i <= CALLBACK_LAST; i++) {


mCallbackQueues[i] = new CallbackQueue();


}


}


ThreadLocal,是不是有点熟悉?之前说 Handler 的时候说过,Handler 是怎么获取当前线程的 Looper 的?就是通过这个 ThreadLocal,同样,这里也是用到 ThreadLocal 来保证每个线程对应一个 Choreographer。


存储方法还是一样,以 ThreadLocal 为 key,Choreographer 为 value 存储到 ThreadLocalMap 中,不熟悉的朋友可以再翻到《Handler 另类难点三问》看看。


所以这里创建的 mHandler 就是 ViewRootImpl 所处的线程的 handler。接着看 postCallback 做了什么。


postCallback




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;


mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);


if (dueTime <= now) {


scheduleFrameLocked(now);


} else {


Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);


msg.arg1 = callbackType;


msg.setAsynchronous(true);


mHandler.sendMessageAtTime(msg, dueTime);


}


}


}


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:


doFrame(System.nanoTime(), 0);


break;


case MSG_DO_SCHEDULE_VSYNC:


doScheduleVsync();


break;


case MSG_DO_SCHEDULE_CALLBACK:


doScheduleCallback(msg.arg1);


break;


}


}


}


void doScheduleCallback(int callbackType) {


synchronized (mLock) {


if (!mFrameScheduled) {


final long now = SystemClock.uptimeMillis();


if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {


scheduleFrameLocked(now);


}


}


}


}


在 ViewRootImpl 中调用了 postCallback 方法之后,可以看到通过 addCallbackLocked 方法,添加了一条 CallbackRecord 数据,其中 action 就是对应之前 ViewRootImpl 的 mTraversalRunnable。


然后判断设定的时间是否在当前时间之后,也就是有没有延迟,如果有延迟就发送延迟消息消息 MSG_DO_SCHEDULE_CALLBACK 到 Handler 所在线程,并最终执行到 scheduleFrameLocked 方法。如果没有延迟,则直接执行 scheduleFrameLocked。


scheduleFrameLocked(准备申请 VSYNC 信号)




private void scheduleFrameLocked(long now) {


if (!mFrameScheduled) {


mFrameScheduled = true;


if (USE_VSYNC) {


//是否运行在主线程


if (isRunningOnLooperThreadLocked()) {


scheduleVsyncLocked();


} else {


//通过 Handler 切换线程


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);


Message msg = mHandler.obtainMessage(MSG_DO_FRAME);


msg.setAsynchronous(true);


mHandler.sendMessageAtTime(msg, nextFrameTime);


}


}


}


case MSG_DO_FRAME:


doFrame(System.nanoTime(), 0);


break;


case MSG_DO_SCHEDULE_VSYNC:


doScheduleVsync();


break;


void doScheduleVsync() {


synchronized (mLock) {


if (mFrameScheduled) {


scheduleVsyncLocked();


}


}


}


该方法中,首先判断了是否开启了 VSYNC(上节说过 Android4.1 之后默认开启 VSYNC),如果开启了,判断在不在主线程,如果是主线程就运行 scheduleVsyncLocked,如果不在就切换线程,也会调用到 scheduleVsyncLocked 方法,而这个方法就是我们之前说过的申请 VSYNC 信号的方法了。


如果没有开启 VSYNC,则直接调用 doFrame 方法。


另外可以看到,刚才我们用到 Handler 发送消息的时候,都调用了 msg.setAsynchronous(true)方法,这个方法就是设置消息为异步消息。因为我们刚才一开始的时候设置了同步屏障,所以异步消息就会先执行,这里的设置异步也就是为了让消息第一时间执行而不受其他 Handler 消息影响。


小结 1




通过上面一系列方法,我们能得到一个初步的逻辑过程了:


  • ViewRootImpl 初始化的时候,会实例化 Choreographer 对象,也就是获取当前线程(一般就是主线程)对应的 Choreographer 对象。

  • Choreographer 初始化的时候,会新建一个当前线程对应的 Handler 对象,初始化 FrameDisplayEventReceiver,计算一帧的时间等一系列初始化工作。

  • 当 UI 改变的时候,会调用到 ViewRootImpl 的 scheduleTraversals 方法,这个方法中加入了同步屏障消息,并且调用了 Choreographer 的 postCallback 方法去申请 VSYNC 信号。


在这个过程中,Handler 发送了延迟消息,切换了线程,并且给消息都设置了异步,保证最先执行。


继续看 scheduleVsyncLocked 方法。


scheduleVsyncLocked




private void scheduleVsyncLocked() {


mDisplayEventReceiver.scheduleVsync();


}


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);


}


}


代码很简单,就是通过 FrameDisplayEventReceiver,请求 native 层面的垂直同步信号 VSYNC。


这个 FrameDisplayEventReceiver 是在 Choreographer 构造方法中实例化的,继承自 DisplayEventReceiver,主要就是处理 VSYNC 信号的申请和接收。


刚才说到调用 nativeScheduleVsync 方法申请 VSYNC 信号,然后当收到 VSYNC 信号的时候就会回调 onVsync 方法了。


onVsync(接收 VSYNC 信号)




private final class FrameDisplayEventReceiver extends DisplayEventReceiver


implements Runnable {


@Override


public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {


//...


mTimestampNanos = timestampNanos;


mFrame = frame;


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);


}


}


这里同样通过 Handler 发送了一条消息,执行了本身的 Runnable 回调方法,也就是 doFrame()。

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
Choreographer全解析_Java_爱好编程进阶_InfoQ写作社区