View 繪製流程和刷新機制

1. 前言

當 Activity 啓動時候,會調用到 ActivityThread 的 handleResumeActivity 方法。在瞭解此篇文章的時候,先了解 Activity 、 Window 、 View 之間的關係,從這裏可以瞭解到 PhoneWindow、DecorView 和 ViewGroup/View 的關係。此篇文章將從 ActivityThread 中的 handleResumeActivity 開始解析。

2. 繪製流程

在 ActivityThread 的 handleResumeActivity 中:

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...


        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        if (r == null) {
            // We didn't actually resume the activity, so skipping any follow-up actions.
            return;
        }
        final Activity a = r.activity;


        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);    // 1
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }

        ...
    }

這裏的核心代碼 wm.addView(decor, l), vm 是 WindowManagerImpl,decor 是 DecorView。其中 WindowManagerImpl 的繼承關係如圖所示:

WindowManagerImpl 中 addView 方法:

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

調用 WindowManagerGlobal 的 addView 方法,這裏的 view 是前面傳遞進來的 decorView,addView 方法如下:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...


        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

此方法中創建了 ViewRootImpl 對象,並且調用 ViewRootImpl 的  setView 方法,並且將 decorView 傳遞進去,這樣 ViewRootImpl 就跟 decorView 聯繫起來了,ViewRootImpl 的繼承關係如下:

setView 方法:


 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                ...


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

            }
        }
    }

此方法中的核心是調用了 requestLayout 方法:

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

接着調用了 scheduleTraversals 方法:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

通過調用 Choreographer 啓動一個TraversalRunnable:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

接着調用 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 方法:

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
    
    ...

    if (!mStopped || mReportNextDraw) {
    
        ...
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ...
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
    }

    ...

    if (didLayout) {
        performLayout(lp, mWidth, mHeight);
        ...
    }

    ...
    performDraw();
    ...

}

核心就是三個方法:performMeasure、performLayout、performDraw。

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

這裏的 mView 是前面傳遞過來的 DecorView,DecorView 是 FrameLayout ,本質是一個 ViewGroup。所以一開始執行的是 ViewGroup 的 measure 方法,並且按照以下的流程逐一對子 View 進行測量:

這裏以 DecorView 爲例子,在 DecorView 和 ViewGroup 中是找不到 measure 方法,measure 方法的實現在父類 View 中:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...


        if (forceLayout || needsLayout) {
            ...
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                ...
            }
            ...

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
        ...
    }

這裏的核心方法是調用了自己本身的 onMeasure 方法,對於 DecorView 來講,onMeasure 方法的實現在 FrameLayout 中:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();

        ...

        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                
                ...

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

如果子 View 的個數大於0,則又調用了子 View 的 measure 方法,整體邏輯符合上面的流程圖。

3. 屏幕刷新機制

Android 屏幕每16秒會刷新一次,也就是每秒會刷新60次,人眼能感覺到卡頓的幀率是每秒24幀。要求我們的應用都能在 16ms 內繪製完成。如果有一次的界面繪製用了 22ms,那麼,用戶在 32ms 內看見的都是同一個界面。就會讓用戶覺得卡頓。而 Android 是如何進行刷新的呢?

在上面的流程中,方法 scheduleTraversals:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//1
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//2
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在上面的分析中,並沒有具體分析這裏面執行的方法,接下來將具體分析下。

註釋1:設置了同步屏障,這裏的作用是爲了更快的響應 Ui 刷新事件。設置了同步屏障之後,同步屏障爲 Handler 消息機制增加了一種簡單的優先級機制,異步消息的優先級要高於同步消息。關於同步屏障,可以參考Handler之同步屏障機制(sync barrier)

在註釋 2 中調用了 Choreographer 的 postCallback 方法:

    @UnsupportedAppUsage
    @TestApi
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

 postCallbackDelayed 方法,這裏的參數 delayMillis 傳遞 0 進來:

    @UnsupportedAppUsage
    @TestApi
    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);
    }

postCallbackDelayedInternal 方法: 

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

由於傳遞進來的 delayMillis 爲0,則執行 scheduleFrameLocked 方法:

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {    // 3
                    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);
            }
        }
    }

 註釋3:判斷當前線程是 UI線程,執行 scheduleVsyncLocked 方法:

    @UnsupportedAppUsage
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

調用 DisplayEventReceiver 的 scheduleVsync 方法 ,DisplayEventReceiver 是一個抽象類,FrameDisplayEventReceiver 是它的實現類:

    /**
     * Schedules a single vertical sync pulse to be delivered when the next
     * display frame begins.
     */
    @UnsupportedAppUsage
    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);
        }
    }

 這裏調用了一個 native層的方法,此方法的作用:通知底層,下一個 VSync 信號來的時候請通知我,當 VSync 信號來的時候,就會收到底層的 JNI 回調,會調用到 dispatchVsync 方法:

    // Called from native code.
    @SuppressWarnings("unused")
    @UnsupportedAppUsage
    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
        onVsync(timestampNanos, physicalDisplayId, frame);
    }

onVsync 方法的實現在 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, CONFIG_CHANGED_EVENT_SUPPRESS);
        }

        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
        // for the internal display implicitly.
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            // Post the vsync event to the Handler.
            // The idea is to prevent incoming vsync events from completely starving
            // the message queue.  If there are no messages in the queue with timestamps
            // earlier than the frame time, then the vsync event will be processed immediately.
            // Otherwise, messages that predate the vsync event will be handled first.
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }

            if (mHavePendingVsync) {
                Log.w(TAG, "Already have a pending vsync event.  There should only be "
                        + "one at a time.");
            } 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);
        }
    }

當接收到信號時候,就會執行 run 方法中的 doFrame 方法:

    @UnsupportedAppUsage
    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            ...

            long intendedFrameTimeNanos = frameTimeNanos;//設置當前幀的Vsync信號到來時間
            startNanos = System.nanoTime();//實際開始執行當前幀的時間
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {//時間差大於一個時鐘週期,認爲跳幀
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                frameTimeNanos = startNanos - lastFrameOffset;// 出現掉幀,把時間修正一下,對比的是上一幀時間
            }

            //時間倒退了,可能是由於改了系統時間,此時就重新申請vsync信號(一般不會走這裏)
            if (frameTimeNanos < mLastFrameTimeNanos) {//
                if (DEBUG_JANK) {
                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                            + "previously skipped frame.  Waiting for next vsync.");
                }
                scheduleVsyncLocked();
                return;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();//申請下一次vsync信號,流程跟上面分析一樣
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

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

        ...
    }

計算收到 VSync 信號到 doFrame 被調用的時間差,VSync 信號間隔是16毫秒一次,大於16毫秒就是掉幀。如果超過30幀(默認30),就打印log提示開發者檢查主線程是否有耗時操作。

接着執行 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;

            ...

        }
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            for (CallbackRecord c = callbacks; c != null; c = c.next) {

                ...

                c.run(frameTimeNanos);
            }
        } finally {
            ...
        }
    }

 這裏的核心的就是取出隊列中任務,也就是 doTraversal 方法。

4. 總結

當 Activity 啓動時候,會調用到 ActivityThread 的 handleResumeActivity 方法,接着調用 ViewRootImpl 的 setView 方法。setView  方法中會調用 requestLayout,requestLayout 方法中調用 scheduleTraversals,接着 doTraversal ,最終調用到了 performTraversals。接着分別執行 performMeasure、performLayout、performDraw 方法。performMeasure 方法中首先會執行 measure 方法,接着執行 onMeasure 方法。在 onMeasure 方法中如果當前 View 存在子 View,則遍歷執行子 View 的 measure ,最終完成測量的工作。performLayout 和 performDraw 的邏輯也一樣。

在 scheduleTraversals 方法中,通過 Choreographer 類向 native 請求 VSync(垂直同步)信號,下一次 VSync(垂直同步)信號來的時候通過 JNI 調用 onVsync 方法通知應用層,並把消息發送到主線程,請求執行 doFrame 渲染下一幀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章