Android面試:從12個View繪製流程大廠面試真題入手,帶你全面理解View繪製流程

一、緣起

對於安卓開發來說,我想除了 Activity 以外,就是 View 接觸的最多了。這篇文章就以面試官的角度來講講 View 的一些知識點,看看問題是如何一層層的深入下去的。

作者:ZYLAB
鏈接:https://juejin.im/post/6872140986579943438

二、View 題目層次

我們以最常見的兩個面試題目(View 的繪製流程 和 View 的事件分發)開始,逐層深入去看一下。

先上 View 的繪製流程。

View 的繪製流程是 measure -> layout -> draw,這個大家都熟悉。

不過由此引申的知識點還是有不少的:

  1. 首次 View 的繪製流程是在什麼時候觸發的?
  2. ViewRootImpl 創建的時機?
  3. ViewRootImpl 和 DecorView 的關係是什麼?
  4. DecorView 的佈局是什麼樣的?
  5. DecorView 的創建時機?
  6. setContentView 的流程
  7. LayoutInflate 的流程
  8. Activity、PhoneWindow、DecorView、ViewRootImpl 的關係?
  9. PhoneWindow 的創建時機?
  10. 如何觸發重新繪製?
  11. requestLayout 和 invalidate 的流程
  12. requestLayout 和 invalidate 的區別

上面這些就是我想到的由 View 繪製流程引申的一系列問題,其實如果細想,還會有很多,這裏就作爲個引子。下面看看問題的詳解(以下代碼分析基於 SDK 28)。

如果上面的問題讀者朋友們都能回答上來,也就沒有必要往下看了~

三、題目詳解

1. 首次 View 的繪製流程是在什麼時候觸發的?

既然開始說到了 View 的繪製流程,那整個流程是什麼時候觸發的呢?

答案在 ActivityThread.handleResumeActivity 裏觸發的。

    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;
            // ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                  // ...
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
        // ...

    }

ActivityThread.handleResumeActivity 裏會調用 wm.addView 來添加 DecorView,wm 是 WindowManagerImpl

// WindowManagerImpl
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
// WindowManagerGlobal
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 這裏的 view 就是 DecorView
        // ...
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // ...
            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) {
            }
        }
    }
// ViewRootImpl.setView
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
      requestLayout();
    }

最終通過 WindowManagerImpl.addView -> WindowManagerGlobal.addView -> ViewRootImpl.setView -> ViewRootImpl.requestLayout 就觸發了第一次 View 的繪製。

2. ViewRootImpl 創建的時機?

從上面流程裏可以看到,ViewRootImpl 也是在 ActivityThread.handleResumeActivity 裏創建的。

3. ViewRootImpl 和 DecorView 的關係是什麼?

// ViewRootImpl.setView
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
      requestLayout();
      // ...
      // 這裏的 view 是 DecorView
      view.assignParent(this);
    }

接着上面的代碼看,在 ViewRootImpl.setView 裏,通過 DecorView.assignParent 把 ViewRootImpl 設置爲 DecorView 的 parent。

所以 ViewRootImpl 和 DecorView 的關係就是 ViewRootImpl 是 DecorView 的 parent。

因爲 DecorView 是我們佈局的頂層,現在我們就知道層層調用 requestLayout 等方法是怎麼調用到 ViewRootImpl 裏的了。

4. DecorView 的佈局是什麼樣的?

對於 Activity 的層級,大家應該都看過一張圖的描述,Activity -> PhoneWindow -> DecorView -> [title_bar, content],其中 DecorView 裏包括了 title_bar 和 content 兩個 View,不過這個是默認的佈局,實際上根據不同的主題樣式,DecorView 對應有不同的佈局。

圖中所包含的 title_bar 和 content 對應的是 R.layout.screen_simple 佈局。

那麼這麼多佈局,是在什麼時候設置的呢?

是在 PhoneWindow.installDecor -> generateLayout 中設置的。

// PhoneWindow
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 生成 DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor); // 生成 DecorView 子View
        }
    }

    protected ViewGroup generateLayout(DecorView decor) {
        // 根據不同的 window feature 給 DecorView 設置不同的佈局
        int layoutResource;
        int features = getLocalFeatures();
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } 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) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // 默認佈局
            layoutResource = R.layout.screen_simple;
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    }

// DecorView
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        // 根據 上一步選擇的 layout 生成 View
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            // 添加到 DecorView 裏
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

5. DecorView 的創建時機?

上面說 DecorView 佈局的時候,其實我們也看到了,在 PhoneWindow.installDecor -> generateDecor 其實就是創建 DecorView。

那 installDecor 是什麼時候調用的呢?

調用鏈是 Activity.setContentView -> PhoneWindow.setContentView -> installDecor

說到這裏那就繼續會想到,Activity.setContentView 的流程是什麼呢?

6. setContentView 的流程

setContentView 流程比較簡單,會調用 PhoneWindow.setContentView。

其中做的事情是兩個:

  1. 創建 DecorView
  2. 根據 layoutResId 創建 View 並添加到 DecorView 中
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
          // 創建 DecorView
            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 {
          // 根據 layoutResId 創建 ContentView
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

7. LayoutInflate 的流程

既然上一步用到了 LayoutInflate.inflate,那使用 LayoutInflate.inflate 加載一個佈局的流程是什麼樣的呢?

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        // 通過 resourceId 獲取 xml 佈局內容
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            // ...
            View result = root;
            try {
                // Look for the root node.
                int type;
                // 找到 xml start 或者 xml end
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                // 處理 merge 標籤
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    // merge 標籤傳入的 parent 是 rootView
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 通過 tag 創建 View
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        // 使用 rootView 默認的 LayoutParams
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }

                    // 創建子 View
                    rInflateChildren(parser, temp, attrs, true);

                    if (root != null && attachToRoot) {
                        // 添加到 rootView
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
            } finally {
            }
            return result;
        }
    }

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                // 處理 include 標籤
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                // 通過 xml 標籤生成 View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

    }

上面的流程可以看到,LayoutInflate.inflate 最終是調用 createViewFromTag 從 xml 生成 View 的,其實這裏纔是關鍵。

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        /** 如果是 view 標籤的話,就取其 class 屬性作爲 name
        * 比如 
        * <view class="LinearLayout"/>
        * 最終生成的會是一個 LinearLayout
        * 是不是又學會了一種 view 的寫法 ^_^
        */
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // 處理 blink 標籤
        if (name.equals(TAG_1995)) {
            return new BlinkLayout(context, attrs);
        }

        try {
          // 通過 mFactory2、mFactory、mPrivateFactory 創建 View
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            // 沒有設置 Factory,走默認的創建 View 的流程
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
        }
    }

這裏我們需要了解一下,mFactory、mFactory2、mPrivateFactory 都是什麼?

    private Factory mFactory;
    private Factory2 mFactory2;
    private Factory2 mPrivateFactory;

    public interface Factory {
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }

    public interface Factory2 extends Factory {
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }

mFactory、mFactory2、mPrivateFactory 分別對應 Factory 和 Factory2 方法,對應的是兩個 onCreateView 方法,Factory.onCreateView 沒有傳入 parent 參數,Factory2.onCreateView 傳入了 parent 參數。

而 mFactory 和 mFactory2 我們是可以設置的,當然不能重複設置,重複設置會拋出異常。

如果已經有 mFactory 的值,則生成一個 FactoryMerger,這個也是繼承了 Factory2,用來控制一下調用順序。

具體代碼如下

    public void setFactory(Factory factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = factory;
        } else {
            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
        }
    }

    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }

    private static class FactoryMerger implements Factory2 {
        private final Factory mF1, mF2;
        private final Factory2 mF12, mF22;

        FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
            mF1 = f1;
            mF2 = f2;
            mF12 = f12;
            mF22 = f22;
        }

        public View onCreateView(String name, Context context, AttributeSet attrs) {
            View v = mF1.onCreateView(name, context, attrs);
            if (v != null) return v;
            return mF2.onCreateView(name, context, attrs);
        }

        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
                    : mF1.onCreateView(name, context, attrs);
            if (v != null) return v;
            return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
                    : mF2.onCreateView(name, context, attrs);
        }
    }

然後我們再看 mPrivateFactory,看名稱就知道是系統的隱藏方法。

調用時機是在 Activity.attach 中,Activity 其實是實現了 Factory2 的 onCreateView 方法,其中對 fragment 做了處理,如果是 fragment 標籤,就調用 fragment 的 onCreateView,這裏就不詳細往下面看了,如果是非 fragment 的標籤,就返回 null,走默認的創建 View 的方法。

    /**
     * @hide for use by framework
     */
    public void setPrivateFactory(Factory2 factory) {
        if (mPrivateFactory == null) {
            mPrivateFactory = factory;
        } else {
            mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
        }
    }

// Activity
    final void attach(...)
        mWindow.getLayoutInflater().setPrivateFactory(this);
    }

    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return null;
    }

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return onCreateView(name, context, attrs);
        }

        return mFragments.onCreateView(parent, name, context, attrs);
    }

所以上面的 Factory 和 Factory2,是系統留給我們的 hook View 創建流程的接口。

如果都沒有設置,那就走到默認的創建 View 的方法。

默認創建 View 的方法比較簡單,就是反射調用 View 的構造函數,然後做一個緩存,然後創建 View。

具體代碼如下

// LayoutInflate
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
          // 前面的 mFactory、mFactory2、mPrivateFactory 都沒有去創建 View
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                  // 如果名稱裏沒有 “.”,也就是系統的 View,需要添加 android.view. 前綴,比如 <LinearLayout />,最終去創建的名稱是 android.view.LinearLayout
                    view = onCreateView(parent, name, attrs);
                } else {
                  // 如果是自定義 View,則直接去創建
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        // ...
    }

    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

        try {
            if (constructor == null) {
              // 加載對應的類
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                // 反射獲取構造函數
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                // 做個緩存,下次直接使用,提高效率
                sConstructorMap.put(name, constructor);
            } else {
            }

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            // 調用構造函數創建 View
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // 處理 ViewStub
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;
        } catch (NoSuchMethodException e) {
        }
    }

所以上面就是 LayoutInflate.inflate 的整個流程。

8. Activity、PhoneWindow、DecorView、ViewRootImpl 的關係?

其實上面的問題中,我們經常會說到 PhoneWindow 這個角色,PhoneWindow 其實是 Window 的唯一子類,是 Activity 和 View 交互系統的中間層,而 DecorView 是整個 View 層級的最頂層,ViewRootImpl 是 DecorView 的 parent,但是他並不是一個真正的 View,只是繼承了 ViewParent 接口,用來掌管 View 的各種事件,包括 requestLayout、invalidate、dispatchInputEvent 等等。

9. PhoneWindow 的創建時機?

既然上面又提到了 PhoneWindow,那麼 PhoneWindow 是什麼時候創建的呢?是在 Activity.attach 裏創建的,而 Activity.attach 又是在 ActivityThread.performLaunchActivity 裏創建的。

這裏就又能引申出 Activity 的啓動流程,這裏就先不講了。

10. 如何觸發重新繪製?

既然上面說到 View 的繪製流程,那我們怎麼觸發 View 的重新繪製呢?

就是調用 requestLayout 和 invalidate。

11. requestLayout 和 invalidate 的流程

requestLayout 流程

// View
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) { 
                  // 如果當前在 layout 流程中,並且是在處理 requestLayout,那麼就直接返回,這個時候需要注意,mPrivateFlags 並沒有設置 FORCE_LAYOUT
                  // 這個時候 reqeustLayout 會在下一個 frame 裏執行
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        // 如果當前在 layout 流程中,但是沒有處理 requestLayout,那麼就繼續後面的流程,這個時候 mPrivateFlags 是設置爲 FORCE_LAYOUT
        // 這個時候 requestLayout 會在下一次 layout 過程中進行執行

        // 設置 FORCE_LAYOUT 和 INVALIDETED flag
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            // 層層調用 parent 的 requestLayout
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

從上面代碼可以看到,會一層層調用 parent 的 requestLayout,而上面的問題中我們也分析到了,DecorView 是整個 View 層級的最頂層,ViewRootImpl 又是 DecorView 的 parent,所以最終調用到 ViewRootImpl 的 requestLayout。

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

ViewRootImpl.requestLayout 調用 scheduleTraversals -> doTraversal -> performTraversals 開啓繪製流程。

其實這裏又涉及到了 Choreographer 的一些流程,這裏也暫時不展開講了。

在 performTraversals 裏,就是熟悉的 performMeasure -> performLayout -> performDraw 三個流程了。

先看 performMeasure,最終調用的是 View.measure

// View
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      // 這裏就是 requestLayout 時設置的 flag,如果執行了 requestLayout,這裏 forceLayout 一定是 true
      final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
      // needsLayout 是 measureSpec 和 oldMeasureSpec 不相符的時候會爲 true
      if (forceLayout || needsLayout) {
        onMeasure(widthMeasureSpec, heightMeasureSpec);
      }
      // 設置 LAYOUT_REQUIRED flag,在 layout 中會用到
      mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

再看 performLayout

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        // 表明在 layout 流程中
        mInLayout = true;
        final View host = mView;
        try {
            // 先執行 layout
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            // 這裏處理在上一次 layout 過程中,調用了 requestLayout 的 View
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                // 獲取有效的需要 layout 的 View,此時獲取的是 mPrivateFlags == PFLAG_FORCE_LAYOUT 的 View,也就是在 View.requestLayout 裏設置了 PFLAG_FORCE_LAYOUT 的 View
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next
                    // frame instead
                    // 表明當前在處理 requestLayout 
                    mHandlingLayoutInLayoutRequest = true;

                    // Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        view.requestLayout();
                    }
                    // 執行 measure
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    // 執行 Layout
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    // Check the valid requests again, this time without checking/clearing the
                    // layout flags, since requests happening during the second pass get noop'd
                    // 獲取 mPrivateFlags != PFLAG_FORCE_LAYOUT 的 View,也就是在 View.requestLayout 裏沒有設置 PFLAG_FORCE_LAYOUT 的 View
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // 在下一次 frame 裏再執行一次 requestLayout
                        // 下一次 performTraversals 裏會執行 getRunQueue().executeActions(mAttachInfo.mHandler);
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }

            }
        } finally {
        }
        mInLayout = false;
    }

上面 performLayout 裏一共執行了三件事:

  1. 執行 View.layout
  2. 執行調用過 requestLayout 的 View 的 measure 和 layout
  3. 將還沒有執行的 requestLayout 加到隊列中,下一次 frame 中進行執行

然後看 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);

        // 如果位置有變化,或者設置了 PFLAG_LAYOUT_REQUIRED,PFLAG_LAYOUT_REQUIRED 是在 View.measure 結束以後設置的
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            // ...
            // 取消 flag
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            // ...
        }
        // 取消 flag
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
        // ...
    }

最後就是 ViewRootImpl.performDraw -> draw 了。

// ViewRootImpl
    private boolean draw(boolean fullRedrawNeeded) {
      // 有 dirty 區域會進行重繪
      if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            // 如果需要全部重繪,把 dirty 區域設置成 DecorView 的區域
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }
        // drawSoftware 調用了 DecorView.draw
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                scalingRequired, dirty, surfaceInsets)) {
            return false;
        }
      }
    }

// View
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        // flag 是 PFLAG_DIRTY_OPAQUE 則需要繪製
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        if (!dirtyOpaque) onDraw(canvas);
        // 繪製 Child
        dispatchDraw(canvas);
        // foreground 不管 dirtyOpaque 標誌,每次都會繪製
        onDrawForeground(canvas);
    }

在 View 的繪製過程中,我們可以看到,只有 flag 被設置爲 PFLAG_DIRTY_OPAQUE 纔會進行繪製(這裏劃重點)

這也就是大家經常說的 requestLayout 不會引發 draw。

invalidate 流程
invalidate -> invalidateInternal 的主要流程就是在設置 mPrivateFlags

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        // ...
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            // 設置 dirty flag
            mPrivateFlags |= PFLAG_DIRTY;

            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }
            // ...
        }
    }

invalidate 會調用 parent.invalidateChild

    public final void invalidateChild(View child, final Rect dirty) {
                  final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
                  // child 不透明的條件是沒有動畫且 child 本身是不透明的
            final boolean isOpaque = child.isOpaque() && !drawAnimation &&
                    child.getAnimation() == null && childMatrix.isIdentity();
            // 不透明的話使用的是 PFLAG_DIRTY_OPAQUE flag
            int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    // 設置 flag 爲 PFLAG_DIRTY_OPAQUE
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                }
                // 計算 parent 的 dirty 區域
                parent = parent.invalidateChildInParent(location, dirty);
            } while (parent != null);
    }

上面的 while 循環裏,會層層計算 parent 的 dirty 區域,最終會調用到 ViewRootImpl.invalidateChildInParent -> invalidateRectOnScreen

    private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
            mAttachInfo.mSetIgnoreDirtyState = true;
            mAttachInfo.mIgnoreDirtyState = true;
        }

        // Add the new dirty rect to the current one
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
        // Intersect with the bounds of the window to skip
        // updates that lie outside of the visible region
        final float appScale = mAttachInfo.mApplicationScale;
        final boolean intersected = localDirty.intersect(0, 0,
                (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        if (!intersected) {
            localDirty.setEmpty();
        }
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            // 調用 scheduleTraversals 進行整個繪製流程
            scheduleTraversals();
        }
    }

最終調用 scheduleTraversals 去觸發整個繪製流程,然後調用到 View.draw 方法,根據 PFLAG_DIRTY_OPAQUE flag 去決定是否重新繪製。

12. requestLayout 和 invalidate 的區別

看完上面的 requestLayout 和 invalidate 的流程,我們就能明白他們之間的區別了。

requestLayout 和 invalidate 都會觸發整個繪製流程。但是在 measure 和 layout 過程中,只會對 flag 設置爲 FORCE_LAYOUT 的情況進行重新測量和佈局,而 draw 只會重繪 flag 爲 dirty 的區域。

requestLayout 是用來設置 FORCE_LAYOUT 標誌,invalidate 用來設置 dirty 標誌。所以 requestLayout 只會觸發 measure 和 layout,invalidate 只會觸發 draw。

四、總結

上面就是我對 View 的繪製流程引申出的一些知識點的分析,當然並沒有列舉全,還有很多點可以去深入分析,只是提供一些思路。

這裏想說明的一點是,這些知識點並非沒有用,比如 LayoutInflate.inflate 的流程中,看到的 Factory2 的設置,在自定義 View 解析的時候就很有用(之前在騰訊的一個項目中就有用到)。

還想說明的是,面試官提問這些知識點,並非要全部答對,更多的是考察在工作中是否有深入瞭解,在深入瞭解源碼的過程中是否有一些自己的思考,畢竟瞭解原理是創新的基礎。

面試複習筆記:

這份資料我從春招開始,就會將各博客、論壇。網站上等優質的Android開發中高級面試題收集起來,然後全網尋找最優的解答方案。每一道面試題都是百分百的大廠面經真題+最優解答。包知識脈絡 + 諸多細節。
節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習。
給文章留個小贊,就可以免費領取啦~

戳我領取:Android對線暴打面試指南超硬核Android面試知識筆記3000頁Android開發者架構師核心知識筆記

《960頁Android開發筆記》

《1307頁Android開發面試寶典》

包含了騰訊、百度、小米、阿里、樂視、美團、58、獵豹、360、新浪、搜狐等一線互聯網公司面試被問到的題目。熟悉本文中列出的知識點會大大增加通過前兩輪技術面試的機率。

《507頁Android開發相關源碼解析》

只要是程序員,不管是Java還是Android,如果不去閱讀源碼,只看API文檔,那就只是停留於皮毛,這對我們知識體系的建立和完備以及實戰技術的提升都是不利的。

真正最能鍛鍊能力的便是直接去閱讀源碼,不僅限於閱讀各大系統源碼,還包括各種優秀的開源庫。

資料已經上傳在我的GitHub,或者關注後私信我【666】即可領取(無償)。

聽說一鍵三連的粉絲都面試成功了?如果本篇博客對你有幫助,請支持下小編哦

Android高級面試精選題、架構師進階實戰文檔傳送門:我的GitHub

整理不易,覺得有幫助的朋友可以幫忙點贊分享支持一下小編~

你的支持,我的動力;祝各位前程似錦,offer不斷!!!

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