Android源码:View是如何绘制到屏幕上的?

  • Post author:
  • Post category:其他




前言



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()

入手:

1.png

可以看到在

Activity



setContentView()

方法中,调用了

getWindow()



setContentView()



Activity

在第一时间就将它交到了

getWindow()

的手里,

getWindow()

返回了一个

Window

的对象,

Window

是一个抽象类,

PhoneWindow

是它唯一的实现类,并且从代码中我们可以得知

mWindow

这个对象是在

Activity



attach()

方法中进行创建的。(

attach()

这个方法很重要,很多跟

Activity

相关的重要信息都是在这个方法中进行初始化的,尤其是初始化了和显示相关的信息,是我们需要重点关注的)此时我们的布局如下:

image.png
我们接着往下看,进入

PhoneWindow



setContentView()

中。

image.png



phoneWindow



setContentView()

中,主要做了两件事:


  • installDecor

    :如果

    mContentParent



    null

    ,就会在这个方法中创建

    DecorView



    ContentParent


  • inflate

    :通过

    inflate

    的方式将我们在

    Activity

    中传入的

    xml

    布局文件也就是

    R.layout.activity_main

    ,转换成了树形结构的

    View

    ,并且把

    ContentParent

    作为父节点。


installDecor

8.jpg

这里我们把重点部分用红框圈了起来,首先在

installDecor()

方法中,通过

generateDecor()

创建出了

DecorView

,以及通过

generateLayout()

创建出了

ContentParent


  • generateDecor

    :创建

    DecorView



    DecorView

    继承自

    FrameLayout

    ,此时为我们的布局中创建出了

    DecorView

    ,布局如下:

image.png


  • generateLayout

    :创建

    ContentParent

    ,在创建的过程中会根据不同的

    feature

    创建不同的系统布局。

这里我们主要来了解一下

ContentParent

的创建过程,首先会根据不同的的

feature

创建不同的系统布局,这里的

feature

较多,我们挑一个最简单也是最普遍的

R.layout.screen_simple

为例,

xml

文件代码如下:

9.png


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()

代码如下:

10.png



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

中之后,此时的布局如下:

image.png

此时

generateLayout()

方法就结束了,我们再次返回到

PhoneWindow



setContentView()

方法中的第



部分,可以看到通过

inflate

的方式将我们从

Activity

中传入的布局

R.layout.activity_main

加载到了

mContentParent

,而这个

mContentParent

就是我们上一步通过

generateLayout()

创建出来的

ContentParent


image.png

所以将

activity_main

添加到

ContentParent

之后的布局如下:

image.png

至此,

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()

的回调。

image.png

可以看到触发

onResume()

回调之后,在

Activity

未关闭并且即将要显示的条件下,先是获取了

Activity



DecorView

,然后又获取了

WindowManager

,最后呢再调用了

WindowManger



addView()

方法将

DecorView

添加到

WindowManager

中,我们来看下这个

WindowManager

是在

Activity



attach()

中通过

setWindowManager

创建出来的

WindowManagerImpl

对象,每一个

Activity

都会对应一个

WindowManager

对象,一层一层的进入

addView()

最终代码来到了

WindowMangerGlobal



addView()

,如下图所示:

5.png


WindowManagerGlobal

是一个单例,在它的

addView()

方法中如果是首次添加的话就会创建了一个

ViewRootImpl

,然后将

DecorView

添加到

ViewRootImpl

中。

6.png



ViewRootImpl



setView()

中,首先会调用

requestLayout()

触发布局的绘制流程,我们熟悉的

measure



layout



draw

的绘制流程就是从这里开始的,这一步可以确保

View

被添加到屏幕上之前已经完成了测量和绘制操作。然后会调用

mWindowSession



addToDisplayAsUser()

通知

WMS

添加窗口, 这里就涉及到了跨进程通信,方法的最后把

decor



parent

设置为了

ViewRootImpl

,这样做的目的就是让

ViewRootImpl

能够管理整个

ViewTree

。 接下来我们就来看一下

mWindowSession

这个对象。

7.png


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、总结

image.png



版权声明:本文为m0_62167422原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。