View繪制流程分析-基于Android API31

View從setContentViewonMeasure->onLayout->onDraw經(jīng)歷哪些流程。下面就來梳理哈View的繪制流程

setContentView做了什么工作?

時序圖
Activity.setContentView
public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}

這里調(diào)用PhoneWindowsetContentView户誓,然后在初始化ActionBar

PhoneWindow.setContentView
    public void setContentView(int layoutResID) {
        ...
          //第一次調(diào)用這里必為空
          if (mContentParent == null) {
            //創(chuàng)建DecorView拄养,并將ContentParent區(qū)域的View對象賦值給mContentParent
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
         mLayoutInflater.inflate(layoutResID, mContentParent);
        ...
    }

創(chuàng)建DecorView,在創(chuàng)建DecorView時也將Window設(shè)置DecorView

LayoutInflater.inflate
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }
        //預(yù)加載的View实抡,比會返回null
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        //得到資源解析器
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
         
                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                //處理merge標簽
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 創(chuàng)建本層級View,也就是xml中最外層的ViewGroup爬早,并加入到ContentParent中
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // 遞歸創(chuàng)建之View,并添加到他的父View中
                    rInflateChildren(parser, temp, attrs, true);
                }
                    ...
            return result;
        }
    }

createViewFromTag:方法創(chuàng)建xml中最外層父ViewGroup窃款,將該View添加至ContentParent中平匈,并設(shè)置ViewGroupLayoutParams.
rInflateChildren:使用遞歸的方法去創(chuàng)建子View框沟,并添加到他們的父View

這樣setContentView的工作就結(jié)束了,setContentView的工作加載xml解析后生成View增炭,填充到DecorViewContentParent中忍燥。xml資源轉(zhuǎn)為對象,并填充至DecorView隙姿。
下面我們繼續(xù)看View的繪制

View繪制

在上一篇《Activity啟動流程-基于Android API31》中有介紹到View在ActivityThread.handleResumeActivity中進行繪制

時序圖

ActivityThread.handleResumeActivity
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
        ...
        //執(zhí)行onResume方法
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        ...
        //添加DecorView
        if (r.window == null && !a.mFinished && willBeVisible) {
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                     //添加decor,并使用Handler發(fā)送異步消息梅垄,用于View繪制
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }
       ...
    }
WindowManagerImpl.addView

WindowManager的實現(xiàn)類

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        //賦值windowToken, PopupDialog多次添加同一個PopupDialog時提示token問題就是這個
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
WindowManagerGlobal.addView

是一個單例输玷,管理ViewRootImpl队丝、RootView

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
         ...
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...
            創(chuàng)建ViewRootImpl實例
            root = new ViewRootImpl(view.getContext(), display);
            //設(shè)置LayoutParams
            view.setLayoutParams(wparams);
            //WindowManagerGlobal 記錄信息
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

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

View的頂層類。WindowManagerGlobal為Window提供View管理欲鹏,WindowManagerGlobal管理所有的ViewRoot机久、ViewRootImplViewRootImpl更像是WindowManagerGlobal的具體實現(xiàn)類赔嚎。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        ....
        requestLayout();
        ...
       //IPC 通信膘盖,這里將ViewRootImpl的通訊句柄交給WMS,就是這里傳入的mWindow尤误,mWindow類型是ViewRootImpl的內(nèi)部類M侠畔,用于WMS對ViewRootImpl的通訊,mWindowSession用于ViewRootImpl對WMS的通訊
      //這里的IWindowSession實現(xiàn)類為“com.android.server.wm.Session”损晤,最終方法內(nèi)調(diào)用WindowManagerService.addWindow添加window
      //res践图,返回添加狀態(tài)詳細參考WindowManagerGlobal.ADD_xxxxx
        res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(), userId,
                mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                mTempControls);
       ...
    }

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //檢測主線程,不在就拋異常
            checkThread();
            mLayoutRequested = true;
            //開啟調(diào)度
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //發(fā)送屏障消息沉馆,為繪制做準備
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //添加繪制回調(diào)
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

  1. 第一步發(fā)送消息屏障
  2. 第二步添加VSYNC信號監(jiān)聽(以60幀率計算 1s/60 最大16.6ms刷新一次)码党,當VSYNC信號來時回調(diào)mTraversalRunnable

消息同步屏障postSyncBarrier()發(fā)送同步屏障消息德崭,這個消息會使Handler停止處理同步普通消息,只處理異步消息揖盘。發(fā)送屏障消息時眉厨,如果Handler處于阻塞狀態(tài)也不會去喚醒。
異步消息:普通message調(diào)用setAsynchronous(true)兽狭,即可成為異步消息憾股,當設(shè)置了同步屏障后,優(yōu)先同步消息執(zhí)行
如果未發(fā)送屏障消息箕慧,異步消息和同步消息一樣服球,不會獲得優(yōu)先執(zhí)行的權(quán)利。
這里設(shè)置同步屏障颠焦,因為馬上要進行界面繪制斩熊,避免其他耗時消息導(dǎo)致繪制延遲。

Choreographer.postCallback

這個類對上層應(yīng)用提供VSYNC信號監(jiān)聽(FrameDisplayEventReceiver)伐庭,當有信號量時處理調(diào)監(jiān)聽回調(diào)粉渠。對底層起到接收VSYNC信號的作用(FrameDisplayEventReceiver

    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) {
        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;
            //添加回調(diào)
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            //延遲時間是否到了,一般情況都是true
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                //還未到時間發(fā)送一個異步消息
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {//true
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }
                //當前線程是否和looper線程一致
                if (isRunningOnLooperThreadLocked()) {
                    //請求信號量
                    scheduleVsyncLocked();
                } else {
                    //不一致用Handler切換到Looper線程
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                //老版本使用Handler去處理繪制圾另,新版本使用VSYNC信號
                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);
            }
        }
    }

    private void scheduleVsyncLocked() {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
            //請求信號量霸株,實際調(diào)用 DisplayEventReceiver.nativeScheduleVsync(mReceiverPtr);
            mDisplayEventReceiver.scheduleVsync();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

Choreographer.FrameDisplayEventReceiverChoreographer的內(nèi)部類繼承DisplayEventReceiver用于監(jiān)聽信號量回調(diào),當注冊后集乔,有了信號量會回調(diào)FrameDisplayEventReceiver.dispatchVsync方法去件,該方法為重寫所以會調(diào)用父類DisplayEventReceiver.dispatchVsync
這里有用到2個同步消息的地方,一個為了切換線程扰路,一個是老版本的利用Handler異步消息去繪制界面

FrameDisplayEventReceiver.dispatchVsync
     private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
         ...
        // 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,
                VsyncEventData vsyncEventData) {
            try {
                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                            "Choreographer#onVsync " + vsyncEventData.id);
                }
                // 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;
                mLastVsyncEventData = vsyncEventData;
                //發(fā)送同步消息箫攀,Callback為this。
                Message msg = Message.obtain(mHandler, this);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }

        @Override
        public void run() {
            //消息回調(diào)執(zhí)行這里
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
        }
    }

這里為什么使用Handler去異步消息后執(zhí)行run方法幼衰,不直接調(diào)用run方法呢靴跛?應(yīng)該也是為了切換線程

Choreographer.doFrame
  void doFrame(long frameTimeNanos, int frame,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
                 ...
                //掉幀日志
                final long jitterNanos = startNanos - frameTimeNanos;
                if (jitterNanos >= frameIntervalNanos) {
                    final long skippedFrames = jitterNanos / frameIntervalNanos;
                    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 % frameIntervalNanos;
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                                + "which is more than the frame interval of "
                                + (frameIntervalNanos * 0.000001f) + " ms!  "
                                + "Skipping " + skippedFrames + " frames and setting frame "
                                + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                    }
                    frameTimeNanos = startNanos - lastFrameOffset;
                }

                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.");
                    }
                    traceMessage("Frame time goes backward");
                    scheduleVsyncLocked();
                    return;
                }

                if (mFPSDivisor > 1) {
                    long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                    if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                        traceMessage("Frame skipped due to FPSDivisor");
                        scheduleVsyncLocked();
                        return;
                    }
                }

                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                        vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
                mFrameScheduled = false;
                mLastFrameTimeNanos = frameTimeNanos;
                mLastFrameIntervalNanos = frameIntervalNanos;
                mLastVsyncEventData = vsyncEventData;
            }

            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            //輸入事件回調(diào)處理
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

            mFrameInfo.markAnimationsStart();
            //動畫回調(diào)處理
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                    frameIntervalNanos);

            mFrameInfo.markPerformTraversalsStart();
            //消息繪制處理
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
            //Commit 相關(guān)回調(diào)
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
        }
    }

void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
            //根據(jù)回調(diào)類型,取出所有回調(diào)
            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]);
            //遍歷回調(diào)
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                //執(zhí)行run方法
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                //回收掉所有的回調(diào)對象
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
  1. 取出回調(diào)并執(zhí)行run方法
  2. 回收回調(diào)對象渡嚣。因為繪制(包括繪制在內(nèi)的梢睛,動畫,輸入事件等回調(diào)事件超級多)非常超級頻繁识椰,如果一直創(chuàng)建和回收會造成內(nèi)存抖動绝葡。

這里的run方法為Choreographer.postCallback方法傳入的TraversalRunnable對象

TraversalRunnable.run
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
ViewRootImpl.performTraversals
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //刪除屏障信息
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //繪制
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

刪除屏障信息,讓Handler獲得繼續(xù)處理同步消息的能力

ViewRootImpl.performTraversals
    private void performTraversals() {
      ...
        //window的寬高
        int desiredWindowWidth;
        int desiredWindowHeight;
        ...
            //根據(jù)window寬高去測試View樹腹鹉,是否需要修改Window藏畅,這個方法中最少執(zhí)行一次onMeasure,至多執(zhí)行三次
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        ...
            //重新設(shè)置Window,并且關(guān)聯(lián)surface
            relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
        ...
            //再次測量
             performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
       ...
        if (didLayout) {
            //調(diào)用Layout
            performLayout(lp, mWidth, mHeight);
        }
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
        if (!cancelDraw) {
            //調(diào)用Draw
            performDraw();
        }
    }

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        boolean windowSizeMayChange = false;

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                "Measuring " + host + " in display " + desiredWindowWidth
                + "x" + desiredWindowHeight + "...");
        //是否測量好
        boolean goodMeasure = false;
        //寬度為WRAP_CONTENT
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            // On large screens, we don't want to allow dialogs to just
            // stretch to fill the entire width of the screen to display
            // one line of text.  First try doing the layout at a smaller
            // size to see if it will fit.
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                    + ", desiredWindowWidth=" + desiredWindowWidth);
            //window的寬度是否大于基礎(chǔ)尺寸
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                //第一次測量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                        + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
                        + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
                //測試的尺寸合適
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                    //子view想要更大的空間
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                            + baseSize);
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    //尺寸不合適愉阎,再次測量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
        }

        if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            //第三次測量
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals -- after measure");
            host.debug();
        }

        return windowSizeMayChange;
    }

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            //RootView 希望填充Window绞蹦,則滿足它,此時它尺寸是確切值
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            //RootView 希望根據(jù)自身內(nèi)容來確定尺寸榜旦,則設(shè)置為AT_MOST 模式
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
           //RootView 希望直接指定尺寸值幽七,則滿足它,此時它尺寸是確切值
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

  private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
        ...
        //根據(jù)測量的view樹的值溅呢,重新設(shè)置window大小
        int relayoutResult = mWindowSession.relayout(mWindow, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize);
        mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
        if (mSurfaceControl.isValid()) {
            if (!useBLAST()) {
                //關(guān)聯(lián)Window和surface
                mSurface.copyFrom(mSurfaceControl);
            } else {
                final Surface blastSurface = getOrCreateBLASTSurface();
                // If blastSurface == null that means it hasn't changed since the last time we
                // called. In this situation, avoid calling transferFrom as we would then
                // inc the generation ID and cause EGL resources to be recreated.
                if (blastSurface != null) {
                    mSurface.transferFrom(blastSurface);
                }
            }
        ...
        ///RootView記錄window大小
        setFrame(mTmpFrames.frame);
        ...
        return relayoutResult;
    }

首先獲取到window的寬高澡屡,然后使用measureHierarchy去測試ViewTree,看是否需要重新設(shè)置window大小。

measureHierarchy三次測量

  1. 先用內(nèi)置的基礎(chǔ)值測量ViewTree
  2. ViewTree要得更大咐旧。于是加大基礎(chǔ)值驶鹉,再次測量
  3. 第二次測量還是不滿足ViewTree,這次使用window的最大寬度再次測量

設(shè)置Window的寬高為當天ViewTree的寬高铣墨,并關(guān)聯(lián)surface
重新設(shè)置好Window后再次測量室埋,然后調(diào)用layoutdraw繪制。
總結(jié)
setContextVIew:將xml資源轉(zhuǎn)化成View對象
onMeasure:測量View大小
onLayout:確定View位置
onDraw:繪制View對象
實際上onDraw后我們?nèi)匀皇强床坏綀D形的踏兜,因為onDraw方法只是將View對象轉(zhuǎn)成一個buffer對象,需要SurfaceFlinger合成八秃,發(fā)送至屏幕碱妆,才可以見到真正的圖片。
View對象是如何轉(zhuǎn)成buffer的呢昔驱,這里就會用到上面綁定Surface

  1. Surface通過dequeueBuffer獲取到一塊繪制緩沖區(qū)
  2. Canvasdraw時會調(diào)用底層Skia引擎進行數(shù)據(jù)寫入
  3. Surface通過queueBuffer提交數(shù)據(jù)到SurfaceFlinger
  4. SurfaceFlinger接受區(qū)對這些數(shù)據(jù)進行合疹尾,發(fā)送到屏幕顯示

SurfaceFlinger:接受緩沖區(qū),對它們進行合并骤肛,然后發(fā)送到屏幕顯示

D杀尽!overR傅摺繁成!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市淑玫,隨后出現(xiàn)的幾起案子巾腕,更是在濱河造成了極大的恐慌,老刑警劉巖絮蒿,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尊搬,死亡現(xiàn)場離奇詭異,居然都是意外死亡土涝,警方通過查閱死者的電腦和手機佛寿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來但壮,“玉大人冀泻,你說我怎么就攤上這事常侣。” “怎么了腔长?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵袭祟,是天一觀的道長。 經(jīng)常有香客問我捞附,道長巾乳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任鸟召,我火速辦了婚禮胆绊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欧募。我一直安慰自己压状,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布跟继。 她就那樣靜靜地躺著种冬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舔糖。 梳的紋絲不亂的頭發(fā)上娱两,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音金吗,去河邊找鬼十兢。 笑死,一個胖子當著我的面吹牛摇庙,可吹牛的內(nèi)容都是我干的旱物。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼卫袒,長吁一口氣:“原來是場噩夢啊……” “哼宵呛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起夕凝,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤烤蜕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后迹冤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讽营,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年泡徙,在試婚紗的時候發(fā)現(xiàn)自己被綠了橱鹏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莉兰,靈堂內(nèi)的尸體忽然破棺而出挑围,到底是詐尸還是另有隱情,我是刑警寧澤糖荒,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布杉辙,位于F島的核電站,受9級特大地震影響捶朵,放射性物質(zhì)發(fā)生泄漏蜘矢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一综看、第九天 我趴在偏房一處隱蔽的房頂上張望品腹。 院中可真熱鬧,春花似錦红碑、人聲如沸舞吭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羡鸥。三九已至,卻和暖如春忠寻,著一層夾襖步出監(jiān)牢的瞬間惧浴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工锡溯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赶舆,地道東北人哑姚。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓祭饭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叙量。 傳聞我的和親對象是個殘疾皇子倡蝙,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容