当前位置:网站首页>Choreographer full resolution

Choreographer full resolution

2022-04-23 13:56:00 InfoQ




null
UI change



It was said in the last issue app Not every one vsync All the signals can be received , Only when the application needs to draw , Will pass scheduledVsync? Method application VSYNC The signal .

Let's start with the need to draw , When we changed UI after , It will be carried out invalidate Method to draw , Here's an example setText Method , Let's review the changes UI The flow of time :

null
You can see , Finally, the parent layout is called ViewRootImpl Of scheduleTraversals Method .

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

//...

}

}

For easy viewing , I just left the relevant code . You can see , stay ViewRootImpl In the construction method , Instantiate Choreographer object , And find out UI It's called when it changes scheduleTraversals In the method , Called postSyncBarrier Method inserts a synchronization barrier , And then it calls postCallback Method , And there's a mTraversalRunnable( It's useful in the back , Pay attention first ), I don't know what this method is for now . Keep looking. .

Choreographer Instantiation



//Choreographer.java

public static Choreographer getInstance() {

return sThreadInstance.get();

}

private static final 
《 A big factory Java Analysis of interview questions + Back end development learning notes + The latest architecture explanation video + Practical project source code handout 》 Free open source Prestige search official account 【 Advanced programming 】
 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);

// initialization FrameDisplayEventReceiver

mDisplayEventReceiver = USE_VSYNC

? new FrameDisplayEventReceiver(looper, vsyncSource)

: null;

mLastFrameTimeNanos = Long.MIN_VALUE;

// One frame interval

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

mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];

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

mCallbackQueues[i] = new CallbackQueue();

}

}

ThreadLocal, Is it a little familiar with ? I said before Handler When I said ,Handler How to get the current thread Looper Of ? Through this ThreadLocal, Again , It's also used here ThreadLocal To ensure that each thread corresponds to a Choreographer.

The storage method is the same , With ThreadLocal by key,Choreographer by value Store in ThreadLocalMap in , Unfamiliar friends can turn to 《Handler Three other difficult questions 》 have a look .

So the mHandler Namely ViewRootImpl Of the thread in which it is located handler. Then look at postCallback What did you do .

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

}

}

}

}

stay ViewRootImpl Called in postCallback After method , You can see through addCallbackLocked Method , Added a CallbackRecord data , among action It's before correspondence ViewRootImpl Of mTraversalRunnable.

Then judge whether the set time is after the current time , That is, there is no delay , If there is a delay, send a delay message MSG_DO_SCHEDULE_CALLBACK To Handler Thread , And finally to scheduleFrameLocked Method . If there is no delay , Directly scheduleFrameLocked.

scheduleFrameLocked( Preparation of application VSYNC The signal )



private void scheduleFrameLocked(long now) {

if (!mFrameScheduled) {

mFrameScheduled = true;

if (USE_VSYNC) {

// Whether to run in the main thread

if (isRunningOnLooperThreadLocked()) {

scheduleVsyncLocked();

} else {

// adopt Handler Switching thread

Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);

msg.setAsynchronous(true);

mHandler.sendMessageAtFrontOfQueue(msg);

}

} else {

// Calculate the time of the next frame

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

}

}

}

In this method , First of all, we judge whether it is on or not VSYNC( Last section said Android4.1 Then it turns on by default VSYNC), If it's on , Judge whether it is in the main thread , If it's the main thread, it runs scheduleVsyncLocked, If not, switch threads , It will also call scheduleVsyncLocked Method , And this method is the application we mentioned before VSYNC The way to signal .

If it's not on VSYNC, Call directly doFrame Method .

In addition, you can see , We just used Handler When sending a message , They all called. msg.setAsynchronous(true) Method , This method is to set the message to asynchronous . Because we set up synchronization barriers in the beginning , So asynchronous messages are executed first , The asynchronous setting here is to make the message execute at the first time and not be affected by others Handler The impact of the news .

Summary 1



Through the above series of methods , We can get a preliminary logical process :

  • ViewRootImpl When initializing , Will be instantiated Choreographer object , That is to get the current thread ( It's usually the main thread ) Corresponding Choreographer object .
  • Choreographer When initializing , Will create a new one corresponding to the current thread Handler object , initialization FrameDisplayEventReceiver, Calculate the time of a frame and a series of initialization work .
  • When UI When things change , Will be called to ViewRootImpl Of scheduleTraversals Method , This method adds synchronization barrier messages , And called Choreographer Of postCallback How to apply VSYNC The signal .

In the process ,Handler A delayed message was sent , Switched threads , And asynchronous is set for all messages , Make sure you do it first .

Continue to look at scheduleVsyncLocked Method .

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

}

}

The code is simple , It is through FrameDisplayEventReceiver, request native The vertical synchronization signal of the plane VSYNC.

This FrameDisplayEventReceiver Is in Choreographer Instantiated in constructor , Inherited from DisplayEventReceiver, The main thing is to deal with VSYNC And receiving signals .

I just talked about calling nativeScheduleVsync Method application VSYNC The signal , And then when you get it VSYNC When the signal comes back onVsync The method .

onVsync( receive VSYNC The signal )



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

}

}

It's also through Handler Sent a message , Executed its own Runnable The callback method , That is to say doFrame().

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204231353209782.html