Android面試題-來說一遍View的繪製流程

作爲一名android開發不管你幾年經驗,view的繪製流程熟記於心總少不了吧,今天帶大家走一遍,也給自己加深印象。

setContentView是我們用來給activity設置我們寫的佈局界面,我們就從這裏入手。

Activity#setContentView

@UnsupportedAppUsage
    private Window mWindow;

 public Window getWindow() {
        return mWindow;
    }

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

可以看到,在activity調用了mWindow.setContentView方法,而window是抽象類,所以我們得找它的子類PhoneWindow

PhoneWindow#setContentView

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

首先判斷mContentParent是否爲空,是會調用installDecor()方法做些初始化工作。然後再通過LayoutInflater將佈局文件加載到mContentParent上面去。

PhoneWindow#installDecor

private DecorView mDecor;
private ViewGroup mContentParent;
private ViewGroup mContentRoot;

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        mDecor.makeOptionalFitsSystemWindows();
        //...
    }
}

可以看到mDecor是DecorView類對象,而DecorView繼承自FrameLayout,generateDecor()方法就是初始化創建一個空的FrameLayout,generateLayout(mDecor) 用來初始化mContentParent。

PhoneWindow#generateLayout(mDecor)

protected ViewGroup generateLayout(DecorView decor) {   
    TypedArray a = getWindowStyle();
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        requestFeature(FEATURE_ACTION_BAR);
    }

    if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
        requestFeature(FEATURE_ACTION_BAR_OVERLAY);
    }
    //...
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        layoutResource = R.layout.screen_progress;

    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {}
    //...
    int layoutResource;
    //...
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

    mContentRoot = (ViewGroup) in;

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    //...
    return contentParent;
 }

可以看到,generateLayout()方法會根據我們Acivity主題樣式,選擇加載不同的系統佈局資源,並將該視圖添加到DecorView中去。

到這裏只是將佈局文件綁定到Activity  -->  mWindow -->  DecorView  -->  mContentParent 上,此時界面還未開始繪製。這時我們需要去到關鍵類ActivityThread類當中了。

ActivityThread#handleResumeActivity

@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        //...
        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;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            //...
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        //...
    }

可以看到調用了 WindowManager.addView()方法,而WindowManager是一個接口,也就是調用WindowManagerGlobl.addView()方法。

WindowManagerGlobl#addView():

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       //...
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

該方法中實例化了 ViewRootImpl,並且調用了ViewRootImpl.setView()方法來持有當前的DecorView。

ViewRootImpl#setView()

 


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

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

    //校驗所在線程,mThread是在ViewRootImpl初始化的時候執行mThread = Thread.currentThread()進行賦值的,也就是初始化ViewRootImpl所在的線程。
    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().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;
            }
        }
    }

可以看到,經過層層調用,最後調用到 ViewRootImpl.performTraversals();方法,這也就是view開始繪製的方法。

ViewRootImpl.performTraversals();

    private void performTraversals() {
        //...
        //開始進行佈局準備
        if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null) {
            //...
            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);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                   
                    //...
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;

                    //...

                    if (measureAgain) {
                        //View的測量
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
        } else {
            //...
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        //...
        
        if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                //...View的繪製
                performDraw();
            }
        } else {
            if (viewVisibility == View.VISIBLE) {
                // Try again
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;
    }

可以看到分別調用了 performMeasure() 、performLayout() 、performDraw() 方法,分別對應了View的measure()、layout()、draw()。根據根佈局的測量模式以及寬高生成對應的MeasureSpec,通過performMeasure()方法將父佈局的measureSpec傳遞給view.measure()。

MeasureSpec:

MeasureSpec是View的內部類,它封裝了一個View的尺寸,在onMeasure()當中會根據這個MeasureSpec的值來確定View的寬高。MeasureSpec的值保存在一個int值當中。一個int值有32位,前兩位表示模式mode後30位表示大小size。即MeasureSpec = mode + size。

在MeasureSpec當中一共存在三種mode:UNSPECIFIED、EXACTLY 和AT_MOST。

View#measure:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //關鍵方法...
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } 
            //...
    }

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

在該方法當中根據父佈局傳遞過來的MeasureSpec和自己的LayoutParmas生成自己的MeasureSpec,然後通過onMeasure()方法調用getDefaultSize() 拿到MeasureSpec存放的測量模式以及size,然後通過setMeasuredDimension() 來設置測量到的寬高,中間還調用了getSuggestedMinimumWidth()/getSuggestedMinimumHeight(),該方法判斷是否有背景,無背景則返回最小寬度或高度,有背景則返回背景的最小寬度或高度,只在測量模式爲UNSPECIFIED的時候設置此值。

這裏需要注意: view的MeasureSpec是由自己的LayoutParams父容器所給的MeasureSpec共同決定的。原因如下:

ViewGroup#getChildMeasureSpec()

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    ......
    switch (specMode) {
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                ......
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
                ......
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
                ......
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                ......
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
                ......
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
                ......
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                ......
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
                ......
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
                ......
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

 

父容器的specMode爲EXACTLY時,一切正常(子View尺寸指定爲match_parent或精確的dimen值時,Mode = EXACTLY,尺寸指定爲wrap_content則Mode = AT_MOST);

父容器specMode爲AT_MOST的時候,可以看到,除了指定了dimen值之外,無論設置爲match_parent或wrap_content,Mode最終都是會變成AT_MOST;

如果父容器specMode是UNSPECIFIED的話,都是會變成UNSPECIFIED的。

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(changed, l, t, r, b);
            //..
        }
        //...
    }

layout()過程,對於View來說用來計算View的位置參數,對於ViewGroup來說,除了要測量自身位置,還需要測量子View的位置。在layout()方法中已經通過setOpticalFrame(l, t, r, b)或 setFrame(l, t, r, b)方法對View自身的位置進行了設置,所以onLayout(changed, l, t, r, b)方法主要是ViewGroup對子View的位置進行計算viewGroup中需要重寫onLayout方法。

View#draw:

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        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;

        drawBackground(canvas);

        // 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
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(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);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }

        //...
        // Step 2, save the canvas' layers
        //....

        // Step 3, draw the content
        onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        //....

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

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    }

 

可以看到,註釋中draw方法內部有六個方法,其中第二和第五個方法不是必要的。當然了,方法中還註釋了第七個方法,都記住是最好的了。

1:drawBackground(canvas):作用就是繪製View的背景。

2:非必要方法 保存畫布的圖層。

3:onDraw(canvas):繪製View的內容。View的內容是根據自己需求自己繪製的,所以方法是一個空方法,View的繼承類自己複寫實現繪製內容。 canvas是從ViewRootImpl中的Surface傳遞進來的

4:dispatchDraw(canvas):遍歷子View進行繪製內容。在View裏面是一個空實現,ViewGroup裏面纔會有實現。View的繪製過程的傳遞通過dispatchDraw來實現的,dispatchDraw會遍歷調用所有子元素的draw方法,如此draw事件就一層層地傳遞了下去。在自定義ViewGroup一般不用複寫這個方法,因爲它在裏面的實現幫我們實現了子View的繪製過程,基本滿足需求。

5:非必要方法 繪製邊緣和恢復畫布的圖層。

6:onDrawForeground(canvas):繪製裝飾(前景色、滾動條)。

7:drawDefaultFocusHighlight(canvas):繪製默認焦點高亮。

 

還有一個地方不知道大家注意沒有,在ActivityThread.handleResumeActivity方法的最後,調用了 r.activity.makeVisible();  ,這也就是Activity在onResume()後可見的原因。

 

就是以上這些,大家一定要牢牢記住,將整個流程捋順了。

 

 

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