前言
在
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、总结