Choreographer全解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 早呀各位。今天继续屏幕刷新机制的知识讲解,上文说到vsync的处理,每一帧UI的绘制前期处理都在Choreographer中实现,那么今天就来看看这个神奇的舞蹈编舞师是怎么将UI变化反应到屏幕上的。

前言


早呀各位。今天继续屏幕刷新机制的知识讲解,上文说到vsync的处理,每一帧UI的绘制前期处理都在Choreographer中实现,那么今天就来看看这个神奇的舞蹈编舞师是怎么将UI变化反应到屏幕上的。


(今天全是代码~)


代码未动,图先行


7.png


UI变化


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


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


8.png


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


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 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就是对应之前ViewRootImplmTraversalRunnable


然后判断设定的时间是否在当前时间之后,也就是有没有延迟,如果有延迟就发送延迟消息消息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改变的时候,会调用到ViewRootImplscheduleTraversals方法,这个方法中加入了同步屏障消息,并且调用了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()


doFrame(绘制帧数据)


void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            //...
            //当前帧的vsync信号来的时间,假如为12分200ms
            long intendedFrameTimeNanos = frameTimeNanos;
            //当前时间,也就是开始绘制的时间,假如为12分150ms
            startNanos = System.nanoTime();
            //计算时间差,如果大于一个帧时间,则是跳帧了。比如是50ms,大于16ms
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
                //计算掉了几帧,50/16=3帧
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                //计算一帧内时间差,50%16=2ms
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                //修正时间,vsync信号应该来得时间,为12分148ms,保证和绘制时间对应上
                frameTimeNanos = startNanos - lastFrameOffset;
            }
            if (frameTimeNanos < mLastFrameTimeNanos) {
                //信号时间已过,不能再绘制了,等待下一个vsync信号,保证后续时间同步上
                scheduleVsyncLocked();
                return;
            }
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }
        try {
            //执行相关的callback任务
            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }


这里主要的工作就是:


  • 设置当前帧的开始绘制时间,上节说过开始绘制要在vsync信号来的时候开始,保证两者时间对应。所以如果时间没对上,就是发送了跳帧,那么就要修正这个时间,保证后续的时间对应上。


  • 执行所有的Callback任务。


doCallbacks(执行任务)


void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;
            if (callbackType == Choreographer.CALLBACK_COMMIT) {
                final long jitterNanos = now - frameTimeNanos;
                Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
                if (jitterNanos >= 2 * mFrameIntervalNanos) {
                    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
                            + mFrameIntervalNanos;
                    frameTimeNanos = now - lastFrameOffset;
                    mLastFrameTimeNanos = frameTimeNanos;
                }
            }
        }
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    private static final class CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;
        @UnsupportedAppUsage
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
    }


其实就是按类型,从mCallbackQueues队列中取任务,并执行对应的CallbackRecord的run方法。


而这个run方法中,判断了token,并执行了action的对应方法。再回头看看我们当初ViewRootImpl传入的方法:


mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);


token为空,所以会执行到action也就是mTraversalRunnable的run方法。


所以兜兜转转,又回到了ViewRootImpl本身,通过Choreographer申请了VSYNC信号,然后接收了VSYNC信号,最终回到自己这里,开始view的绘制。


最后看看mTraversalRunnable的run方法。


mTraversalRunnable


final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }


这就很熟悉了吧,调用了performTraversals方法,也就是开始了测量,布局,绘制的步骤。同时,关闭了同步屏障。


总结


最后再看看总结图:


7.png


参考


https://juejin.cn/post/6863756420380196877https://blog.csdn.net/stven_king/article/details/78775166

目录
相关文章
|
6月前
|
Android开发
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
|
6月前
|
Android开发
Android高级开发面试题以及笞案整理,实战解析
Android高级开发面试题以及笞案整理,实战解析
|
6月前
|
存储 Android开发
【Android 从入门到出门】第八章:分页入门指南
【Android 从入门到出门】第八章:分页入门指南
48 3
|
人工智能 算法 搜索推荐
BIRCH算法全解析:从原理到实战
BIRCH算法全解析:从原理到实战
305 0
|
设计模式 JavaScript 前端开发
看文档不如看源码系列热身 - Redux 源码全解析
众所周知,前端轮子太多,大部分同学每次学习新轮子都是学完不用就忘。我最近看一些库,其实这些库的实现都很简单,但是文档往往又很多,甚至还有些文档说的不清不楚,偶尔用到了都要去查文档,细节一点的东西文档又往往无法体现,感觉还不如将看文档的时间用来看源码。这些库的源码往往很精简,看完了既能知道如何使用,还能知其所以然,不亏。所以有了这个系列。
|
移动开发 架构师 小程序
让人茅塞顿开!史上最全的Android面试题集锦,内容太过真实
让人茅塞顿开!史上最全的Android面试题集锦,内容太过真实
让人茅塞顿开!史上最全的Android面试题集锦,内容太过真实
|
Android开发
【Android应用开发】EasyDialog 源码解析(一)
【Android应用开发】EasyDialog 源码解析(一)
181 0
【Android应用开发】EasyDialog 源码解析(一)
|
缓存 安全 前端开发
Android 高级面试题目整理
阿里云双十一拼团活动:https://www.aliyun.com/1111/2019/group-buying-share 1. ThreadLocal的理解 可以保证线程的安全。在多个线程共享相同的数据的时候,会为每个线程创建单独的副本,在单独的副本上进行数据的操作,不会对其它线程的数据产生影响,保证了线程安全。
|
缓存 Linux Android开发