performTraversals三大流程開始之地

工作一段時間了,但是感覺自己對View的三大流程還不是理解透徹。所以主要根據(jù)《Android開發(fā)藝術探索》一書和查看源碼去了解下View的三大流程。
在《Android開發(fā)藝術探索》中說到,ViewRoot是連接WindowManager和DectorView的紐帶,View的三大流程都是通過ViewRoot實現(xiàn)的。而ViewRootImpl是ViewRoot的實現(xiàn)。在Activity被創(chuàng)建之后,會將DectorView添加到window上,同時創(chuàng)建ViewRootImpl,并將兩者關聯(lián)起來(通過ViewRootImpl.setView)。
View的繪制流程是從ViewRoot.performTraversals()開始的,并通過調(diào)用performMeasure、performLayout、performDraw完成頂級View的三大流程。


《Android開發(fā)藝術探索》流程

performTraversals方法很長,分段閱讀畢竟容易理解和消化。首先是很長的一段(哈哈),主要作用就是確定當前窗體大小并進行view樹的測量(測量會提出來下一段分析)

        // cache mView since it is used so much below...
        final View host = mView;

        if (host == null || !mAdded)
            return;

        //一些窗口變量的處理

        Rect frame = mWinFrame;
        if (mFirst) {//是否第一次請求,構造方法中為true
           ....
           //---Activity當前的Window的寬高的確定---
        } else {
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                //視圖大小發(fā)生改變,重繪相關標志位置為true
                mFullRedrawNeeded = true;//需要重新繪制標志位
                mLayoutRequested = true; //要求重新Layout標志位
                windowSizeMayChange = true;//Window的尺寸可能改變
            }
        }

        ....

        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
        if (layoutRequested) {
            ...
            //--檢測邊襯區(qū)域是否發(fā)生變化,有變化則 insetsChanged 變量置為true
            //測量Window的可能大小,實際上進行了measure()測量過程,只不過這個測量過程不屬于三大流程
            // Ask host how big it wants to be
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        }
        
        ...

        if (layoutRequested) {
            // Clear this now, so that if anything requests a layout in the
            // rest of this function we will catch it and re-run a full
            // layout pass.
            mLayoutRequested = false;
        }
        //前面已經(jīng)做了一次measure()工作,host寬高和當前窗口寬高不一致則Activity窗口發(fā)生變化
        boolean windowShouldResize = layoutRequested && windowSizeMayChange
            && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
                || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.width() < desiredWindowWidth && frame.width() != mWidth)
                || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.height() < desiredWindowHeight && frame.height() != mHeight));
        windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;

        // If the activity was just relaunched, it might have unfrozen the task bounds (while
        // relaunching), so we need to force a call into window manager to pick up the latest
        // bounds.
        windowShouldResize |= mActivityRelaunched;
        //檢測相關邊襯區(qū)域,Activity窗口是否指定了額外的內(nèi)容區(qū)域邊襯和可見區(qū)域邊襯
        // Determine whether to compute insets.
        // If there are no inset listeners remaining then we may still need to compute
        // insets in case the old insets were non-empty and must be reset.
        final boolean computesInternalInsets =
                mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
                || mAttachInfo.mHasNonEmptyGivenInternalInsets;

        ...

        final boolean isViewVisible = viewVisibility == View.VISIBLE;
        final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
        //1.Activity窗口是第一次執(zhí)行測量、布局和繪制操作,即mFirst == true
        //2.前面windowShouldResize==true,即Activity窗口的大小發(fā)生了變化
        //3.前面insetsChanged==true,即Activity窗口的內(nèi)容區(qū)域邊襯發(fā)生了變化
        //4.viewVisibilityChanged==true,Activity窗口的可見性發(fā)生了變化
        //5.變量params指向了一個WindowManager.LayoutParams對象,Activity窗口的屬性發(fā)生了變化
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;

            if (isViewVisible) {
                // If this window is giving internal insets to the window
                // manager, and it is being added or changing its visibility,
                // then we want to first give the window manager "fake"
                // insets to cause it to effectively ignore the content of
                // the window during layout.  This avoids it briefly causing
                // other windows to resize/move based on the raw frame of the
                // window, waiting until we can finish laying out this window
                // and get back to the window manager with the ultimately
                // computed insets.
                //Activity窗口是否指定了額外的內(nèi)容區(qū)域邊襯和可見區(qū)域邊襯
                insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
            }

            .....

            try {
                ....
                //請求WindowManagerService服務計算Activity窗口的大小以及過掃描區(qū)域邊襯大小和可見區(qū)域邊襯大小
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                .....
                contentInsetsChanged = !mPendingContentInsets.equals(
                        mAttachInfo.mContentInsets);
                ....
                if (contentInsetsChanged) {
                    mAttachInfo.mContentInsets.set(mPendingContentInsets);
                }
                ....
            } catch (RemoteException e) {
            }
            ...
            //計算完畢之后,Activity窗口的大小就會保存在成員變量mWinFrame中
            //變量frame和mWinFrame引用的是同一個Rect對象
            mAttachInfo.mWindowLeft = frame.left;
            mAttachInfo.mWindowTop = frame.top;

            // !!FIXME!! This next section handles the case where we did not get the
            // window size we asked for. We should avoid this by getting a maximum size from
            // the window session beforehand.
            if (mWidth != frame.width() || mHeight != frame.height()) {
                mWidth = frame.width();
                mHeight = frame.height();
            }
            ....
            //-------進行測量過程,下面分析-------
        } else {
            // Not the first pass and no window/insets/visibility change but the window
            // may have moved and we need check that and if so to update the left and right
            // in the attach info. We translate only the window frame since on window move
            // the window manager tells us only for the new frame but the insets are the
            // same and we do not want to translate them more than once.
            //判斷window是否有移動,發(fā)生移動則執(zhí)行移動動畫
            maybeHandleWindowMove(frame);
        }

上面這一大段代碼,主要作用其實就是為了確定Activity窗口的大小,包括內(nèi)容窗口大小和相關的邊襯區(qū)域大小的確定。

  • Activity窗口是第一次執(zhí)行測量、布局和繪制操作,即mFirst == true
  • 前面windowShouldResize==true,即Activity窗口的大小發(fā)生了變化
  • 前面insetsChanged==true,即Activity窗口的內(nèi)容區(qū)域邊襯發(fā)生了變化
  • viewVisibilityChanged==true,Activity窗口的可見性發(fā)生了變化
  • 變量params != null,指向了一個WindowManager.LayoutParams對象,Activity窗口的屬性發(fā)生了變化

當上面5中情況中一種情況為true,則activity窗口大小發(fā)生變化需要重新測量。并通過WMS請求計算activity窗口大小。然后開始測量流程。

            //mStopped==true,該窗口activity處于停止狀態(tài)
            //mReportNextDraw,Window上報下一次繪制
            if (!mStopped || mReportNextDraw) {
                //觸摸模式發(fā)生了變化,且檢測焦點的控件發(fā)生了變化
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                //1. 焦點控件發(fā)生變化
                //2. 窗口寬高測量值 != WMS計算的mWinFrame寬高
                //3. contentInsetsChanged==true,邊襯區(qū)域發(fā)生變化
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    //------開始執(zhí)行測量操作--------
                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                    //根據(jù)水平/垂直權重值判斷是否重新測量
                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

                    if (measureAgain) {
                        //----有相關權重,需要重新測量-----
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
  • 焦點控件發(fā)生變化
  • 窗口寬高測量值 != WMS計算的mWinFrame寬高
  • contentInsetsChanged==true,邊襯區(qū)域發(fā)生變化

當上述情況之一出現(xiàn)則進行測量流程。測量完成后根據(jù)是否有配置權重進行再次測量。

        //layout布局要求標志位
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            //--------開始執(zhí)行布局操作---------
            performLayout(lp, mWidth, mHeight);

            // By this point all views have been sized and positioned
            // We can compute the transparent area
            //計算透明區(qū)域
            ....
        }
        .....

        mFirst = false;

        ....

        // Remember if we must report the next draw.
        if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
            reportNextDraw();
        }

        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

        if (!cancelDraw && !newSurface) {
            ....
            //-----開始執(zhí)行繪制操作-------
            performDraw();
        } else {
            if (isViewVisible) {
                // Try again
                scheduleTraversals();//重新執(zhí)行performTraversals
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;

Invalidate、postInvalidate、requestLayout應用場景?
哪一個流程可以放在子線程中去執(zhí)行?

參考資料
View繪制流程及源碼解析(一)——performTraversals()源碼分析
Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析
《Android開發(fā)藝術探索》

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容