View繪制流程(一)

View繪制流程(二)

最近在學習View的繪制流程,看了幾篇不錯的博客(ViewRootImpl的獨白,我不是一個View(布局篇)Android應用層View繪制流程與源碼分析)自己對照源碼,梳理了一遍。

相關類

  • Activity:一個Activity是一個應用程序組件,提供一個屏幕,用戶可以用來交互為了完成某項任務,例如撥號、拍照。
  • View:作為所有圖形的基類。
  • ViewGroup:對View繼承擴展為視圖容器類。
  • Window:它概括了Android窗口的基本屬性和基本功能。(抽象類)
  • PhoneWindow:Window的子類。
  • DecorView:界面的根View,PhoneWindow的內部類。
  • ViewRootImpl:ViewRoot是GUI管理系統與GUI呈現系統之間的橋梁。
  • WindowManangerService:簡稱WMS,它的作用是管理所有應用程序中的窗口,并用于管理用戶與這些窗口發生的的各種交互。

View樹的繪制流程是在ViewRootImpl類的performTraversals()方法開始的

ViewRootImpl簡介

ViewRootImpl是View中的最高層級,屬于所有View的根(但ViewRootImpl不是View,只是實現了ViewParent接口),實現了View和WindowManager之間的通信協議。

ViewRootImpl的初始化

WindowManager繼承ViewManger,從ViewManager這個類名來看就是用來對View類進行管理的,從ViewManager接口中的添加、更新、刪除View的方法也可以看出來WindowManager對View的管理。
WindowManagerImplWindowManager的實現類。WindowManagerImpl內部方法實現都是由代理類WindowManagerGlobal完成,而WindowManagerGlobal是一個單例,也就是一個進程中只有一個WindowManagerGlobal對象服務于所有頁面的View。

public final class WindowManagerGlobal {
    /*******部分代碼省略**********/
    //所有Window對象中的View
    private final ArrayList<View> mViews = new ArrayList<View>();
    //所有Window對象中的View所對應的ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //所有Window對象中的View所對應的布局參數
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
    /*******部分代碼省略**********/
}

WindowManagerGlobal在其內部存儲著ViewRootImplView實例的映射關系(順序存儲)。

在Activity的onResume之后,當前Activity的Window對象中的View會被添加在WindowManager中。

public final class ActivityThread {
    /*******部分代碼省略**********/
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        /*******部分代碼省略**********/
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        if (r != null) {
            /*******部分代碼省略**********/
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                //window的類型:一個應用窗口類型(所有的應用窗口類型都展現在最頂部)。
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //將decor添加在WindowManager中
                    wm.addView(decor, l);
                }
            /*******部分代碼省略**********/
        } else {
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(token, Activity.RESULT_CANCELED, null, false);
            } catch (RemoteException ex) {
            }
        }
    }
}
wm.addView(decor, l);方法的具體實現是在WindowManager的代理類WindowManagerGlobal
public final class WindowManagerGlobal {
    /*******部分代碼省略**********/
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
         /*******部分代碼省略**********/
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        //聲明ViwRootImpl
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }
            /*******部分代碼省略**********/
            //創建ViwRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            //將Window所對應的View、ViewRootImpl、LayoutParams順序添加在WindowManager中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
        try {
            //把將Window所對應的View設置給創建的ViewRootImpl
            //通過ViewRootImpl來更新界面并完成Window的添加過程。
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            /*******部分代碼省略**********/
        }
    }
}
創建ViewRootImpl實例后,將將Window所對應的ViewViewRootImplLayoutParams順序添加在WindowManager中,然后將Window所對應的View設置給創建的ViewRootImplroot.setView(view, wparams, panelParentView);
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    /*******部分代碼省略**********/
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                //ViewRootImpl成員變量view進行復制,以后操作的都是mView。
                mView = view;
                /*******部分代碼省略**********/
                //Window在添加完之前先進行一次布局,確保以后能再接受系統其它事件之后重新布局。
                //對View完成異步刷新,執行View的繪制方法。
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //將該Window添加到屏幕。
                    //mWindowSession實現了IWindowSession接口,它是Session的客戶端Binder對象.
                    //addToDisplay是一次AIDL的跨進程通信,通知WindowManagerService添加IWindow
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
                } catch (RemoteException e) {
                    /*******部分代碼省略**********/
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                /*******部分代碼省略**********/
                //設置當前View的mParent
                view.assignParent(this);
                /*******部分代碼省略**********/
            }
        }
    }
}
requestLayout(); 方法請求view繪制,其過程主要是在ViewRootImplperformTraversals方法中。
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    /*******部分代碼省略**********/
    //請求對界面進行布局
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    /*******部分代碼省略**********/
    //安排任務
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
        }
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    //做任務
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
            try {
                //執行任務
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

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

整個View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法(這個方法巨長)開始的,該方法做的執行過程主要是根據之前設置的狀態,判斷是否重新計算視圖大小(measure)、是否重新放置視圖的位置(layout)、以及是否重繪 (draw),其核心也就是通過判斷來選擇順序執行這三個方法。

private void performTraversals() {
        ......
        //最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來
        //lp.width和lp.height在創建ViewGroup實例時等于MATCH_PARENT
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ......
        performDraw();
        ......
 }

這里的最外層根視圖是DecorView,也就是mView,在WindowManagerGlobal 中的addview中傳遞過來的。

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 = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
        break;
        ......
    }
    return measureSpec;
}

該方法的是用來測Root View的。上面傳入參數后這個函數走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法組裝一個MeasureSpecMeasureSpecspecMode等于EXACTLYspecSize等于windowSize,也就是為何根視圖總是全屏的原因。

View的測量

ViewRootImpl調用performMeasure執行Window對應的View的測量。

  • ViewRootImpl的performMeasure;
  • DecorView(FrameLayout)的measure;
  • DecorView(FrameLayout)的onMeasure;
  • DecorView(FrameLayout)所有子View的measure
private fun performMeasure(childWidthMeasureSpec: Int, childHeightMeasureSpec: Int) {
    if (mView == null) {
        return
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure")
    try {
        //mView在Activity中為DecorView(FrameLayout)
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW)
    }
}
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    ......
    //final方法,子類不可重寫
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        //回調onMeasure()方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
    }
}

為整個View樹計算實際的大小,然后設置實際的高和寬,每個View控件的實際寬高都是由父視圖和自身決定的。實際的測量是在onMeasure方法進行,所以在View的子類需要重寫onMeasure方法,這是因為measure方法是final的,不允許重載,所以View子類只能通過重載onMeasure來實現自己的測量邏輯。

int widthMeasureSpec:他由兩部分組成,高2位表示MODE,定義在MeasureSpec類(View的內部類)中,有三種類型,MeasureSpec.EXACTLY表示確定大小, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不確定。低30位表示size,也就是父View的大小。對于系統Window類的DecorVIew對象Mode一般都為MeasureSpec.EXACTLY ,而size分別對應屏幕寬高。對于子View來說大小是由父View和子View共同決定的。

//View的onMeasure默認實現方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

onMeasure默認的實現僅僅調用了setMeasuredDimension,它對View的成員變量mMeasuredWidthmMeasuredHeight變量賦值,measure的主要目的就是對View樹中的每個View的mMeasuredWidthmMeasuredHeight進行賦值,所以一旦這兩個變量被賦值意味著該View的測量工作結束。

默認的尺寸大小即傳入的參數都是通過getDefaultSize返回的,我們就看一下該方法的實現。

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    //通過MeasureSpec解析獲取mode與size
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

specMode等于AT_MOSTEXACTLY就返回specSize

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

建議的最小寬度和高度都是由View的Background尺寸與通過設置View的miniXXX屬性共同決定的。也只有當Mode為MeasureSpec.UNSPECIFIED時才會使用該尺寸。

到此一次最基礎的元素View的measure過程就完成了。

View實際是嵌套的,而且measure是遞歸傳遞的,所以每個View都需要measure,能夠嵌套的View都是ViewGroup的子類,所以在ViewGroup中定義了measureChildren, measureChild, measureChildWithMargins方法來對子視圖進行測量,measureChildren內部實質只是循環調用measureChildmeasureChildmeasureChildWithMargins的區別就是是否把marginpadding也作為子視圖的大小。ViewGroup本身不調用measureChildWithMarginsmeasureChildren方法,由繼承類通過for循環調用此方法進行子View的測量。下面看一下ViewGroup中稍微復雜的measureChildWithMargins方法。

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    //獲取子視圖的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //調整MeasureSpec
    //通過這兩個參數以及本身的LayoutParams來共同決定子視圖的測量規則
    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方法,子View的measure中會回調子View的onMeasure方法
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

該方法就是對父視圖提供的measureSpec參數結合子視圖的LayoutParams參數進行了調整,然后再來調用child.measure()方法,具體通過方法getChildMeasureSpec來進行參數調整。

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剩余大小),若差值小于0直接返回0
    int size = Math.max(0, specSize - padding);
    //定義返回值存儲變量
    int resultSize = 0;
    int resultMode = 0;
    //依據當前Parent的Mode進行switch分支邏輯
    switch (specMode) {
    // Parent has imposed an exact size on us
    //默認Root View的Mode就是EXACTLY
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            //如果child的layout_wOrh屬性在xml或者java中給予具體大于等于0的數值
            //設置child的size為真實layout_wOrh屬性值,mode為EXACTLY
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //如果child的layout_wOrh屬性在xml或者java中給予MATCH_PARENT
            // Child wants to be our size. So be it.
            //設置child的size為size,mode為EXACTLY
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //如果child的layout_wOrh屬性在xml或者java中給予WRAP_CONTENT
            //設置child的size為size,mode為AT_MOST
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    ......
    //其他Mode分支類似
    }
    //將mode與size通過MeasureSpec方法整合為32位整數返回
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec的規則

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

最終決定View的measure大小是View的setMeasuredDimension方法,所以我們可以通過setMeasuredDimension設定死值來設置View的mMeasuredWidthmMeasuredHeight的大小,但是一個好的自定義View應該會根據子視圖的measureSpec來設置mMeasuredWidthmMeasuredHeight的大小,這樣的靈活性更大

View測量總結

ActivityonResume之后,當前ActivityWindow對象中的View(DecorView)會被添加在WindowManager中。也就是在ActivityThreadhandleResumeActivity方法中調用wm.addView(decor, l);將DecorView添加到WindowManager中;

WindowManager繼承ViewManager,它的實現類為WindowManagerImpl,該類中的方法的具體實現是由其代理類WindowManagerGlobal實現的;

在它的addView方法中會創建ViewRootImpl的實例,然后將Window對應的View(DecorView),ViewRootImpl,LayoutParams順序添加在WindowManager中,最后將Window所對應的View設置給創建的ViewRootImpl,通過ViewRootImpl來更新界面并完成Window的添加過程;

設置view調用的是ViewRootImplsetView方法,在該方法中調用requestLayout();方法來異步執行view的繪制方法;之后將Window添加到屏幕,通過WMS(跨進程通信)

requestLayout方法中最終會調用ViewRootImplperformTraversals();方法,該方法做的執行過程主要是根據之前設置的狀態,判斷是否重新計算視圖大小(measure)、是否重新放置視圖的位置(layout)、以及是否重繪 (draw),其核心也就是通過判斷來選擇順序執行這三個方法:performMeasureperformLayoutperformDraw;

performMeasure方法中調用的是Viewmeasure方法,該方法是final修飾,不能被子類重寫,在該方法中實際調用的是ViewonMeasure方法,子類可以重寫onMeasure方法來實現自己的測量規則。

View默認的onMeasure方法很簡單只是調用了setMeasuredDimension方法,該方法的作用是給View的成員變量mMeasuredWidthmMeasuredHeight賦值,View的測量主要就是給這兩個變量賦值,這兩個變量一旦賦值,也就意味著測量過程的結束。

setMeasuredDimension方法傳入的尺寸是通過getDefaultSize(int size, int measureSpec);方法返回的,在
getDefaultSize方法中解析measureSpecModeSize,如果Mode為MeasureSpec.AT_MOST或者MeasureSpec.EXACTLY,最終的size的值為解析后的size;如果ModeMeasureSpec.UNSPECIFIED,最終的size為建議的最小值=getSuggestedMinimumWidth,該方法的具體實現為return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());,建議的最小寬度和高度都是由View的Background尺寸與通過設置View的miniXXX屬性共同決定的

measureSpec是由getRootMeasureSpec方法決定的:measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);根布局的大小是Window的大小,Window大小是不能改變的,總是全屏的。

View實際是嵌套的,而且measure是遞歸傳遞的,所以每個View都需要measure,能夠嵌套的View都是ViewGroup的子類,所以在ViewGroup中定義了measureChildren, measureChild, measureChildWithMargins方法來對子視圖進行測量,measureChildren內部實質只是循環調用measureChildmeasureChildmeasureChildWithMargins的區別就是是否把marginpadding也作為子視圖的大小。

measureChildWithMargins方法的作用就是對父View提供的measureSpec參數結合子ViewLayoutParams參數進行了調整,然后再來調用child.measure()方法,具體通過方法getChildMeasureSpec方法來進行參數調整。計算出來自身的measureSpec作為調用其子視圖measure方法的參數,同時也作為自身調用setMeasuredDimension的參數,如果其不包含子視圖則默認情況下最終會調用onMeasure的默認實現,并最終調用到setMeasuredDimension

最終決定Viewmeasure大小是ViewsetMeasuredDimension方法,該方法就是設置mMeasuredWidth和mMeasuredHeight的大小,ViewGroup在onMeasure 方法調用setMeasuredDimension之前調整了measureSpec

Kotlin項目實戰

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容