Android笔记(四)DecorView & ViewRootImpl & Window

这三者之间的关系网络上有很多篇不错的blog。比如这篇。为了让自己更加熟悉View绘制的流程,在这里特意将这三者的关系好好梳理一番。
为了理清这三者的关系,还是先上一张图
这里写图片描述

  1. ViewRootImpl是ViewRoot的实现类,ViewRoot不是view,而是整个ViewTree的管理者。
  2. DecorView是整个ViewTree的根布局视图
  3. ViewRoot通过在Activity.attach()中将DecorView绑定到Activity对应的Window中,在addView时涉及到跨进程通信
  4. 这里的跨进程通信主要就是ViewRoot和WMS之间通信,通过传递IWindowSession和IWindow来完成。

按照个人理解,在window起来的时候,先完成的工作是得到一个最初的View,用作最底层的view(或者说放在Window里面作为根view,事实上不存在这种将view放置在window中的说法,因为window只是一个虚拟的窗口),这个view也就是我们通常说的DecorView。得到这个DecorView之后,接下来的工作就是通过addView将这个View呈现出来,这里才会涉及到ViewRootImpl,这样DecorView才算是真正与window绑定在一起了。

Window创建过程

PhoneWindow是Window的实现类,里面几个和DecorView相关的属性

ViewGroup mContentParent;//ViewGroup,也就是我们的R.id.content
TextView mTitleView;//也就是我们的R.id.title
DecorContentParent mDecorContentParent; //接口,用来设置一些window的属性

我们以Activity的启动创建Window为例来进行说明,除此之外Dialog、Toast等在启动时也会创建Window。
我们知道,在启动一个activity时,activity中会走到onCreate()方法中执行

setContentView(R.layout_main);

这个方法实际上做了下面的事情

getWindow().setContentView(R.layout.main);//getWindow()就是获取mWindow对象,
//这个对象怎么来的?说明在onCreate()之前就已经new PhoneWindow()了。
//但具体在哪儿,我们留作彩蛋一
initWindowDecorActionBar();

Activity的启动较为复杂,后面会专门花时间来讲解。Acitvity的启动最终都会由ActivityThread的handleLaunchActivity()来完成启动。(这里啰嗦一句,下面的代码已经是在Activity所在进程中执行的了,从AMS到Activity这之间的过程本文暂不分析,大致就是ApplicationThread作为媒介在AMS和Activity进程之间的交互了,最终在用户进程中执行下面这些操作)。
先是调用handleLaunchActivity(),再在里面执行performLaunchActivity()

//ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ``````
    //获取WindowManagerService的Binder引用(proxy端)。
    WindowManagerGlobal.initialize();

    //会调用Activity的onCreate,onStart,onResotreInstanceState方法,彩蛋二
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        ``````
        //会调用Activity的onResume方法.
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        ``````
    } 

}

其中performLaunchActivity中做的重要事情就是1.创建Activity;2.将activity.atttch();3在attach()中创建window对象

...
//通过反射创建Acitivity
ClassLoader cl = r.packageInfo.getClassLoader();
activity=mInstrumentation.newActivity(cl,component.getClassName(),r.intent);
...
//在activity中创建window对象
activity.attach(appContext,this,getInstrumentation(),r.token,..window);
...省略号

在Activity中的attach方法中,完成了 Activity中window的创建,而且设置相关回调接口,包括我们常见的onAttachedToWindow、dispatchTouchEvent()等。此外还将ActivityThread中的信息传递到Activity

attachBaseContext();
mWindow = new PhoneWindow(this,window);
mWindow.setCallback(this);
...
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;

这样我们就在启动一个Acitivity时,创建好了一个window。但此时window中啥都没有。但可以解释上面的彩蛋一。创建window到Activity执行onCreate()调用setContentView()之间经历了什么?
其实上面performLaunchActivity()的流程到了attach()之后,还有其他的操作,下面接着写attach()完之后performLaunchActivity()还做了啥

if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
...
if (!r.activity.mFinished) {
                    if (r.isPersistable()) {
                        if (r.state != null || r.persistentState != null) {
                            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                        }
                    } else if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
                }
...

原来是通过mInstrumentation间接执行了Activity中的onCreate(),onRestoreInstaneState()等方法,这样就可以解开彩蛋二的谜底了。此外,mInstrumentation是在上一步attach()中创建Window时创建的。

我们在activity中解析xml文件,添加view到window是怎样做到的呢。首先,我们需要想到的是,一个window对应一个根view。这个view就是DecorView。添加view的过程在setContentView()中完成。包括创建DecorView,添加view,显示view。
Activity中的setContentView()方法

public void setContentView(View view,ViewGroup.LayoutParams params){
    getWindow().setContentView(view,params);
    initWindowDecorActionBar();
}

Window的具体实现是PhoneWindow。因此我们只需要看PhoneWindow中的相关逻辑。

在PhoneWindow的setContentView方法中,如果没有DecorView,那就创建一个。

//mContentParent是DecorView的子布局ViewGroup,也就是我们的R.id.content
if(mContentParent = null){
    installDecor();
}

然后解析我们的xml或者加载view到mContentParent上,其中mLayoutInflater在PhoneWindow构造时被赋值

mLayoutInflater.inflate(layoutResID,mContentParent);

执行完上面这些,我们其实已经走过了Activity声明周期的onCreate() onStart()方法。此时我们已经

  • 在attach()方法中 创建了window
  • 在setContentView()中将view添加到了window。

完成这些后,还只是完成了Window的创建以及与Activity的绑定,还没有将Window添加到WMS中,所以还不会显示出来(mDecorView还没有被wms识别)。
让我们在次回到handleLaunchActivity方法中,发现上面的工作做了这么多,其实也还只是执行了performLaunchActivity(),而显示流程则需要接下来的handleResumeActivity()方法来完成。handleResumeActivity的核心代码如下

...
performResumeActivity();彩蛋三
...
if(a.mVisibleFromClient && !a.mWindowAdded){
    a.mWindowAdded = true;
    wm.addView(decor,l);
}
...

从方法命名我们大概可以猜出,performResumeActivity()中一定是执行了mInstrumentation.callActivityOnResume()操作,从而回调Activity中的onResume()方法显示。完成这后,后面会做一个很重要的操作,就是

wm.addView(decor,l);//有木有很熟悉,我们自己创建Window的时候
//是不是也是这样子直接wm.addView(view,l);

这样我们就把根布局”作为一个窗口”加载到了windowManager中.
到了这一步之后,window总算是给添加到WMS了。要分析WMS是如何添加window的,就得硬着头皮往WMS中分析了。从继承关系我们得知addView操作最终执行的是

//WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

接着往里看

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //创建ViewRootImpl,作为WMS管理view/window的桥梁
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);    
    root.setView(view, wparams, panelParentView);//最终的显示靠这个,彩蛋4
    ...
}

ViewRootImpl中持有WMS端的Session代理对象

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    //获取IWindowSession的代理类【见小节2.8.1】
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mDisplay = display;
    mThread = Thread.currentThread(); //主线程
    mWindow = new W(this); 
    mChoreographer = Choreographer.getInstance();
    ...
}

最终的显示要靠root.setView()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  synchronized (this) {
    ...
    //通过Binder调用,进入system进程的Session
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
          getHostVisibility(), mDisplay.getDisplayId(),
          mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
          mAttachInfo.mOutsets, mInputChannel);
    ...
  }
}

进一步就是WMS的Display部分了,这里不做更深的研究。就此打住!!!

DecorView介绍

DecorView为顶级View,事实上也具有普通View的特性,其布局为FrameLayout布局。以前的版本中,DecorView是PhoneWindow的内部类,PhoneWindow是Window抽象类的唯一实现,足以表示两者的关系。不过最新的版本中DecorView已经独立成了一个普通类,里面有三个最重要的属性
这里写图片描述

  • mWindow 窗口,后续加载content内容用到

  • mContentRoot 用于放置R.id.content的ViewGroup

  • mDecorCaptionView 和多窗口相关

这样我们就清楚地知道了DecorView里面的布局。对于普通Activity这两这个子View(mContentRoot mDecorCaptionView都有,对于没有多窗口的,则只有mContentRoot)。将view添加到DecorView主要是靠下面这个方法onResourceLoaded()
这里写图片描述

layoutResource为我们传进来的R.layout.xxx资源。先创建mDecorCaptionView
这里写图片描述
第1992行清楚地向我们显示了DecorCaptionView的加载。再回到上一幅图,加载完DecorCaptionView之后就加载mContentRoot(01919行所示),然后需要做的事情就是先判断mDecorCaptionView是否为空,如果为空则只把mContentRoot直接添加到DecorView中。如果不为空则先把mDecorCaptionView添加到DecorView,然后把mContentRoot添加到mDecorCaptionView中。大致的图如下
这里写图片描述
到这里我们就完成了整个DecorView的构造,下面就要进行其放置过程。事实上,DecorView的构造是在PhoneWindow中进行的。大概流程为:Activity中的setContentView最终会调用到PhoneWindow中的setContentView。在这个方法中首先就要判断DecorView是否存在,不存在就创建。DecorView的添加是在ActivityThread中的handlerResumeActivity添加的。

wm.addView(decor,l);

从这儿就可以看出Activity中onCreate在onResume方法之前,先创建DecorView,然后放置DecorView。WindowManager是一个抽象类,我们需要在其实现类WindowManagerImpl中去查看addview方法。结果发现真正调用的是WindowManagerGlobal类中的addView方法
这里写图片描述
也就是说,RootViewImpl和DecorView上下文一一对应。
这里写图片描述
从上面可以看出真正的添加DecorView操作是在这儿,调用ViewRootImpl.setView方法,在这个方法内部,会通过跨进程的方式向WMS(WindowManagerService)发起一个调用,从而将DecorView最终添加到Window上,在这个过程中,ViewRootImpl、DecorView和WMS会彼此关联,至于详细过程这里不展开来说了。完成了

  • mViews.add(view);
  • mRoots.add(root);
  • mParams.add(wparams)
  • root.setView()
    之后,会在root.setView()中调用requestLayout()开始遍历

最后通过WMS调用ViewRootImpl#performTraverals方法开始View的测量、布局、绘制流程。
后面将会讲解ViewTree遍历的时机

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值