Activity啟動(2)----setView之后(View的繪制過程)


上一篇文章最后結束在ViewRootImpl.setView()函數,這個函數之后發生了什么事情,我們接着分析。

 1. ViewRootImpl簡介

ViewRootImpl作為視圖層次中的頂層,實現了View和WindowManager之間需要的協議,與SystemServer進程的WindowManagerService有交互,具體實現了WindowManagerGlobal內部的大部分功能。

1.1 ViewRootImpl的定義:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    ......
    final IWindowSession mWindowSession; final W mWindow;                //W繼承於IWindow.Stub類。
    View mView; // 由setView()函數賦值。
    private final Surface mSurface = new Surface();       //mSurface可能存在多線程訪問,需要加鎖保護。
    ......
    public ViewRootImpl(Context context, Display display) {
        ......
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mWindow = new W(this);
        ......
    }
}

mWindowSession將被用於建立Activity的ViewRootImpl和WindowManagerService的關系。創建代碼如下:

getWindowSession()@WindowManagerGlobal.java

public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService(); //得到WindowMangerService的Binder代理.
                sWindowSession = windowManager.openSession(imm.getClient(), imm.getInputContext());  ///openSession操作是一個使用Binder通信的跨進程調用。
                ...... } catch (RemoteException e) { ...... } } return sWindowSession; } }

getWindowManagerService()@WindowManagerGlobal.java

public static IWindowManager getWindowManagerService() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowManagerService == null) {
            sWindowManagerService = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
        }
        return sWindowManagerService;
    }
}

openSession()@WindowManagerService.java

public IWindowSession openSession(IInputMethodClient client,
            IInputContext inputContext) {
    ......
    Session session = new Session(this, client, inputContext);
    return session;
}

可以看到,通過WindowManagerService.openSession()獲得一個Session對象。類Session繼承於IWindowSession.Stub,並實現了IBinder接口。它支持Binder通信,並屬於Bn端,即響應請求的服務端。

1.2 ViewRootImpl與WindowManagerService的關系

    ViewRootImpl通過mWindowSession(IWindowSession)與WMS進程進行跨進程通信。

    ViewRootImpl的mWindow是基於Binder通信的類,而且W是IWindow的Bn端,用於響應請求。

    它們之間屬於跨進程通信,關系如下圖:

  

    每個APP進程都會和WMS建立一個IWindowSession會話,APP進程利用這個會話與WMS通信。WMS利用IWindow進行事件通知,每當發生一些事件(按鍵觸摸事件)時,WMS就會告訴某個IWindow.

    事件分發大致流程:

    (1) WMS所在的SystemServer進程接收到按鍵事件。

    (2) WMS找到UI位於屏幕頂端的進程所對應的IWindow對象,這個是Bp端對象。

    (3) 調用這個IWindow對象的dispatchKey。 IWindow對象的Bn端位於ViewRootImpl中,ViewRootImpl根據內部View的位置信息找到真正處理這個事件的View,最后調用dispatchKey函數完成事件處理。

    每個按鍵事件都會被轉化成一個消息,由系統將這個消息加入到對應進程的消息隊列中。該進程在處理消息派發時,根據消息的句柄找到對應的Window,進而由Window處理掉這個消息。

2. ViewRootImpl.setView()函數

setView()@ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;                 //傳進來的view是PhoneWindow的DecorView,保存起來。
            mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
            mFallbackEventHandler.setView(view);
            mWindowAttributes.copyFrom(attrs);
            ......
            mSoftInputMode = attrs.softInputMode;         // InputMode可以影響輸入法彈出時視圖應該怎么顯示。
            ......
            mAdded = true;
            int res;     /* = WindowManagerImpl.ADD_OKAY; */
            //在被加到WindowManager之前調度第一次layout,確保收到系統事件之前重新進行了布局。
 requestLayout(); if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                mInputChannel = new InputChannel();          //創建InputChannel對象
            }
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,            //注意第一個參數是mWindow. 繼承IWindow.Stub類。
                             getHostVisibility(), mDisplay.getDisplayId(),
                             mAttachInfo.mContentInsets, mInputChannel);
            } catch (RemoteException e) {
                ......
            }

           // 創建InputQueue和WindowInputEventReceiver與窗口輸入事件相關的對象。 if (mInputChannel != null) { if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue); } mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); } mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0; // 根據res設置touch mode。 mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0; // 根據res設置visible屬性。 ...... } } }

3. performTraversals()函數

requestLayout()函數的功能就是觸發UI繪制。結合時序圖。調用3至調用15,是消息發送傳遞及Callback設置過程。最后會由TraversalRunnable的run函數中調用ViewRootImpl.doTraversal()函數去調用performTraversals()開始進行UI繪制。

performTraversals()函數很復雜,我們先專注於時序圖展示的內容。

performTraversals()@ViewRootImpl.java

private void performTraversals() {
    ......
    final View host = mView;

    if (mFirst || windowShouldResize || insetsChanged ||
              viewVisibilityChanged || params != null) {
......

          try {
              ......
              relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
          }
          ......

        if (!mStopped) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                            (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);       //獲取測量模式。lp.width和lp.height在創建ViewGroup實例時等於MATCH_PARENT
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                // Ask host how big it wants to be
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);            // 根據測量模式進行測量。
                ......
                layoutRequested
= true; } } } final boolean didLayout = layoutRequested && !mStopped; boolean triggerGlobalLayoutListener = didLayout || attachInfo.mRecomputeGlobalAttributes; if (didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); // By this point all views have been sized and positioned         ...... }     ......      boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() || viewVisibility != View.VISIBLE; if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) {             ......
            performDraw(); } }
    ...... }

3.1 relayoutWindow()函數

relayoutWindow()@ViewRootImpl.java

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
                           boolean insetsPending) throws RemoteException {
    ......
    // 調用WMS的relayout函數。通過該函數修改一個window的參數,提供新的參數,它將返回屏幕上window的新的一幀(忽略位置),和該window的surface。如果window當前是隱藏的,那么surface是無效的,否則就可以使用它來畫window的內容。
    int relayoutResult = mWindowSession.relayout(
                             mWindow,  // 需要修改參數的window
                             mSeq,     // 排序的序列號
                             params,   // 要應用到window的新參數
                             (int) (mView.getMeasuredWidth() * appScale + 0.5f), //window想要的寬
                             (int) (mView.getMeasuredHeight() * appScale + 0.5f), //window想要的高
                             viewVisibility, // window的root view的可見性
                             insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                             mWinFrame, // outFrame, 放着屏幕上新position/size的Rect
                             mPendingOverscanInsets,
                             mPendingContentInsets,
                             mPendingVisibleInsets,
                             mPendingConfiguration,
                             mSurface  // 存放新顯示surface的對象。
                         );
    ......
    return relayoutResult;
}

3.2 performMeasure()

    在執行performMesure()時,需要傳入測量模式,這里簡單介紹下MeasureSpec。MeasureSpec封裝了從父view傳 給子view的布局要求,每個MeasureSpec代表對寬度或高度的要求。每個MeasureSpec值由大小和模式組成,高16位表示mode,低 16位表示size,即父view的size。
MeasureSpec有三種可能的模式:
    UNSPECIFIED: 父view沒有強加任何限制給子view,子view可以有它想要的size。
    EXACTLY:父view決定子view的確切size,子view將被指定這些邊界,而忽視它想要的size.
    AT_MOST: 父view對子view沒有限制,子view可以獲得它自己指定的size。

對於DecorView來說,mode一般為EXACTLY,size為屏幕的寬高。

看下父view中是如何得到MeasureSpec的:

getRootMeasureSpec()@ViewRootImpl.java

// 根據layout params計算window中root view的MeasureSpec. // windowSize: window可用的寬或高 // window的寬或高的layout params. private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. // MeasureSpec.EXACTLY: 父控件決定子控件的確切大小,子控件與父控件一樣大,子控件要求的大小被忽視。 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. // MeasureSpec.AT_MOST:子控件的大小可以為它指定的大小。 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }

performMeasure()@ViewRootImpl.java

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
         mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);     // 調用mView的measure。
    } finally {
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

measure()@View.java 

// 這個函數計算出view的大小,然后設置實際的寬和高。每個view的實際寬高都是由父view和自身共同決定的。
// 實際測量工作在onMeasure()函數中執行,子類也能重載onMesure()函數實現對自身的測量。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  // final方法,子類不可以重寫
    ......if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // 調用onMeasure方法, 測量自己,這個調用需要重新設置measured dimension flag。
 onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        ......
        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
            // 子類重寫onMeausre方法的時,必須調用setMeasuredDimension或者super.onMeasure方法,不然就會走到這里報錯。
            // setMeasuredDimension中回去改變mPrivateFlags的值
            throw new IllegalStateException("onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }

        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

onMeasure()@View.java

// 提供view和它的內容的測量寬高,應該由子類重寫這個函數來提供准確有效的測量值。這個函數的兩個參數都是由父view傳過來的,代表父view的規格。
// 子類重寫這個函數的時候必須調用setMeasuredDimension()保存這個view的測量寬和高,確保測量的寬和高至少是view的最小寬高。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

setMeasuredDimension()@View.java

// 設置測量結果及flags.
protected
final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } // 賦值給mMeasuredWidth,getMeasuredWidth()函數返回的就是這個值。
    // measure的目的就是對view樹中的每個view的mMeasuredWidth和mMesuredHeight賦值。這兩個值被賦值,該view的測量工作才算結束。
mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; // 設置flag, 這就是重寫onMeasure方法時如果不調用setMeasuredDimension方法時, measure會拋出異常的原因。 mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }

View,ViewGroup,FrameLayout,DecorView的關系如下:

DecorView.onMesure()@PhoneWindow.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
    final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;

    final int widthMode = getMode(widthMeasureSpec);
    final int heightMode = getMode(heightMeasureSpec);

    boolean fixedWidth = false;
    if (widthMode == AT_MOST) {
        final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
        if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
            final int w;
            if (tvw.type == TypedValue.TYPE_DIMENSION) {
                w = (int) tvw.getDimension(metrics);
            } else if (tvw.type == TypedValue.TYPE_FRACTION) {
                w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
            } else {
                w = 0;
            }
            if (w > 0) {
                final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
                widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(w, widthSize), EXACTLY);
                fixedWidth = true;
            }
        }
    }

    if (heightMode == AT_MOST) {
        final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
        if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
            final int h;
            if (tvh.type == TypedValue.TYPE_DIMENSION) {
                h = (int) tvh.getDimension(metrics);
            } else if (tvh.type == TypedValue.TYPE_FRACTION) {
                h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
            } else {
                h = 0;
            }
            if (h > 0) {
                final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
                heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(h, heightSize), EXACTLY);
            }
        }
    }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    // 調用FrameLayout.onMeasure().
    int width = getMeasuredWidth(); boolean measure = false; widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); if (!fixedWidth && widthMode == AT_MOST) { final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor; if (tv.type != TypedValue.TYPE_NULL) { final int min; if (tv.type == TypedValue.TYPE_DIMENSION) { min = (int)tv.getDimension(metrics); } else if (tv.type == TypedValue.TYPE_FRACTION) { min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels); } else { min = 0; } if (width < min) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); measure = true; } } } if (measure) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }

 onMesure()@FrameLayout.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();         // 獲得子view的數目

    final boolean measureMatchParentChildren =
               MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
               MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    for (int i = 0; i < count; i++) {    // 測量每個子View
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }

    // Account for padding too
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
 
    // Check against our minimum height and width
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Check against our foreground's minimum height and width
    final Drawable drawable = getForeground();
    if (drawable != null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    // 設置自己的size, 即ViewGroup的size. 可以看到,它的size由父view及子view共同決定。 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState
<< MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) {   // 對於match parent的child進行測量。 for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidthMeasureSpec; int childHeightMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } if (lp.height == LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }

 measureChildWithMargins()@ViewGroup.java

// 要求view的一個child測量自己,同時考慮MeasureSpec要求和padding和margins.
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
    int parentHeightMeasureSpec, int heightUsed) {
    // 獲取子view的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();     // 調整MeasureSpec,通過傳進來的父view的MeasureSpec及子view自身的LayoutParams,共同決定子view的MeasureSpec. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);      // 調用子view的measure(),又重新開始上面measure的流程。子view的measure()中會回調子view的onMeasure()函數。 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

getChildMeasureSpec()@ViewGroup.java

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //獲取parent view的mode和size
   int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec);     //parent size與padding的差值(即parent中除去padding后剩余的大小),若小於0則直接返回0. int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) {  //根據parent mode進行處理 // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) {                     // 若child的layout_width或者layout_height在xml或者代碼中指定了大於等於0的值,
               // 則設置child的size值為指定的值,mode為EXACTLY.
                resultSize
= childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. // 若child的layout_width或者layout_height在xml或者代碼中指定為MATCH_PARENT,
               // 則設置child的size值為size,mode為EXACTLY.

                resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be bigger than us. // 若child的layout_width或者layout_height在xml或者代碼中指定為WRAP_CONTENT,
               // 則設置child的size值為指定的值,mode為AT_MOST.

                resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us // 下面兩個分支類似
        case MeasureSpec.AT_MOST:            ......break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED:             ......break; }
    //使用mode和size生成MeasureSpec返回。
return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

   可以看到,getChildMeasureSpec()是通過其父view提供的MeasureSpec參數得到specMode和specSize,然后根據計算出來的specMode以及子view的childDimension(layout_width或layout_height)來計算自身的measureSpec,如果其本身包含子視圖,則計算出來的measureSpec將作為調用其子視圖measure函數的參數,同時也作為自身調用setMeasuredDimension()的參數,如果其不包含子視圖則默認情況下最終會調用onMeasure的默認實現,並最終調用到setMeasuredDimension()。

performMeasure()小結:

(1) mView.measure() , 即調用View.measure()方法。

(2) View.measure()內部調用onMeasure()方法,因為這里的mView是DecorView,調用的是DecorView.onMeasure(), 這個函數內部調用super.onMeasure()。因為DecorView繼承於FrameLayout,所以super.onMeasure()即調用FrameLayout的onMeasure()方法。

(3) FrameLayout.onMeasure()對每個子view進行遍歷測量,對每個子view調用measureChildWithMargins()方法測量子view。

(4) measureChildWithMargins()內部調用getChildMeasureSpec()計算出傳給子view的MeasureSpec,並調用子view的measure()方法。

(5) FrameLayout.onMeasure()中根據父view及(3)測量出來的子view的MeasureSpec信息調用setMeasuredDimension()設置自身的size。

(6) 使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取view測量的寬高,必須保證這兩個方法在onMeasure流程之后被調用才能返回有效值。

(7) ViewGroup的子類必須要求LayoutParams繼承於MarginLayoutParams,否則無法使用layout_margin參數。

3.3 performLayout()

performLayout()@ViewRootImpl.java

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;
 
    final View host = mView;
 
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        // 把performMeasure測量的結果傳入layout()函數
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 
        mInLayout = false;
        ......
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

這個函數內調用mView.layout()方法,而ViewGroup中重寫了layout()方法,並調用了super.layout(), 即View.layout()。
layout()@View.java

// 給view及它的子孫的size及position賦值。
// 這是布局機制的第二階段(第一階段是measuring)。在這個階段,每個父view都會調用其所有子view的layout()來放置它們。
// 子類中不應該重載這個方法,若子類中有children, 則該子類應該重載onLayout()函數,在這個函數中調用每個child的layout.
public void layout(int l, int t, int r, int b) {
    ......int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
 
    // 這部分是判斷這個View的大小是否已經發生了變化,來判斷是否需要重新layout。判斷的方式就是通過將l,t,r,b參數傳給setFrame()賦值給
    // mLeft,mTop, mRight,mBottom。
// isLayoutModeOptical(): 如果mParent是一個使用可見范圍進行編排的ViewGroup, 返回true. boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { // 調用onLayout方法 onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;        ...... } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }

我們先來看View的onLayout()方法,

onLayout()@View.java

protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } 

這是一個空方法,這是因為Layout需要ViewGroup來控制進行。

再來看ViewGroup的onLayout()方法:

protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

onLayout在ViewGroup中是個抽象方法,所以ViewGroup的子類都需要實現該方法。FrameLayout繼承於ViewGroup,我們來看下FrameLayout.onLayout()的實現。

onLayout()@FrameLayout.java

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
 
void layoutChildren(int left, int top, int right, int bottom,
                              boolean forceLeftGravity) {
    final int count = getChildCount();
 
    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();
 
    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();
 
    mForegroundBoundsChanged = true;
 
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            // 獲取子view的LayoutParams
final LayoutParams lp = (LayoutParams) child.getLayoutParams();            // measure過程計算處理的子view的寬高 final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; }             final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;            // 根據absoluteGravity計算childLeft switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; }             // 根據verticalGravity計算childTop switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } //調用子child的layout方法設置child的位置。 child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }

FrameLayout.onLayout()函數也展示了一般的自定義ViewGroup重載onLayout函數的實現方式。onLayout()函數的目的就是確定子view們在父view中的具體位置,通常做法是用一個for循環調用每個子view的layout(l, t, r, b)函數,傳入不同的l,t,r,b值確定子view的位置。

performLayout()小結:

(1) 調用host.layout(), 即View.layout()。

(2) View.layout()中調用setFrame()或者setOpticalFrame()賦值給View的mLeft,mTop,mRight,mBottom變量並判斷位置是否變化,若變化則調用onLayout()。

(3) 因為host是mView就是DecorView,也就是FrameLayout的子類,所以實際上是FrameLayout.onLayout()被調用。

(4) 在FrameLayout.onLayout()函數中遍歷每個子view, 並調用每個子view的layout()設置子view的位置。

(5) View.layout()函數可以被重載,ViewGroup.layout不能被重載,ViewGroup的onLayout為abstract的,子類必須重載實現自己的位置邏輯。

(6) measure()過程的結果是得到每個view的measuredWidth和measuredHeight,layout()過程的結果是得到每個view的mLeft,mTop,mRight,mBottom.這些值都是相對於父view而言的。

(7) layout_XXX的布局屬性都是針對包含子view的ViewGroup的,當對一個沒有父容器的view設置相關layout_XXX屬性是沒有意義的。

(8) 使用View的getWidth()和getHeight()方法來獲取view測量的寬高時,必須保證這兩個方法在onLayout流程之后被調用才能返回有效值。

 3.4 performDraw()

performDraw()@ViewRootImpl.java

private void performDraw() {
    if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
        return;
    }

    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;

    mIsDrawing = true;
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {
        // 開始draw
 draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ......
}

 draw()@ViewRootImpl.java 

private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    if (!surface.isValid()) {
        return;
    }
    ......if (!dirty.isEmpty() || mIsAnimating) {
        if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
            ......
            attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty);   // 使用硬件繪制
        } else {
            ......// 使用軟件進行繪制。
            if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
                return;
            }
        }
    }

    if (animating) {
        mFullRedrawNeeded = true;
        scheduleTraversals();    // 如果在動畫中,重新發送繪制消息
    }
}

 drawSoftware()@ViewRootImpl.java

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
                             boolean scalingRequired, Rect dirty) {
    // 使用軟件渲染繪制。
    Canvas canvas;
    try {
        int left = dirty.left;
        int top = dirty.top;
        int right = dirty.right;
        int bottom = dirty.bottom;

        canvas = mSurface.lockCanvas(dirty);    //從mSurface中lock一塊Canvas. The dirty rectangle can be modified by Surface.lockCanvas()
        //no inspection Constant Conditions
        if (left != dirty.left || top != dirty.top || right != dirty.right ||
                        bottom != dirty.bottom) {
            attachInfo.mIgnoreDirtyState = true;
        }
            // TODO: Do this in native
        canvas.setDensity(mDensity);
    } catch (Surface.OutOfResourcesException e) {
        handleOutOfResourcesException(e);
        return false;
    } catch (IllegalArgumentException e) {
        // Don't assume this is due to out of memory, it could be
        // something else, and if it is something else then we could
        // kill stuff (or ourself) for no reason.
        mLayoutRequested = true;    // ask wm for a new surface next time.
        return false;
    }

    try {
        // 如果該位圖格式包含alpha通道,需要在繪制之前把它清除。這樣child就可以在透明背景下重新合成繪制。
        // 如果使用了偏移,需要清理偏移不會出現的區域,以免垃圾遺留在空白區域上。
        if (!canvas.isOpaque() || yoff != 0) {
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }
    
        dirty.setEmpty();
        mIsAnimating = false;
        attachInfo.mDrawingTime = SystemClock.uptimeMillis();
        mView.mPrivateFlags |= View.PFLAG_DRAWN;

        try {
            canvas.translate(0, -yoff);
            if (mTranslator != null) {
                mTranslator.translateCanvas(canvas);
            }
            canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
            attachInfo.mSetIgnoreDirtyState = false;

            mView.draw(canvas); // 調用View.draw()由view自己在canvas上進行繪制。
    
            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
            if (!attachInfo.mSetIgnoreDirtyState) {
                // Only clear the flag if it was not set during the mView.draw() call
                attachInfo.mIgnoreDirtyState = false;
            }
        }
    } finally {
        try {
            surface.unlockCanvasAndPost(canvas);    // unlock canvas
        } catch (IllegalArgumentException e) {
            mLayoutRequested = true;    // ask wm for a new surface next time.
            //noinspection ReturnInsideFinallyBlock
            return false;
        }
    }
    return true;
}

 draw()@View.java

// 在給定的canvas上手動渲染view及所有子view.這個函數被調用之前,該view必須已經完整走了一遍layout.
// 當實現一個view時,應該實現onDraw()而不是重載這個函數。如果要重載這個函數,調用super.draw()。
public void draw(Canvas canvas) {
    if (mClipBounds != null) {
        canvas.clipRect(mClipBounds);
    }
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
        final Drawable background = mBackground;  // 獲取xml中通過android:background屬性或者代碼中setBackgroundColor(),setBackgroundResource()等進行賦值的背景Drawable.
        if (background != null) {
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            // 根據layout過程確定的view的位置設置背景繪制區域。
            if (mBackgroundSizeChanged) {
                background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                mBackgroundSizeChanged = false;
            }

            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);    // 調用Drawble.draw()完成繪制工作。 
            } else {
                canvas.translate(scrollX, scrollY);
                background.draw(canvas);
                canvas.translate(-scrollX, -scrollY);
            }
        }
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {                         // 沒有邊緣效果
        // Step 3, draw the content
        if (!dirtyOpaque)  onDraw(canvas);  // 對view的內容進行繪制
        
        // Step 4, draw the children
 dispatchDraw(canvas);   // 對當前所有子view進行繪制,如果當前的view沒有子view,就不需要繪制。// Step 6, draw decorations (scrollbars). scroolbars第一次喚醒時進行繪制。
        // 注意,每一個view都是有水平和垂直滾動條的,只是大多數情況不顯示而已。
onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // we're done... return; }     ......
}

    View.onDraw()方法是一個空方法,而ViewGroup也沒有重寫該方法,需要子類去實現具體的onDraw()邏輯,這是因為每個view的內容是各不相同的。

View.dispatchDraw()是一個空函數,有子view時才進行繪制,看下在ViewGroup中的實現。

dispatchDraw()@ViewGroup.java

protected void dispatchDraw(Canvas canvas) {
    final int count = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
    ......
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } else { for (int i = 0; i < count; i++) { final View child = children[getChildDrawingOrder(count, i)]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } // Draw any disappearing views that have animations if (mDisappearingChildren != null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; // Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } } ......// mGroupFlags might have been updated by drawChild() flags = mGroupFlags; if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) { invalidate(true); }     ......
}

 drawChild()@ViewGroup.java

 
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);  //調用View.draw(),child的繪制重新走上面流程。        
}

 performDraw()小結:

(1) View默認不會繪制內容的,需要子類自己去實現實際的繪制。

(2) View的繪制是利用onDraw()函數傳入的Canvas進行的。

(3) 看源碼中有很多關於動畫的邏輯,需要區分View動畫和ViewGroup布局動畫。View動畫是指View自身的動畫,可以通過setAnimation()進行設置。ViewGroup布局動畫是針對ViewGroup顯示內部子view時設置的動畫,可以在xml文件中對ViewGroup設置layoutAnimation屬性。

(4) 子view在獲取canvas的剪切區時不用關注padding,padding會被自動處理掉。

(5) 默認情況下子ViewGroup.drawChild()的順序與子view被添加的順序一致,可以通過ViewGroupDrawingOrder()函數提供不同的順序。

3.5 View.invalidate()和View.postInvalidate()介紹

上一篇文章中,ViewGroup.addView()調用了invalidate()函數,現在來介紹下這個函數的內容。

3.5.1 View.invalidate()的作用

invalidate()的作用是廢止整個view,如果這個view是可見的,那么在將來某個時間點onDraw()會被調用。這個函數必須在UI線程中調用,在非UI線程可以調用postInvalidate()。

這個函數有三種具體實現:

(1) void invalidate(boolean invalidateCache) : 完整的廢止會將drawing cache一起廢止, 當view的內容或者dimensions不變的時候,這個函數可以設置不廢止cache。

(2) void invalidate(Rect dirty) : 標記由dirty定義的區域為需要繪制的區域。

(3) void invalidate(int l, int t, int r, int b) : 標記由rect(l,t,r,b)定義的區域,dirty rect的坐標是相對於view的。

3.5.2 常見會引起invalidate()調用的原因

(1) 直接調用invalidate()方法請求重新draw,只會繪制調用者本身。

(2) 調用setSelection()方法請求重新draw,只會繪制調用者本身。

(3) 調用setVisibility()方法, 當view的狀態從INVISIBLE變成VISIBLE時會間接調用invalidate方法,從而繪制該view。當view的狀態從 INVISIBLEVISIBLE變為GONE狀態時會間接調用requestLayout和invalidate()方法,同時由於view樹大小發生 了變化,所以會請求measure過程以及draw過程,同樣只繪制需要重新繪制的view。

(4) 調用setEnabled()方法請求重新draw,不會重新繪制任何view包括該調用者本身。

(5) 調用requestFocus()方法請求view樹的draw過程,只繪制需要重繪的view。

3.5.3 invalidate()源碼分析

View.invalidate()會調用父view的invalidateChild()函數,實際上調用的是ViewGroup.invalidateChild()函數。

invalidateChild()@ViewGroup.java

public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    ......
    do {
        ......    
        parent = parent.invalidateChildInParent(location, dirty);  //層層循環向上調用,知道ViewRootImpl返回null.
        ......
    } while (parent != null);
    ......
}

invalidateChild()實際上是一個從當前view向上父view回溯的過程。每一層的父view都將自己的顯示區域與傳入的dirty Rect做交集。

invalidateChild()函數調用最后會傳到ViewRootImpl.invalidateChindInParent()。

invalidateChindInParent()@ViewRootImpl

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    ......
    final Rect localDirty = mDirty;
    ......
    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);   // 融合dirty rect與當前的rect.
    ......
    scheduleTraversals();          // 回到上文第3點, 會異步觸發執行performTraversals()。
    ......
    return null;
}

ViewRootImpl.invalidateChindInParent()直接返回null,也就是ViewGroup.invalidateChild()中do-while循環結束的條件。

調用invalidate()的實質就是從當前view層層上傳到父級,直到ViewRootImpl觸發scheduleTraversals()函數,整個view樹重新走一遍上面的繪制流程。

3.5.4 postInvalidate()函數

View.invalidate()是在UI線程里運行的,在其它線程中只能調用View.postInvalidate(),該函數最終會通過ViewRootImpl類的handler發送MSG_INVALIDATE消息,最終又在UI線程中執行View.invalidate()。

3.6 requestLayout()函數

requestLayout()@View.java

public void requestLayout() {
    ......
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ......
}

當view的requestLayout()函數被調用時,實際上是層層向父級傳遞,知道調用ViewRootImpl.requestLayout()為止。

requestLayout()@ViewRootImpl.java

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

scheduleTraversals()即時序圖中的第3個調用,后面的流程與上面描述的類似了。需要注意的是requestLayout()函數會觸發measure和layout,但不會draw,不會重新繪制包括調用者本身的任何view。

4. addToDisplay()@Session.java

通過addToDisplay()將window對象添加到WMS中,WMS將管理這個window。

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
    int viewVisibility, int displayId, Rect outContentInsets, InputChannel outInputChannel) {
    // 調用WindowManagerService的addWindow()函數
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,        
            outContentInsets, outInputChannel);
}

addWindow()@WindowManagerService.java

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, InputChannel outInputChannel) {
    ......
    WindowState win = null;
    ......
    // 創建一個WindowState, WMS中維護的是WindowState對象.
    win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
    ......
    win.attach();    // 調用WindowState.attach()函數。
    mWindowMap.put(client.asBinder(), win);
    ......
    return res;
}

attach()@WindowState.java

void attach() {
    mSession.windowAddedLocked();
}

windowAddedLocked()@Session.java

void windowAddedLocked() {
    if (mSurfaceSession == null) {
        mSurfaceSession = new SurfaceSession();  //創建SurfaceSeesion對象
        mService.mSessions.add(this);
    }
    mNumWindow++;
}

 

 

(時序圖中調用26以后就是在WindowManagerService中與底層交互,控制底層顯示實際內容了,在本文中暫不考慮)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM