作者:宋智孝的小迷弟
转载地址:https://juejin.cn/post/7088997543619035150
前言
在Android
中如果要显示一个页面,那么只需要在Activity
的onCreate()
方法中,把我们写好的页面布局layout
传入到setContentView()
方法中即可,这样Activity
就会完成接下来的工作,通过一系列的操作把我们想要的布局页面显示出来,不需要我们再做任何的处理,所以最初的时候一直以为是Activity
将layout
布局中的控件绘制出来的,但事实究竟是怎样的呢,我们来一探究竟。
首先,我们都知道在onCreate()
方法中调用setContentView()
函数,Activity
并不会立刻将页面显示出来,而是在执行到Activity
的onResume()
生命周期之后才会将Activity
显示出来。所以针对于这个问题 “View是如何绘制到屏幕上的?” ,可以从以下两个方面来进行思考。
- 调用
setContentView
之后都做了哪些事? - 在
onResume
这个生命周期里也就是Activity
被显示之前做了哪些事?
Ok,Let's go~
1、Activity的setContentView()
我们从Activity
的setContentView()
入手:

可以看到在Activity
的setContentView()
方法中,调用了getWindow()
的setContentView()
,Activity
在第一时间就将它交到了getWindow()
的手里,getWindow()
返回了一个Window
的对象,Window
是一个抽象类,PhoneWindow
是它唯一的实现类,并且从代码中我们可以得知mWindow
这个对象是在Activity
的attach()
方法中进行创建的。(attach()
这个方法很重要,很多跟Activity
相关的重要信息都是在这个方法中进行初始化的,尤其是初始化了和显示相关的信息,是我们需要重点关注的)此时我们的布局如下:

我们接着往下看,进入PhoneWindow
的setContentView()
中。

在phoneWindow
的setContentView()
中,主要做了两件事:
-
installDecor:如果
mContentParent
为null
,就会在这个方法中创建DecorView
和ContentParent
。 -
inflate:通过
inflate
的方式将我们在Activity
中传入的xml
布局文件也就是R.layout.activity_main
,转换成了树形结构的View
,并且把ContentParent
作为父节点。
installDecor

这里我们把重点部分用红框圈了起来,首先在installDecor()
方法中,通过generateDecor()
创建出了DecorView
,以及通过generateLayout()
创建出了ContentParent
。
-
generateDecor
:创建DecorView
,DecorView
继承自FrameLayout
,此时为我们的布局中创建出了DecorView
,布局如下:

-
generateLayout:创建
ContentParent
,在创建的过程中会根据不同的feature
创建不同的系统布局。
这里我们主要来了解一下ContentParent
的创建过程,首先会根据不同的的feature
创建不同的系统布局,这里的feature
较多,我们挑一个最简单也是最普遍的R.layout.screen_simple
为例,xml
文件代码如下:

R.layout.screen_simple
中是由ViewStub
和FrameLayout
两个部分组成,进入ViewStub
的layout
布局能够发现其实就是一个ActionBar
;这里注意下FrameLayout
的id
为content
,后面会用到。了解了screen_simple
中的内容后,接下来我们看mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)
这行代码,在onResourcesLoaded()
中先是将布局id
转换成View
,然后再通过addView
将此View
添加到DecorView
中。onResourcesLoaded()
代码如下:

在onResourcesloaded()
之后呢又通过findViewById(ID_ANDROID_CONTENT)
查找到了ContentParent
,并且将ContentParent
返回,这个ID_ANDROID_CONTENT
就是 com.android.internal.R.id.content
,也就是我们上面提到的R.layout.screen_simple
布局文件中id
为content
的FrameLayout
,所以最终将screen_simple
中的布局控件添加到DecorView
中之后,此时的布局如下:

此时generateLayout()
方法就结束了,我们再次返回到PhoneWindow
的setContentView()
方法中的第②
部分,可以看到通过inflate
的方式将我们从Activity
中传入的布局R.layout.activity_main
加载到了mContentParent
,而这个mContentParent
就是我们上一步通过generateLayout()
创建出来的ContentParent
。

所以将activity_main
添加到ContentParent
之后的布局如下:

至此,Activity
需要显示的内容已经被初始化完成了,但是此时Activity
并不是可见的,直到Activity
的onResume()
阶段才会将PhoneWindow
中的DecorView
真正的绘制到屏幕上,我们对setContentview()
进行一个总结,至于onResume()
的分析我们稍后继续。
setContentView总结:
在Activity
实例创建好并且执行attach()
方法的时候,会为Activity
创建一个PhoneWindow
,接下来就到了onCreate()
的生命周期,在调用setContentView()
的时候,如果还没有初始化ContentParent
,说明是第一次进行setContentView()
,那么就会初始化DecorView
,还会给DecorView
添加一个系统页面样式的子View(R.layout.screen_simple)
,那么在系统样式的ViewGroup
中,就可以通过id
找到用来加载自定义布局的ContentParent
,再通过inflate
就可以将我们自己写的xml
文件(R.layout.main)
转化为一颗ViewTree
了,这颗ViewTree
就在ContentParent
里面。
所以setContentView()
的作用最终可以总结为:
- 创建
DecorView
- 创建
ContentParent
- 自定义布局转化为
ViewTree
,放在ContentParent
中
接下来让我们继续了解一下Activity
的onResume()
。OK,Let's go~
2、Activity的onResume
Activity
的onResume()
生命周期是在ActivityThread
中的handleResumeActivity()
方法中执行的,在这个方法中通过performResumeActivity()
触发了onResume()
的回调。

可以看到触发onResume()
回调之后,在Activity
未关闭并且即将要显示的条件下,先是获取了Activity
的DecorView
,然后又获取了WindowManager
,最后呢再调用了WindowManger
的addView()
方法将DecorView
添加到WindowManager
中,我们来看下这个WindowManager
是在Activity
的attach()
中通过setWindowManager
创建出来的WindowManagerImpl
对象,每一个Activity
都会对应一个WindowManager
对象,一层一层的进入addView()
最终代码来到了WindowMangerGlobal
的addView()
,如下图所示:

WindowManagerGlobal
是一个单例,在它的addView()
方法中如果是首次添加的话就会创建了一个ViewRootImpl
,然后将DecorView
添加到ViewRootImpl
中。

在ViewRootImpl
的setView()
中,首先会调用requestLayout()
触发布局的绘制流程,我们熟悉的measure
,layout
,draw
的绘制流程就是从这里开始的,这一步可以确保View
被添加到屏幕上之前已经完成了测量和绘制操作。然后会调用mWindowSession
的addToDisplayAsUser()
通知WMS
添加窗口, 这里就涉及到了跨进程通信,方法的最后把decor
的parent
设置为了ViewRootImpl
,这样做的目的就是让ViewRootImpl
能够管理整个ViewTree
。 接下来我们就来看一下mWindowSession
这个对象。

mWindowSession
是WindowManagerGlobal
中的一个单例对象,它是IWindowSession
类型并且继承自IBinder
,它的实现类是System
进程中的Session
,可以看到它是通过WMS
进程的openSession
来获取的,至此剩下的工作就交由WMS
进程来进行后续的添加工作了。
onResume 总结:
在onResume()
中会调用WindowManager
中的addView()
添加DecorView
,当WindowManager
管理ViewTree
的时候会给ViewTree
分配一个ViewRootImpl
,ViewRootImpl
的第一个作用就是管理ViewTree
的绘制工作,包括显示、测量,同步刷新以及事件分发等等,第二个作用就是负责与其他的服务进行通信。在同时存在多个Activity
的情况下,每个Activity
都有自己的PhoneWindow
、DecorView
以及WindowManagerImpl
,WindowManagerGlobal
持有每个Activity
的RootView
,mWindowSession
和mWindow
是用来和WMS
进行双向通信的。
3、总结

网友评论