美文网首页
Android-ViewRootImpl

Android-ViewRootImpl

作者: 有腹肌的豌豆Z | 来源:发表于2020-09-29 08:33 被阅读0次

    简介

    源码学习

    接着前面的 setContentView

    ViewRootImpl#setView()
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    mView = view;  // 这个是decorview 
                    ....
                    // Schedule the first layout -before- adding to the window
                    // manager, to make sure we do the relayout before receiving
                    // any other events from the system.
                    requestLayout(); // 发起布局请求 
                    ....
                    view.assignParent(this);
                    ....
                }
            }
        }
    }
    
    View#assignParent() 指定父View,这个调用的是View的方法
        /**
         * The parent this view is attached to.
         * {@hide}
         *
         * @see #getParent()
         */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
        protected ViewParent mParent;
    
        @UnsupportedAppUsage
        void assignParent(ViewParent parent) {
            if (mParent == null) {
                mParent = parent;
            } else if (parent == null) {
                mParent = null;
            } else {
                throw new RuntimeException("view " + this + " being added, but"
                        + " it already has a parent");
            }
        }
    

    参数是 ViewParent,而 ViewRootImpl 是实现了 ViewParent 接口的,所以在这里就将 DecorView 和 ViewRootImpl 绑定起来了。每个Activity 的根布局都是 DecorView,而 DecorView 的 parent 又是 ViewRootImpl,所以在子 View 里执行 invalidate() 之类的操作,循环找 parent 时,最后都会走到 ViewRootImpl 里来。

    跟界面刷新相关的方法里应该都会有一个循环找 parent 的方法,或者是不断调用 parent 的方法,这样最终才都会走到 ViewRootImpl 里,也就是说实际上 View 的刷新都是由 ViewRootImpl 来控制的。

    即使是界面上一个小小的 View 发起了重绘请求时,都要层层走到 ViewRootImpl,由它来发起重绘请求,然后再由它来开始遍历 View 树,一直遍历到这个需要重绘的 View 再调用它的 onDraw() 方法进行绘制。

    ViewRootImpl#requestLayout()
        @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    

    当 View 发起重绘操作 invalidate() 时,最后也调用了 scheduleTraversals() 这个方法么。其实这个方法就是屏幕刷新的关键,它是安排一次绘制 View 树的任务等待执行,具体后面再说。

    也就是说,其实打开一个 Activity,当它的 onCreate---onResume 生命周期都走完后,才将它的 DecoView 与新建的一个 ViewRootImpl 对象绑定起来,同时开始安排一次遍历 View 任务也就是绘制 View 树的操作等待执行,然后将 DecoView 的 parent 设置成 ViewRootImpl 对象。

    这也就是为什么在 onCreate---onResume 里获取不到 View 宽高的原因,因为在这个时刻 ViewRootImpl 甚至都还没创建,更不用说是否已经执行过测量操作了。

    还可以得到一点信息是,一个 Activity 界面的绘制,其实是在 onResume() 之后才开始的。

    ViewRootImpl#scheduleTraversals()

    schedule [ˈskedʒuːl] vt.编制目录; 安排,计划 n. 时间表;计划表;一览表
    traversal [trəˈvərs(ə)l] n. [计] 遍历;横越;横断物

        @UnsupportedAppUsage
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                // 过滤同一帧多次发起的请求
                mTraversalScheduled = true;  
                
                //添加同步消息屏障,这个方法也比较关键,这里先不关心,我们说完Choreographer再分析         
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                
                //向Choreographer中发送消息
                mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
    Choreographer#postCallback() 向Choreographer中发送消息
        public void postCallback(int callbackType, Runnable action, Object token) {
            postCallbackDelayed(callbackType, action, token, 0);
        }
    
        public void postCallbackDelayed(int callbackType,Runnable action, Object token, long delayMillis) {
            if (action == null) {
                throw new IllegalArgumentException("action must not be null");
            }
            if (callbackType < 0 || callbackType > CALLBACK_LAST) {
                throw new IllegalArgumentException("callbackType is invalid");
            }
    
            postCallbackDelayedInternal(callbackType, action, token, delayMillis);
        }
    
        // 这个才是重点 
        private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {
                synchronized (mLock) {
                
                final long now = SystemClock.uptimeMillis();
                final long dueTime = now + delayMillis;
                
                //将消息以当前的时间戳放进mCallbackQueue 队列里
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    
                if (dueTime <= now) {
                    //如果没有设置消息延时,直接执行
                    scheduleFrameLocked(now);
                } else {
                    //消息延时,但是最终依然会调用scheduleFrameLocked
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                    msg.arg1 = callbackType;
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, dueTime);
                }
            }
        }
    

    将runable存到CallbackQueue 这个数组里面 👇会详解

    @UnsupportedAppUsage
        private final CallbackQueue[] mCallbackQueues;
    
    Choreographer#scheduleFrameLocked()
    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (isRunningOnLooperThreadLocked()) {
                    //如果当前线程是Choreographer的工作线程,我理解就是主线程
                    scheduleVsyncLocked();
                } else {
                    //否则发一条消息到主线程
                    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);
                    if (DEBUG_FRAMES) {
                        Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                    }
                    Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, nextFrameTime);
                }
        }
    }
    
    接下来最终会调用到一个native方法
        @UnsupportedAppUsage
        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);
            }
        }
    
        @FastNative
        private static native void nativeScheduleVsync(long receiverPtr);
    
    

    到这里跟不下去了 。。。

    上面创建了一个Runable存入了 CallbackQueue ,那这个CallbackQueue 是什么呢?前面Choreographer#postCallbackDelayedInternal()方法中,我们看到了将消息以当前的时间戳放进队列里,那消息什么时候被取出来执行呢?有哪些操作。

    CallbackQueue
    private final class CallbackQueue {
        private CallbackRecord mHead;
    
        public boolean hasDueCallbacksLocked(long now) {
            return mHead != null && mHead.dueTime <= now;
        }
    
        //这就是取出消息的方法
        public CallbackRecord extractDueCallbacksLocked(long now) {
            CallbackRecord callbacks = mHead;
            if (callbacks == null || callbacks.dueTime > now) {
                return null;
            }
    
            CallbackRecord last = callbacks;
            CallbackRecord next = last.next;
            while (next != null) {
                if (next.dueTime > now) {
                    last.next = null;
                    break;
                }
                last = next;
                next = next.next;
            }
            mHead = next;
            return callbacks;
        }
    
        //添加消息
        public void addCallbackLocked(long dueTime, Object action, Object token) {...}
    
        //删除消息
        public void removeCallbacksLocked(Object action, Object token) {...}
    }
    

    跟踪代码发现,这个CallbackQueue#extractDueCallbacksLocked()会被Choreographer#doCallbacks()调用,Choreographer#doCallbacks()又会被Choreographer#doFrame()调用,最终我们跟到了FrameDisplayEventReceiver类。

         void doCallbacks(int callbackType, long frameTimeNanos) {
            
            CallbackRecord callbacks;
            synchronized (mLock) {
                final long now = System.nanoTime();
                callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                        now / TimeUtils.NANOS_PER_MS);
                ....
        
            }
            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 void recycleCallbackLocked(CallbackRecord callback) {
            callback.action = null;
            callback.token = null;
            callback.next = mCallbackPool;
            mCallbackPool = callback;
        }
    
    

    继续向上跟踪

       void doFrame(long frameTimeNanos, int frame) {
           
           ....
           try {
               Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
               AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
    
               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);
           }
    
          ....
       }
    

    继续向上寻找到 FrameDisplayEventReceiver

    nativeScheduleVsync()会向SurfaceFlinger注册Vsync信号的监听,VSync信号由SurfaceFlinger实现并定时发送,当Vsync信号来的时候就会回调FrameDisplayEventReceiver#onVsync(),这个方法给发送一个带时间戳Runnable消息,这个Runnable消息的run()实现就是FrameDisplayEventReceiver# run(), 接着就会执行doFrame()。

    Choreographer#FrameDisplayEventReceiver
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
    
        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
            super(looper, vsyncSource);
        }
    
        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                scheduleVsync();
                return;
            }
            long now = System.nanoTime();
            if (timestampNanos > now) {
                timestampNanos = now;
            }
    
            if (mHavePendingVsync) {
            } else {
                mHavePendingVsync = true;
            }
    
            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);
        }
    }
    

    doFrame()会计算当前时间与时间戳的间隔,间隔越大表示这一帧处理的时间越久,如果间隔超过一个周期,就会去计算跳过了多少帧,并打印出一个日志,这个日志我想很多人可能都见过:
    Log.i(TAG, "Skipped " + skippedFrames + " frames! "
    + "The application may be doing too much work on its main thread.");

    最终doFrame()会从mCallbackQueue 中取出消息并按照时间戳顺序调用mTraversalRunnable的run()函数,mTraversalRunnable就是最初被加入到Choreographer中的Runnable()

    //ViewRootImp 
    mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    
    TraversalRunnable
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
    ViewRootImp#doTraversal()
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步消息屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
    
            performTraversals();
    
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
    

    到这里 请求刷新 到收到系统的Vsync 信号回调 后面就是View的绘制流程了下一章在学习


    同步消息屏障

    void scheduleTraversals() {
        if (!mTraversalScheduled) {         
            mTraversalScheduled = true;
            //☆ 同步消息屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //向Choreographer中发送消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            //...
        }
    }
    

    我们知道,Android是基于消息机制的,每一个操作都是一个Message,如果在触发绘制的时候,消息队列中还有很多消息没有被执行,那是不是意味着要等到消息队列中的消息执行完成后,绘制消息才能被执行到,那么依然无法保证Vsync信号和绘制的同步,所以依然可能出现丢帧的现象.

    还记不记得我们之前在Choreographer#scheduleFrameLocked()和FrameDisplayEventReceiver#onVsync()中提到,我们会给与Message有关的绘制请求设置成异步消息(msg.setAsynchronous(true)),为什么要这么做呢?

    这时候MessageQueue#postSyncBarrier()就发挥它的作用了,简单来说,它的作用就是一个同步消息屏障,能够把我们的异步消息(也就是绘制消息)的优先级提到最高。

    MessageQueue#postSyncBarrier()
    @TestApi
        public int postSyncBarrier() {
            return postSyncBarrier(SystemClock.uptimeMillis());
        }
    
        private int postSyncBarrier(long when) {
            // Enqueue a new sync barrier token.
            // We don't need to wake the queue because the purpose of a barrier is to stall it.
            synchronized (this) {
                final int token = mNextBarrierToken++;
                final Message msg = Message.obtain();
                msg.markInUse();
                msg.when = when;
                msg.arg1 = token;
    
                Message prev = null;
                Message p = mMessages;
                if (when != 0) {
                    while (p != null && p.when <= when) {
                        prev = p;
                        p = p.next;
                    }
                }
                if (prev != null) { // invariant: p == prev.next
                    msg.next = p;
                    prev.next = msg;
                } else {
                    msg.next = p;
                    mMessages = msg;
                }
                return token;
            }
        }
    

    主线程的 Looper 会一直循环调用 MessageQueue 的 next() 来取出队头的 Message 执行,当 Message 执行完后再去取下一个。

    当 next() 方法在取 Message 时发现队头是一个同步屏障的消息时,就会去遍历整个队列,只寻找设置了异步标志的消息,如果有找到异步消息,那么就取出这个异步消息来执行,否则就让 next() 方法陷入阻塞状态。

    如果 next() 方法陷入阻塞状态,那么主线程此时就是处于空闲状态的,也就是没在干任何事。

    所以,如果队头是一个同步屏障的消息的话,那么在它后面的所有同步消息就都被拦截住了,直到这个同步屏障消息被移除,否则主线程就一直不会去处理同步屏障后面的同步消息

    那这么同步屏障是什么时候被移除的呢?
    其实我们就是在我们上面提到的ViewRootImp#doTraversal()方法中。

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

    总结

    • 界面上任何一个 View 的刷新请求最终都会走到 ViewRootImpl 中的 scheduleTraversals() 里来安排一次遍历绘制 View 树的任务;

    • scheduleTraversals() 会先过滤掉同一帧内的重复调用,在同一帧内只需要安排一次遍历绘制 View 树的任务即可,这个任务会在下一个屏幕刷新信号到来时调用 performTraversals() 遍历 View 树,遍历过程中会将所有需要刷新的 View 进行重绘;

    • 接着 scheduleTraversals() 会往主线程的消息队列中发送一个同步屏障,拦截这个时刻之后所有的同步消息的执行,但不会拦截异步消息,以此来尽可能的保证当接收到屏幕刷新信号时可以尽可能第一时间处理遍历绘制 View 树的工作;

    • 发完同步屏障后 scheduleTraversals() 才会开始安排一个遍历绘制 View 树的操作,作法是把 performTraversals() 封装到 Runnable 里面,然后调用 Choreographer 的 postCallback() 方法;

    • postCallback() 方法会先将这个 Runnable 任务以当前时间戳放进一个待执行的队列里,然后如果当前是在主线程就会直接调用一个native 层方法,如果不是在主线程,会发一个最高优先级的 message 到主线程,让主线程第一时间调用这个 native 层的方法;

    • native 层的这个方法是用来向底层注册监听下一个屏幕刷新信号,当下一个屏幕刷新信号发出时,底层就会回调 Choreographer 的onVsync() 方法来通知上层 app;

    • onVsync() 方法被回调时,会往主线程的消息队列中发送一个执行 doFrame() 方法的消息,这个消息是异步消息,所以不会被同步屏障拦截住;

    • doFrame() 方法会去取出之前放进待执行队列里的任务来执行,取出来的这个任务实际上是 ViewRootImpl 的 doTraversal() 操作;

    • 上述第4步到第8步涉及到的消息都手动设置成了异步消息,所以不会受到同步屏障的拦截;

    • doTraversal() 方法会先移除主线程的同步屏障,然后调用 performTraversals() 开始根据当前状态判断是否需要执行performMeasure() 测量、perfromLayout() 布局、performDraw() 绘制流程,在这几个流程中都会去遍历 View 树来刷新需要更新的View;

    相关文章

      网友评论

          本文标题:Android-ViewRootImpl

          本文链接:https://www.haomeiwen.com/subject/mezruktx.html