Android- analogy: 12 high-frequency interview questions for View drawing process, which will give you a comprehensive understanding of View drawing process

1. Origin

For Android development, I think in addition to Activity, View is the most exposed. This article will talk about some knowledge points of View from the perspective of the interviewer, and see how the problem goes deeper.

2. View topic level

Let's start with the two most common interview questions (View drawing process and View event distribution), and look at them layer by layer.

First go to the drawing process of View.

The drawing process of View is measure -> layout -> draw, which everyone is familiar with.

However, there are still many knowledge points derived from this:

  1. When was the first view drawing process triggered?
  2. When was the ViewRootImpl created?
  3. What is the relationship between ViewRootImpl and DecorView?
  4. What is the layout of DecorView?
  5. When was DecorView created?
  6. The flow of setContentView
  7. LayoutInflate process
  8. What is the relationship among Activity, PhoneWindow, DecorView, ViewRootImpl?
  9. When was PhoneWindow created?
  10. How to trigger a redraw?
  11. The flow of requestLayout and invalidate
  12. The difference between requestLayout and invalidate

The above are a series of questions that I thought of and derived from the View drawing process. In fact, if you think about it, there will be many more, so here is an introduction. Let's take a look at the detailed explanation of the problem (the following code analysis is based on SDK 28).

If readers and friends can answer the above question, there is no need to read it down~

Three, detailed questions

1. When was the first view drawing process triggered?

Now that we talked about the drawing process of View, when did the whole process trigger?

The answer is triggered in 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 will call wm.addView to add DecorView, wm is 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();
    }

Finally, through WindowManagerImpl.addView -> WindowManagerGlobal.addView -> ViewRootImpl.setView -> ViewRootImpl.requestLayout, the first view drawing is triggered.

2. When was the ViewRootImpl created?

As you can see from the above process, ViewRootImpl is also created in ActivityThread.handleResumeActivity.

3. What is the relationship between ViewRootImpl and DecorView?

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

Then look at the code above, in ViewRootImpl.setView, set ViewRootImpl as the parent of DecorView through DecorView.assignParent.

So the relationship between ViewRootImpl and DecorView is that ViewRootImpl is the parent of DecorView.

Because DecorView is the top layer of our layout, now we know how to call requestLayout and other methods layer by layer is called to ViewRootImpl.

4. What is the layout of DecorView?

For the level of Activity, everyone should have seen a description of the picture, Activity -> PhoneWindow -> DecorView -> [title_bar, content], where DecorView includes two views title_bar and content, but this is the default layout. In fact, DecorView has different layouts according to different theme styles.

The title_bar and content contained in the figure correspond to the R.layout.screen_simple layout.

So when are so many layouts set?

It is set in 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. When was DecorView created?

When we talked about the DecorView layout above, we actually saw that in PhoneWindow.installDecor -> generateDecor is actually to create DecorView.

When is installDecor called?

The call chain is Activity.setContentView -> PhoneWindow.setContentView -> installDecor

Having said that, I will continue to think, what is the process of Activity.setContentView?

6. The flow of setContentView

The setContentView process is relatively simple and will call PhoneWindow.setContentView.

Among them are two things:

  1. Create DecorView
  2. Create View based on layoutResId and add it to 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 process

Since LayoutInflate.inflate was used in the previous step, what is the process of loading a layout using 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);
            }
        }

    }

As you can see from the above process, LayoutInflate.inflate finally calls createViewFromTag to generate View from xml. In fact, this is the key.

    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) {
        }
    }

Here we need to understand, what are mFactory, mFactory2, and 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, and mPrivateFactory correspond to the Factory and Factory2 methods respectively, corresponding to the two onCreateView methods, Factory.onCreateView does not pass in the parent parameter, Factory2.onCreateView passes in the parent parameter.

We can set mFactory and mFactory2. Of course, they cannot be set repeatedly. Repeated settings will throw an exception.

If there is already a value of mFactory, a FactoryMerger is generated, which also inherits Factory2 to control the calling sequence.

The specific code is as follows

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

Then we look at mPrivateFactory again, and look at the name to know that it is the hidden method of the system.

The timing of the call is in Activity.attach. Activity actually implements the onCreateView method of Factory2, which processes the fragment. If it is a fragment tag, call the onCreateView of the fragment. I won’t read it in detail here. If it’s non-fragment. For the label, return null and follow the default method of creating 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);
    }

Therefore, the above Factory and Factory2 are the interfaces of the hook View creation process left by the system.

If none is set, then go to the default method of creating View.

The default method of creating a View is relatively simple, that is, reflectively calling the constructor of the View, then making a cache, and then creating the View.

The specific code is as follows

// 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) {
        }
    }

So the above is the entire process of LayoutInflate.inflate.

8. What is the relationship among Activity, PhoneWindow, DecorView, ViewRootImpl?

In fact, in the above questions, we often talk about the role of PhoneWindow. PhoneWindow is actually the only subclass of Window and the middle layer of the interaction system between Activity and View, while DecorView is the top level of the entire View hierarchy, and ViewRootImpl is the parent of DecorView. But it is not a real View, it just inherits the ViewParent interface and is used to manage various events of the View, including requestLayout, invalidate, dispatchInputEvent, and so on.

9. When is PhoneWindow created?

Since PhoneWindow is mentioned above, when was PhoneWindow created? It is created in Activity.attach, and Activity.attach is created in ActivityThread.performLaunchActivity.

Here we can extend the activation process of Activity again, so I won't talk about it here.

10. How to trigger a redraw?

Since the above mentioned the drawing process of View, how do we trigger the redrawing of View?

Is to call requestLayout and invalidate.

11. The flow of requestLayout and invalidate

requestLayout process

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

As you can see from the above code, requestLayout of parent will be called layer by layer, and we also analyzed in the above question, DecorView is the top level of the entire View hierarchy, ViewRootImpl is the parent of DecorView, so the requestLayout of ViewRootImpl is finally called.

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

ViewRootImpl.requestLayout calls scheduleTraversals -> doTraversal -> performTraversals to start the drawing process.

In fact, some processes of Choreographer are involved here, so I won't go into it for now.

In performTraversals, there are three familiar processes: performMeasure -> performLayout -> performDraw.

First look at performMeasure, the final call is 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;
    }

Look at performLayout again

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

Three things are performed in the above performLayout:

  1. Execute View.layout
  2. Execute the measure and layout of the View that called requestLayout
  3. Add the unexecuted requestLayout to the queue and execute it in the next frame

Then look at the flow of 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;
        // ...
    }

The last is 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);
    }

During the drawing process of View, we can see that only the flag is set to PFLAG_DIRTY_OPAQUE to draw (emphasis here) .

This is what people often say that requestLayout will not trigger draw.

invalidate process
invalidate -> invalidateInternal The main process is to set 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 will call 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);
    }

In the while loop above, the dirty area of ​​the parent will be calculated layer by layer, and will eventually be called to 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();
        }
    }

Eventually call scheduleTraversals to trigger the entire drawing process, and then call the View.draw method to decide whether to redraw according to the PFLAG_DIRTY_OPAQUE flag.

12. The difference between requestLayout and invalidate

After reading the above requestLayout and invalidate process, we can understand the difference between them.

Both requestLayout and invalidate will trigger the entire drawing process. But in the process of measure and layout, only the situation where flag is set to FORCE_LAYOUT will be remeasured and laid out, and draw will only redraw the area where flag is dirty.

requestLayout is used to set the FORCE_LAYOUT flag, and invalidate is used to set the dirty flag. So requestLayout will only trigger measure and layout, and invalidate will only trigger draw.

Four, summary

The above is my analysis of some of the knowledge points derived from the drawing process of View. Of course, I have not listed all of them. There are many points that can be analyzed in depth, just to provide some ideas.

The point I want to explain here is that these knowledge points are not useless. For example, in the process of LayoutInflate.inflate, the settings of Factory2 that you see are very useful when customizing View parsing (before it was useful in a Tencent project ).

I also want to explain that the interviewer does not have to answer all of these knowledge points when asking questions. It is more about examining whether he has a deep understanding at work and whether he has some thoughts in the process of understanding the source code. After all, understanding the principle is innovation. Foundation.

If you have any insights on the article, or any technical questions, you can leave a message in the comment area to discuss, and you will definitely reply.
Everyone is also welcome to come to my B station to play with me, video explanations of the advanced technical difficulties of various Android architects, Ren Junbai prostitutes ~
B station through train: https://space.bilibili.com/484587989

Friends who like the article, don’t forget to pay attention, leave a thumbs up and go, focus on the Android interview meow~

Guess you like

Origin blog.csdn.net/Androiddddd/article/details/109061564