谈谈Activity的View怎么与View绘制工具ViewRootImpl关联的

本文研究基于api24
上一篇谈谈Activity的setContentView是怎么加载XML视图的我们了解到了添加的布局文件中的View是如何被添加到Activity的窗口的,今天要解决的是添加到PhoneWindow的View是怎么与View绘制工具ViewRootImpl关联的

上一篇中有介绍到在PhoneWindw的generateLayout方法中最后有确定DecorView的内容和一个装载我们自己添加的布局的ViewGroup,其中的
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)
这个方法最终会走到ViewGroup的addView方法;而且generateLayout方法最后会调用mDecor.finishChanging()这个方法

public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
void finishChanging() {
        mChanging = false;
        drawableChanged();
}
private void drawableChanged() {
        if (mChanging) {
            return;
        }

        setPadding(mFramePadding.left + mBackgroundPadding.left,
                mFramePadding.top + mBackgroundPadding.top,
                mFramePadding.right + mBackgroundPadding.right,
                mFramePadding.bottom + mBackgroundPadding.bottom);
        requestLayout();
        invalidate();
 }

确定Activity的根布局后,最后回到PhoneWindow的setContentView方法时,几个重载方法里面的调用如下
mLayoutInflater.inflate(layoutResID, mContentParent);
或者mContentParent.addView(view, params);

这几个方法是真正把我们设置的布局内容添加到Activity的窗口里;同时这几个方法最终也会走到上面的addView方法。

很明显可以看到上述贴出的方法里有两句代码

requestLayout();
invalidate(true);

我们平时进行自定义View的开发的时候知道这两个方法的调用会引发View的重新布局和重新绘制,但是这里就这么调用了难道在这里就开始绘制View了?

感觉不对劲啊,Android开发者都知道View的绘制是由ViewParent(ViewRootImpl类是这个类的实现类,ViewParent是一个接口)去完成的,但是我们研究setContentView源码下来暂时没发现View跟这个ViewRootImpl产生关联啊,也就是没看到View拿到View的绘制工具,那问题就来了,那他们两是什么时候关联了呢?

既然正方向不好找,那就从反反向倒推吧,上面贴出的代码都会走requestLayout()方法,它是View类的方法

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

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

可以看到此时会调用到mParent.requestLayout(),由mParent去确定布局,这个mParent是什么呢,看定义

protected ViewParent mParent;

这个类就是View的绘制工具了,由ViewRootImpl实现,接下来在这个类里找它被初始化的地方,如下

void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }

找了一下发现View类里没有调用这个方法,那看看ViewGroup类,因为ViewGroup是View的子类;可以看到ViewGroup有调用

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }
        ....
        ....
        // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }
        ....
        ....
    }

继续查找这个addViewInner被谁调用了,最后是ViewGroup的addView方法调用的,好像又绕回来了,这不是死局吗,再理一下思路

  1. DecorView是Activity根布局,他添加一个layout的时候最终会调用ViewGroup的addView方法,其实DecorView就是ViewGroup的子类
  2. DecorView的layout中有一个FrameLayout是作为mContentParent用来添加我们设置的布局;添加的时候也会调用ViewGroup的addView方法,因为mContentParent就是ViewGroup
  3. ViewGroup最终会调用View的requestLayout去确定布局,但是ViewGroup也是View的子类,这样第一步和第二步产生的ViewGroup跳过中间环节就是走到了View里,并且View里会让ViewParent去完成最终的绘制工作
  4. 我们知道View的绘制是先绘制最外层父布局,然后循环迭代绘制里面的view,而绘制工作是由ViewParent完成,而它的实现类是ViewRootImpl,那结果就是DecorView使用ViewRootImpl去绘制视图,也就是整个Activity的视图交由ViewRootImpl去绘制
  5. DecorView的终极父类是View,那最终的目的就是要搞清View是什么时候和ViewRootImpl产生关联的
  6. 上面分析可知View持有ViewParent的引用,并且只有一处是ViewParent的初始化,也就是assignParent这个方法;那解决思路就是要找到这个方法是在哪里被调用以让View持有它的引用,用来绘制视图

assignParent被调用之处

这里涉及到Activity的启动相关知识,这一复杂流程就留在下一篇文章分析,这里抛出结果。
每个应用都有一个主线程,也就是main线程,所有的UI操作都要在主线程操作,而这个主线程就是ActivityThread,在这个类里有一个内部类H,继承自Handler,在里面的handleMessage方法中有下面这段代码

case RESUME_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
                            args.argi3, "RESUME_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

这里走到了handleResumeActivity这个方法

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
            return;
        }

        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            final Activity a = r.activity;
            ......
            ......
            ......
            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;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
                ...
                ...
                ...

    }

方法很长,省略了很多,留了关键的,可以看到在if (r.window == null && !a.mFinished && willBeVisible) 这个判断里

  1. 先通过 r.activity.getWindow()获取当前Activity的PhoneWindow,再通过PhoneWindow获取DecorView,然后把DecorView设置隐藏;其实这里也说明在这个handler的RESUME_ACTIVITY消息里,一开始Activity的View还没显示
  2. 通过a.getWindowManager()获取PhoneWindow的ViewManager;最后通过wm.addView(decor, l)把DecorView添加到了ViewManager
  3. ViewManager是个接口,子类是WindowManager,实现类是WindowManagerImpl

    我们找到WindowManagerImpl的addview方法

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

mGlobal是WindowManagerGlobal,继续到这个类去

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
          ...
          ....
          ....

        ViewRootImpl root;
        View panelParentView = null;
        ...
        ...
        ...
        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;
        }
    }

这个方法里new了一个ViewRootImpl实例,然后调用了它的setView方法,这个方法很长,省略如下

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

可以看到调用了View类的assignParent方法,其实这个view是前面传入的DecorView,到这里DecorView总算跟ViewRootImpl关联起来了

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/80927524
今日推荐