美文网首页
Window、DecorView、ViewRootImpl间的关

Window、DecorView、ViewRootImpl间的关

作者: code希必地 | 来源:发表于2020-09-10 14:02 被阅读0次

    Android视图的呈现,我们使用最多的就是在Activity的onCreate()中使用setContentView(int resId)将我们的布局文件显示出来。这里我们就从setContentView()说起。

    1、Window、DecorView的创建

     private Window mWindow;
     public void setContentView(@LayoutRes int layoutResID) {
            //调用Window的setContentView()
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    
    public Window getWindow() {
            return mWindow;
        }
    

    我们发现Activity.setContentView()最终是调用Window.setContentView(),那么mWindow是在哪里赋值的呢?
    Activity##attach()

    final void attach(Context context, ActivityThread aThread,
                Instrumentation instr, IBinder token, int ident,
                Application application, Intent intent, ActivityInfo info,
                CharSequence title, Activity parent, String id,
                NonConfigurationInstances lastNonConfigurationInstances,
                Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                Window window, ActivityConfigCallback activityConfigCallback) {
            //省略部分代码....
            mWindow = new PhoneWindow(this, window, activityConfigCallback);
            mWindow.setWindowControllerCallback(this);
            mWindow.setCallback(this);
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);
            //省略部分代码....
        }
    

    可以看出mWindow是在Activity的attach()方法中创建的,mWindow实际是PhoneWindow对象,PhoneWindow是Window的唯一实现类。
    系统会为每一个Activity创建一个Window对象与之对应。
    PhoneWindow##setContentView()

    @Override
        public void setContentView(int layoutResID) {
           //在Activity首次调用setContentView()时mContentParent =null
            if (mContentParent == null) {
                //初始化DecorView
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                //如果Activity没有设置过渡动画,在非首次调用setContentView时会走到这里,并移除mContentParent的所有子View。
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                view.setLayoutParams(params);
                final Scene newScene = new Scene(mContentParent, view);
                transitionTo(newScene);
            } else {
                //将资源文件转换成View树并添加到mContentParent中
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    这里看下installDecor()方法做了什么
    PhoneWIndow##installDecor()

    private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                //如果mDecorView为空则创建一个DecorView对象
                //generateDecor()里面的实现很简单就是new DecorView()
                mDecor = generateDecor(-1);
                //省略...
            }
            if (mContentParent == null) {
                //如果mContentParent 为null,则创建一个
                mContentParent = generateLayout(mDecor);
               //省略....
            }
        }
    

    installDecor()做了两件事:

    • 1、generateDecor(-1)中通过new DecorView()创建一个DecorView
    • 2、generateLayout(mDecor)创建mContentParent
      PhoneWindow##generateLayout(mDecor)
    protected ViewGroup generateLayout(DecorView decor) {
            //各种判断,根据主题来设置DecorView的样式  
            int layoutResource;
            int features = getLocalFeatures();
            //各种判断,根据不同的features来选择不同的布局文件layoutResource
            //将layoutResource添加到DecorView中
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
            //从DecorView中获取id为android.R.id.content的View即为contentParent
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
           //....
            return contentParent;
        }
    

    generateLayout()做了如下几件事:

    • 1、根据不同的主题来设置DecorView的样式。
    • 2、根据features 来选在添加到Decorview中的布局。

    这也是为什么requestWindowFeature(Window.FEATURE_NO_TITLE)需要在setContentView()前执行的原因。在PhoneWindow的setContentView()方法末尾会将mContentParentExplicitlySet赋值为true,在调用requestFeature()时,第一句就会判断mContentParentExplicitlySet,若为true则直接抛出异常。

    梳理一下Activity的setContentView()做了些什么事情:

    • 1、在Activity的attach()方法中创建PhoneWindow对象,可见每一个Activity都对应一个Window对象。
    • 2、Activity的setContentView()会调用PhoneWindow的setContentView()。在其中会创建DecorViewContentParent
    • 3、将Activity的布局文件添加到id为android.R.id.content的 ContentParent中。

    2、Window的管理者WindowManager

    上面我们讲了Window的创建、DecorView的创建以及DecorView子View的添加过程。每个Activity都有一个与之关联的Window,每个Window中也都有一个DecorView。那如何管理Window呢?这就需要WindowManager了,WindowManager管理Window实际上是在管理Window中的DecorView。
    下面就来看下DecorView是如何和WindowManager关联起来的。
    Activity的启动流程还是比较复杂的,我们跳过前面的步骤,直接从Activity的创建开始说起。
    Activity的创建是由ActivityThread完成的,在Activity创建完成后就开始调度Activity执行,具体流程如下图。

    Activity的调度.png

    这里我们重点分析下ActivityThread.handleResumeActivity()

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                String reason) {
            //调用Activity的onResume()
            final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
            if (r == null) {
                // We didn't actually resume the activity, so skipping any follow-up actions.
                return;
            }
    
            final Activity a = r.activity;
    
            //省略....
            if (r.window == null && !a.mFinished && willBeVisible) {
                //从Activity中获取Window,Window是在Activity的attach中创建的
                r.window = r.activity.getWindow();
                //获取Window中的DecorView,在PhoneWindow的installDecor()中创建
                View decor = r.window.getDecorView();
                //设置DecorView不可见
                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 (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //添加DecorView
                        wm.addView(decor, l);
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }
    
                // 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;
            }
    
            //......
             if (r.activity.mVisibleFromClient) {
                    //设置DecorView可见
                    r.activity.makeVisible();
                }
        }
    

    通过调用wm.addView(decor, l)WindowManager和DecorView就建立了联系。WindowManager是一个接口,其实现类是WindowManagerImpl,所以wm.addView(decor, l)最终会调用WindowManagerImpl.addView()
    WindowManagerImpl##addView()

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

    从代码中可以发现WindowManagerImpl虽然实现了WindowManager但是其中并没有实现添加View的操作,而是全部交给了WindowManagerGlobal去处理(不仅仅是addView(),WindowManager将所有View相关的操作都交给WindowManagerGlobal处理了)。
    WindowManagerGlobal##addView()

    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            //省略...
            ViewRootImpl root;
            View panelParentView = null;
    
            synchronized (mLock) {
                // 省略...
                //创建ViewRootImpl
                root = new ViewRootImpl(view.getContext(), display);
    
                view.setLayoutParams(wparams);
                //将DecorView、布局参数、ViewRootImpl保存在3个集合中
                //供后续操作时查询
                mViews.add(view);
                mRoots.add(root);
                mParams.add(wparams);
    
                // do this last because it fires off messages to start doing things
                try {
                    //这里的view就是DecorView
                    //将DecorView设置给ViewRootImpl
                    //然后由ViewRootImpl向WMS添加新的窗口、申请Surface以及在Surface上的重绘操作
                    //这才是真正意义上完成了窗口添加操作
                    root.setView(view, wparams, panelParentView);
                } catch (RuntimeException e) {
                    // BadTokenException or InvalidDisplayException, clean up.
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                    throw e;
                }
            }
        }
    
    • 1、创建ViewRootImpl,将DecorView、ViewRootImpl、布局参数缓存到三个集合中,供removeView()updateViewLayout()在操作中查询所用。
    • 2、WindowManagerGlobal.addView()的真正实现交给了ViewRootImpl去实现。WindowManagerGlobalremoveView()updateViewLayout()的真正操作也是由ViewRootImpl来完成的。
      这里先看下ViewRootImpl.setView()

    3、View的管理者ViewRootImpl

    ViewRootImpl##setView()

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    //将DecorView赋值给mView
                    mView = view;
                    //.....
                    //请求UI开始绘制
                    requestLayout();
                    //.....
                    try {
                        mOrigWindowType = mWindowAttributes.type;
                        mAttachInfo.mRecomputeGlobalAttributes = true;
                        collectViewAttributes();
                        //通知WindowManagerService添加一个窗口并注册事件监听
                        //监听按键、触摸事件
                        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                    }
                    //.....
                    //将ViewRootImpl赋值给DecorView的parent
                    //DecorView是View树的根View,ViewRootImpl不是View,但它是DecorView名义的parent
                    view.assignParent(this);
                    //....
                }
            }
        }
    

    setView()的工作流程:
    ViewRootImpl.setView()——>ViewRootImpl.requestLayout()——>ViewRootImpl.scheduleTraversals()——>收到屏幕刷新信号,发消息到主线程——>ViewRootImpl.doTraversal()——>ViewRootImpl.performTraversals()——>View树的绘制
    源码这里就不贴了具体自行查看。

    4、总结

    • 1、Window是视图的承载器,内部持有一个DecorView,而这个DecorView才是View的根布局。Window通过WindowManager将DecorView加载其中,并将DecorView交于ViewRootImpl,由ViewRootImpl控制视图的绘制以及事件的传递。
    • 2、DecorView是FrameLayout的子类,是View树的根布局。DecorView作为根节点,我们设置的布局文件就是添加到子控件(id为android.id.content的FrameLayout)中。ViewRootImp继承Viewparent,虽然不是View,但是DecorView名义上的Parent。
    • 3、ViewRootImpl是连接WindowManagerService和DecorView的桥梁。Window通过WindowManager添加DecorView实际上是ViewRootImpl通知WindowManagerService进行窗口添加的。ViewRootImpl虽然不在View树中,但它却是DecorView名义上的父视图,Android中的触屏事件、按键事件、View树的刷新都是由ViewRootImpl分发的。

    相关文章

      网友评论

          本文标题:Window、DecorView、ViewRootImpl间的关

          本文链接:https://www.haomeiwen.com/subject/eeweektx.html