当前位置:网站首页>Choreographer全解析

Choreographer全解析

2022-04-23 13:53:00 InfoQ




null
UI变化



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

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

null
可以看到,最后会调用到父布局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, &quot;PostCallback: type=&quot; + callbackType

  • &quot;, action=&quot; + action + &quot;, token=&quot; + token
  • &quot;, delayMillis=&quot; + 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, &quot;Attempted to schedule a vertical sync pulse but the display event &quot;

  • &quot;receiver has already been disposed.&quot;);

} 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()。

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/c286c0494c1a6ab651876464b