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()后可见的原因。

 

就是以上这些,大家一定要牢牢记住,将整个流程捋顺了。

 

 

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