Activtiy完全解析(三、View的顯示過程measure、layout、draw)

轉載請標明出處:
http://blog.csdn.net/xmxkf/article/details/52840065
本文出自:【openXu的博客】

  在Activity完全解析的第一篇文章 Activtiy完全解析(一、Activity的創建過程)中,我們分析了從調用startActivtiy()Activtiy創建完成的整個過程。其中step20:ActivtiyThread.handleLaunchActivity(r, null)這一步中有兩個重要的步驟,第一步就是調用performLaunchActivtiy(r, customIntent),這個方法中首先創建了需要打開的activity對象,然後調用其activtiy.attach()方法完成activtiy對象的初始化,最後調用其onCreate()方法,這時候activity的聲明週期就開始了。而在上一篇文章 Activtiy完全解析(二、layout的inflate過程)中,我們分析了在onCreate()方法中,從調用setContentView()到layout佈局樹加載完畢的過程,到此爲止,Activtiy並不會展示到用戶眼前,因爲layout只是被填充成一個框架了,就像修房子,首先得畫出設計圖紙,佈局樹就像是房子的設計圖,設計圖畫好了還得按照設計圖紙一磚一瓦的蓋房,所有有了佈局樹,還需要爲上面的每一個控件測量大小,安排擺放的位置並畫到屏幕上。本文我們一起分析Activity窗口的測量、佈局和繪製的過程。

  在講解之前,我們先通過關鍵源碼瞭解一些重要的類之間的關係:ActivityWindowPhoneWindowViewManagerWindowManagerWindowManagerImplWindowManagerGlobalWindowmanagerService

/**Window系列*/
public abstract class Window {
    private WindowManager mWindowManager;
    public void setWindowManager(android.view.WindowManager wm, IBinder appToken, String appName,
                                 boolean hardwareAccelerated) {
        ...
        //調用WindowManagerImpl的靜態方法new一個WindowManagerImpl對象
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    public android.view.WindowManager getWindowManager() {
        return mWindowManager;
    }
}
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    private DecorView mDecor;
}

/**WindowManager系列,用於管理窗口視圖的顯示、添加、移除和狀態更新等*/
public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
public interface WindowManager extends ViewManager {
    ...
}
public final class WindowManagerImpl implements WindowManager {
    //每個WindowManagerImpl對象都保存了一個WindowManagerGlobal的單例
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    public WindowManagerImpl createLocalWindowManager(android.view.Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        //調用WindowManagerGlobal對象的addView()方法
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    ...
}
public final class WindowManagerGlobal {
    private static WindowManagerGlobal sDefaultWindowManager;
    private static IWindowManager sWindowManagerService;
    private static IWindowSession sWindowSession;
    //單例
    private WindowManagerGlobal() {
    }
    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }
    /**WindowManagerGlobal類被加載時,就獲取到WindowManagerService對象*/
    public static void initialize() {
        getWindowManagerService();
    }

    public static IWindowManager getWindowManagerService() {
        //線程安全的
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
            }
            return sWindowManagerService;
        }
    }
    public static IWindowSession getWindowSession() {
        //線程安全的
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to open window session", e);
                }
            }
            return sWindowSession;
        }
    }
}
/**Activity*/
public class Activity ...{
    private Window mWindow;              //mWindow是PhoneWindow類型
    private WindowManager mWindowManager;//mWindowManager是WindowManagerImpl類型
    /**
     * Activity被創建後,執行attach()初始化的時候,就會創建PhoneWindow對象和WindowManagerImpl對象
     */
    final void attach(...) {
        ...
        //①.爲activity創建一個PhoneWindow對象
        mWindow = new PhoneWindow(this);
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        //②.調用Window的getWindowManager()方法初始化mWindowManager,其實就是new了一個WindowManagerImpl對象
        mWindowManager = mWindow.getWindowManager();
    }
}

PhoneWindow:在上一篇文章中就講過,這其實理解起來也比較容易,我們知道activity就是用於展示界面,控制用戶交互的,所以activity中一定維護了用於描述界面的對象,還有就是控制窗口顯示的管理器對象,實際上這兩個對象都是由PhoneWindow維護的,而每個activity都擁有一個PhoneWindow對象mWindow

WindowManager:用於管理窗口的一些狀態、屬性以及窗口的添加、刪除、更新、順序等,WindowManagerViewManager的子接口,ViewManager接口中只有三個抽象方法(addViewupdateViewLayoutremoveView),可顯而知,這個管理器的作用就是控制窗口View的添加、刪除以及狀態更新的。WindowManager也是一個接口,他並沒有實現ViewManager中的抽象方法,其實真正的實現類是WindowManagerImpl。在activity被創建後調用attach()方法初始化的時候,首先創建了PhoneWindow對象mWindow,然後調用mWindowsetWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE) ...)爲其mWindowManager賦值。

WindowManagerGlobal:由於WindowManagerGlobal的關鍵代碼比較長,稍後講解的時候再貼代碼。WindowManagerGlobal是單例模式,Activity在創建完成調用attach()方法初始化時,會實例化mWindowManagerWindowManager的引用指向WindowManagerImpl 的對象),WindowManagerImpl創建之後,就維護了WindowManagerGlobal的單例對象,WindowManagerGlobal中有一個IWindowSession類型的變量sWindowSession,他就是用來和遠程服務WindowManagerService通信的代理。

WindowManagerService:跟之前講解Activity啓動過程中提到的ActivityManagerService(用於管理Android組件的啓動、關閉和狀態信息等)一樣,WMS也是一個複雜的系統服務。
  在瞭解WMS之前,首先要了解窗口(Window)是什麼,Android系統中的窗口是隻屏幕上一塊用於繪製UI元素並能相應用戶交互的矩形區域,從原理上講,窗口是獨自佔有一個Surface實例的顯示區域,例如DialogActivity界面、比值、狀態欄、Toast等都是窗口。Surface就是一塊畫布,應用可以通過Cancas或者OpenGL在其上面作畫,然後通過urfaceFlinger將多塊Surface的內容按照特定的順序進行混合並輸出到FrameBuffer,從而將應用界面顯示給用戶。
  既然每個窗口都有一塊Surface供自己塗鴉,必然需要一個角色對所有窗口的Surface進行協調管理,WMS就是用來做這個事情的,WMS掌管Surface的顯示順序(Z-order)以及位置尺寸,控制窗口動畫,並且還是輸入系統的重要中轉站。

通過上面的分析,我們知道這幾個關鍵類的大概作用,以及他們之間的嵌套關係,接下來,我們就開始分析Activity的繪製過程:

step1:ActivityThread.handleResumeActivity()

首先接着第一篇文章step20handleResumeActivity()方法分析:

//獲取activity的頂層視圖decor
View decor = r.window.getDecorView();
//讓decor顯示出來
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 (a.mVisibleFromClient) {
    a.mWindowAdded = true;
    //將decor放入WindowManager(WindowManagerImpl對象)中,這樣activity就顯示出來了
    wm.addView(decor, l);

wm.addView(decor, l)實際上就是調用WindowManagerImpladdView()方法將Activity的根窗口mDecor添加到窗口管理器中的,而WindowManagerImpladdView()方法又調用的是WindowManagerGlobal單例的addView()方法。接下來我們就分析WindowManagerGlobal.addView()

step2:WindowManagerGlobal.addView()

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

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }
            ...

            //創建一個視圖層次結構的頂部,ViewRootImpl實現所需視圖和窗口之間的協議。
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
        try {
            //將activity的根窗口mDecor添加到root中
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            ...
            throw e;
        }
    }
}

在這個方法中,首先創建了一個ViewRootImpl的對象root,然後調用root.setView(),將activity的根窗口視圖設置給root,下面我們看看ViewRootImpl類:

step3:ViewRootImpl.setView()

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {

    final IWindowSession mWindowSession;

    public ViewRootImpl(Context context, Display display) {
        mWindowSession = WindowManagerGlobal.getWindowSession();
        ...
    }

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

                int res; /* = WindowManagerImpl.ADD_OKAY; */
                //請求對應用程序窗口視圖的UI作第一次佈局,應用程序窗口的繪圖表面的創建過程
                requestLayout();
                ...
                try {
                    ...
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    ...
                    throw new RuntimeException("Adding window failed", e);
                }
                ...

            }
        }

    @Override
    public void requestLayout () {
        if (!mHandlingLayoutInLayoutRequest) {
            //檢查當前線程是否是主線程,對視圖的操作只能在UI線程中進行,否則拋異常
            checkThread();
            //應用程序進程的UI線程正在被請求執行一個UI佈局操作
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //Choreographer類型,用於發送消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
}

  ViewRootImpl實現了ViewParent,它並不是真正的View(沒有繼承View),它定義了頂層視圖的職責API,比如requestLayout()請求佈局等等,在setView()方法中,調用了requestLayout()方法,請求對Activity根窗口做第一次佈局。
  requestLayout()方法中首先調用checkThread()檢查當前線程是否爲UI線程,如果不是則拋出異常,然後調用scheduleTraversals()方法,scheduleTraversals()方法中調用了mChoreographer.postCallback()mChoreographer中維護了一個Handler,通過handler機制實現消息調度,並傳入一個回調mTracersalRunnablemTracersalRunnable的run方法中調用了doTraversal()方法,doTraversal()方法繼續調用performTraversals():

step4:ViewRootImpl.performTraversals()

private void performTraversals() {
    final View host = mView;   //mView就是Activity的根窗口mDecor
    ...
    //下面代碼主要確定Activity窗口的大小
    boolean windowSizeMayChange = false;
    //Activity窗口的寬度desiredWindowWidth和高度desiredWindowHeight
    int desiredWindowWidth;
    int desiredWindowHeight;
    ...
    //Activity窗口當前的寬度和高度是保存ViewRoot類的成員變量mWinFrame中的
    Rect frame = mWinFrame;
    ...
    /*
     * 接下來的兩段代碼都是在滿足下面的條件之一的情況下執行的:
     * 1. Activity窗口是第一次執行測量、佈局和繪製操作,即ViewRoot類的成員變量mFirst的值等於true
     * 2. 前面得到的變量windowShouldResize的值等於true,即Activity窗口的大小的確是發生了變化。
     * 3. 前面得到的變量insetsChanged的值等於true,即Activity窗口的內容區域邊襯發生了變化。
     * 4. Activity窗口的可見性發生了變化,即變量viewVisibilityChanged的值等於true。
     * 5. Activity窗口的屬性發生了變化,即變量params指向了一個WindowManager.LayoutParams對象。
     */
    if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null) {
        ...
        try {

            //★請求WindowManagerService服務計算Activity窗口的大小以及內容區域邊襯大小和可見區域邊襯大小
            relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
            ...
        } catch (RemoteException e) {
        }

        ...
        //將計算得到的Activity窗口的寬度和高度保存在ViewRoot類的成員變量mWidth和mHeight中
        if (mWidth != frame.width() || mHeight != frame.height()) {
            mWidth = frame.width();
            mHeight = frame.height();
        }
        ...

        if (!mStopped || mReportNextDraw) {
            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);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                // 一、測量控件大小(根窗口和其子控件樹)
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                int width = host.getMeasuredWidth();
                int height = host.getMeasuredHeight();
                boolean measureAgain = false;
                if (lp.horizontalWeight > 0.0f) {
                    width += (int) ((mWidth - width) * lp.horizontalWeight);
                    childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width,
                            View.MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (lp.verticalWeight > 0.0f) {
                    height += (int) ((mHeight - height) * lp.verticalWeight);
                    childHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
                            View.MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (measureAgain) {
                    // 一、測量控件大小(根窗口和其子控件樹)
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }

                layoutRequested = true;
            }
        }
    }else{
        ...
    }

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        //二、佈局過程
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ...
    }
    ...
    boolean skipDraw = false;
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
            viewVisibility != View.VISIBLE;

    if (!cancelDraw && !newSurface) {
        if (!skipDraw || mReportNextDraw) {
            ...
            //三、繪製過程
            performDraw();
        }
    } else {
        if (viewVisibility == View.VISIBLE) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            ...
        }
    }

    mIsInTraversal = false;
}

  performTraversals()方法中,分爲四部分,上面很大一段代碼主要作用是確定Activity窗口的大小,也就是通過各種判斷,是否是第一次?是否需要重新計算大小?最終確定Activity根窗口的大小並保存起來。第二步就是遍歷測量子控件的大小;第三步就是佈局;第四部就是繪製。對於Activity窗口大小的計算,這裏就不深入探討,有興趣可以看看。接下來我們重點分析後面三個步驟,也就是控件的測量、佈局、繪製三個過程。

step5: ViewRootImpl.performMeasure()控件測量過程

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

  performMeasure()方法中調用了mView.measure()方法,mView就是Activity的根窗口,他是DecorView類型(FrameLayout的子類),這個類定義在PhoneWindow類中,接下來我們看看DecorViewmeasure()方法(measure()方法是View中final修飾的方法,不能覆蓋):

step6: View.measure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    //如果View的mPrivateFlags的PFLAG_FORCE_LAYOUT位不等於0時,就表示當前視圖正在請求執行一次佈局操作;
    //或者當前的寬高約束條件不等於視圖上一次的約束條件時,需要重新測量View的大小。
    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        //首先清除所測量的維度標誌
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        ...

        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        //如果需要強制佈局操作,或者忽略測量歷史,將會調用onMeasure對控件進行一次測量
        //sIgnoreMeasureCache是一個boolean值,初始化爲sIgnoreMeasureCache = targetSdkVersion < KITKAT;
        //意思是如果Android版本低於19,每次都會調用onMeasure(),而19以上時sIgnoreMeasureCache==false
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            //調用onMeasure來真正執行測量寬度和高度的操作
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            ...
        }
        // 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) {
            throw new IllegalStateException("View with id " + getId() + ": "
                    + getClass().getName() + "#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
}

  View中有兩個關於測量的方法measure()onMeasure()measure()方法是final的,不能被重寫。measure()方法中調用了onMeasure()方法,onMeasure()方法在View中的默認實現是測量自身的大小,所以在我們自定義ViewGroup的時候,如果我們不重寫onMeasure()測量子控件的大小,子控件將會顯示不出來的。ViewGroup中沒有重寫onMeasure()方法,在ViewGroup的子類(LinearLayoutRelativeLayout等)中會根據容器自身的佈局特性,比如LinearLayout有兩種佈局方式,水平或者垂直,分別對onMeasure()方法進行不同的重寫。

  總結一下就是measure()方法不能被重寫,它會調用onMeasure()方法測量控件自身的大小,如果控件是一個容器,它需要重寫onMeasure()方法遍歷測量所有子控件的大小。還有當我們繼承View自定義控件時,在佈局文件中明明使用了wrap_content可結果還是填充父窗體,這是因爲ViewonMeasure()方法的默認實現,如果要按照我們的需求正確的測量控件大小也需要重寫onMeasure()方法。

  由於View.measure()中調用了onMeasure()方法,所以就調用到了DecorViewonMeasure ()方法,DecorView.onMeasure()中首先對寬高約束做了一些處理後,接着調用了super.onMeasure(),也就是FrameLayout中的onMeasure()方法:

step7: FrameLayout.onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //獲得一個視圖容器所包含的子視圖的個數
    int count = getChildCount();

    final boolean measureMatchParentChildren =
            View.MeasureSpec.getMode(widthMeasureSpec) != View.MeasureSpec.EXACTLY ||
                    View.MeasureSpec.getMode(heightMeasureSpec) != View.MeasureSpec.EXACTLY;
    //mMatchParentChildren用於緩存子控件中佈局參數設置爲填充父窗體的控件
    mMatchParentChildren.clear();

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

    //①、遍歷根窗口下所有的子控件,挨個測量大小
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //調用measureChildWithMargins來測量每一個子視圖的寬度和高度
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //由於FrameLayout的佈局特性(Z軸幀佈局,向上覆蓋),將子控件中,最大的寬高值記錄下來
            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);
                }
            }
        }
    }

    //②、設置FrameLayout的寬高
    //上面已經得到子控件中寬高的最大值,然後加上容器(FrameLayout)設置的padding值
    // 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());
    }
    //調用resolveSizeAndState()方法根據計算出的寬高值和寬高約束參數,計算出正確的寬高值;
    //然後調用setMeasuredDimension()方法設置當前容器的寬高值
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));

    //③、計算子控件中設置爲填充父窗體的控件的大小
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();

            final int childWidthMeasureSpec;
            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0, getMeasuredWidth()
                        - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                        - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
                        width, View.MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                                lp.leftMargin + lp.rightMargin,
                        lp.width);
            }

            final int childHeightMeasureSpec;
            if (lp.height == LayoutParams.MATCH_PARENT) {
                final int height = Math.max(0, getMeasuredHeight()
                        - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                        - lp.topMargin - lp.bottomMargin);
                childHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
                        height, View.MeasureSpec.EXACTLY);
            } else {
                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                                lp.topMargin + lp.bottomMargin,
                        lp.height);
            }

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

代碼中註釋已經寫的比較清楚了,簡單描述一遍,FrameLayoutonMeasure方法可以分爲三個步驟:
①. 遍歷所有子控件,並調用measureChildWithMargin()方法測量子控件的大小,measureChildWithMargin() 方法是從ViewGroup中繼承下來的,它會調用child.measure(),如果子控件又是一個容器就會繼續遍歷child的子控件測量,最後FrameLayout的所有子控件(包括子容器中的子控件…)都完成了測量;
②. 根據第①步中記錄的子控件寬高的最大值,然後加上padding值以及最小寬高的比較,最終確定FrameLayout容器的寬高;
③. 重新計算子控件寬高設置爲MATCH_PARENT(填充父窗體 )的寬高

  FrameLayoutonMeasure()方法執行完畢後,整個Activity窗體重所有的控件都完成了測量,這時調用view.getMeasuredWidth()/getMeasuredHeight()就可以獲取到控件測量的寬高值了。測量的部分到此就結束了,如果想進一步深入瞭解控件測量的相關知識可以參考 Android自定義View(三、深入解析控件測量onMeasure)。下面我們接着step4中的第二步performLayout()分析窗口的佈局過程。

step8:ViewRootImpl.performLayout()請求佈局過程

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 {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
 }

  ViewRootImpl.performLayout()方法中會調用mViewActivity根窗口mDecor)的layout()方法,爲窗口中所有的子控件安排顯示的位置,由於不同的容器有不同的佈局策略,所以在佈局之前首先要確定所有子控件的大小,才能適當的爲子控件安排位置,這就是爲什麼測量過程需要在佈局過程之前完成。接着我們看看DecorViewlayout()方法(layout方法繼承自View):

step9:View.layout()

public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    //記錄上一次佈局後的左上右下的座標值
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    //爲控件重新設置新的座標值,並判斷是否需要重新佈局
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //onLayout()方法在View中是一個空實現,各種容器需要重寫onLayout()方法,爲子控件佈局
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

  從上面的代碼中,我們注意到關於佈局有兩個重要的方法,View.layout()View.onLayout(),這兩個方法有什麼關係?各自的作用是什麼呢?他們都是定義在View中的,不同的是layout()方法中有很長一段實現的代碼,而onLayout()確實一個空的實現,裏面什麼事也沒做。
  首先我們要明確佈局的本質是什麼,佈局就是爲View設置四個座標值,這四個座標值保存在View的成員變量mLeft、mTop、mRight、mBottom中,方便View在繪製(onDraw)的時候知道應該在那個區域內繪製控件。而我們看到layout()方法中實際上就是爲這幾個成員變量賦值的,所以到底真正設置座標的是layout()方法,那onLayout()的作用是什麼呢?
  onLayout()都是由ViewGroup的子類實現的,他的作用就是確定容器中每個子控件的位置,由於不同的容器有不容的佈局策略,所以每個容器對onLayout()方法的實現都不同,onLayout()方法會遍歷容器中所有的子控件,然後計算他們左上右下的座標值,最後調用child.layout()方法爲子控件設置座標;由於layout()方法中又調用了onLayout()方法,如果子控件child也是一個容器,就會繼續爲它的子控件計算座標,如果child不是容器,onLayout()方法將什麼也不做,這樣下來,只要Activity根窗口mDecorlayout()方法執行完畢,窗口中所有的子容器、子控件都將完成佈局操作。

  其實佈局過程的調用方式和測量過程是一樣的,ViewGroup的子類都要重寫onMeasure()方法遍歷子控件調用他們的measure()方法,measure()方法又會調用onMeasure()方法,如果子控件是普通控件就完成了測量,如果是容器將會繼續遍歷其孫子控件。

繼續查看DecorView.onLayout()方法:

step10:DecorView .onLayout()

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    getOutsets(mOutsets);
    if (mOutsets.left > 0) {
        offsetLeftAndRight(-mOutsets.left);
    }
    if (mOutsets.top > 0) {
        offsetTopAndBottom(-mOutsets.top);
    }
}

  DecorViewFrameLayout的子類,FrameLayout又是ViewGroup的子類,FrameLayout重寫了onLayout()方法,DecorView也重寫了onLayout()方法,但是調用的是super.onLayout(),然後做了一些邊界判斷,下面我們看FrameLayout.onLayout()

step11:FrameLayout .onLayout()

@Override
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();

    //獲取padding值
    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    //遍歷子控件,爲其計算左上右下座標,由於不同容器的佈局特性,下面的計算過程都是根據容器的佈局特性計算的
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            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;

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

            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;
            }
            //調用其layout()方法爲子控件設置座標
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

  所有的佈局容器的onLayout方法都是一樣的流程,都是先遍歷子控件,然後計算子控件的座標,最後調用子控件的layout()方法設置佈局座標,但是不同的佈局容器有不同的佈局策略,所以區別就在於計算子控件座標時的差異。比如LinearLayout線性佈局,如果是水平佈局,第一個子控件的l值是0,r是100,那第二個子控件的l就是101(只是打個比方),而FrameLayout,如果沒有設置padding,子控件也沒設置margin,第一個子控件的l值就是0,第二個子控件的l還是0,這就是不同容器的計算區別。

  FrameLayout.onLayout()方法執行完畢後,整個Activity的根窗口的佈局過程也就完成了。接下來進入第三個過程–繪製過程:

step12:ViewRootImpl.performDraw()控件繪製過程

private void performDraw() {
    ...

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

    ...
}
private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;

    final Rect dirty = mDirty;
    ...

    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
            ...
            //使用硬件渲染
            mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
        } else {
            ...
            // 通過軟件渲染.
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }

    ...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                             boolean scalingRequired, Rect dirty) {
    final Canvas canvas;

    try {
        ...
        canvas = mSurface.lockCanvas(dirty);
        ...
    } catch (Surface.OutOfResourcesException e) {
        return false;
    } catch (IllegalArgumentException e) {
        return false;
    }
    ...
    try {
        ...
        try {
            ...
            mView.draw(canvas);
            ...
        } finally {
                ...
        }
    } finally {
       ...
    }
    return true;
}

  ViewRootImplperformDraw()方法調用draw(boolean),在這個過程中主要完成一些條件判斷,surface的設置準備,以及判斷使用硬件渲染還是軟件渲染等操作,由於我們主要研究繪製代碼流程層面,所以直接看drawSoftware()方法,對於硬件渲染具體是怎樣的有興趣可以跟蹤一下。drawSoftware()方法中,通過mSurface.locakCanvas(dirty)拿到畫布,然後調用mView.draw(canvas),這裏的mView就是Activity的根窗口DecorView類型的對象。

step13:DecorView.draw()

public void draw(Canvas canvas) {
    super.draw(canvas);

    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
}

  DecorView重寫了Viewdraw()方法,增加了繪製菜單背景的內容,因爲Activity根窗口上會有一些菜單按鈕(比如屏幕下方的返回鍵等),draw()方法中調用了super.draw(cancas),所以我們看看Viewdraw()方法:

step14:View.draw()

public void draw(Canvas canvas) {
    ...
    /*
     * 繪製遍歷執行幾個繪圖步驟,必須以適當的順序執行:
     * 1.繪製背景
     * 2.如果有必要,保存畫布的圖層,以準備失效
     * 3.繪製視圖的內容
     * 4.繪製子控件
     * 5.如果必要,繪製衰落邊緣和恢復層
     * 6.繪製裝飾(比如滾動條)
     */

    // Step 1, 繪製背景
    int saveCount;

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // 通常情況請跳過2和5步
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, 繪製本控件的內容
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, 繪製子控件
        dispatchDraw(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // we're done...
        return;
    }
    //下面的代碼是從第一步到第六步的完整流程
    ...
}

  Viewdraw()方法中有六個步驟,其中最爲重要的就是第1步繪製背景、第2步繪製內容、第3步繪製子控件;繪製背景我們就不看了,我們看看繪製內容,調用的是onDraw()方法,View中的onDraw()方法是一個空的實現,可以想象,因爲View是所有控件的超類,它並不知道他的孩子要怎樣畫自己,所以繪製的具體操作由孩子重寫onDraw()方法自己繪製,所以DecorView重寫了onDraw()方法來繪製Activity的跟窗口,具體怎麼畫的請跟蹤DecorViewonDraw()方法:

step15:DecorView.onDraw()

@Override
public void onDraw(Canvas c) {
    super.onDraw(c);
    mBackgroundFallback.draw(mContentRoot, c, mContentParent);
}

  mView(根窗口)把自己的內容繪製完成之後,緊接着開始遍歷繪製他的孩子,View中的dispatchDraw()也是一個空的實現,因爲超類的後代是不一定有孩子的(比如Button,TextView等),dispatchDraw()是在ViewGroup中實現的,我們看看ViewGroup.dispatchDraw():

step16:ViewGroup.dispatchDraw()

protected void dispatchDraw(Canvas canvas) {
    ...
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;

    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
        ...
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }
            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    }
    ...
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

  dispatchDraw()方法中首先獲取子控件的數量childrenCount,然後遍歷所有子控件,調用drawChild()方法,drawChild()方法中只是簡單的調用child.draw(),這樣就完成了子控件的繪製。細心的可以發現child的draw()方法又會執行之前DecorView.draw()的六步(draw()是在View裏面實現的),所以說,所有的控件在繪製的時候都會調用draw()方法,draw()方法中會先調用onDraw()方法繪製自己,然後調用dispatchDraw()繪製它的子控件,如果此控件不是ViewGroup的子類,也就是說是葉子控件,dispatchDraw()`什麼也不做。

  到此爲止,Activity的整個顯示過程就已經分析完畢,這篇文章主要講解了View測量、佈局、繪製流程,這三個流程一共涉及到六個重要的方法measure()onMeasure()layout()onLayout()draw()onDraw()onMeasure()onLayout()onDraw()一般都由View的子類(具體的控件或者佈局)重寫,measure()layout()draw()在View中有默認的實現,他們都是xxx()調用onXxx(),這種形式就能依次完成整個佈局樹的繪製流程。將這篇文章的流程整理如下圖:

        這裏寫圖片描述

參考源碼工程:

https://github.com/openXu/AndroidActivityLaunchProgress

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