概述
上一章节,我们从 setContentView
为起点,阅读源码,熟悉了 view,Activity和Window
的关系。
大概可以总结如下:
Activity
中setContentView
实际上走到了Window
的setContentView
。Window
有唯一的实现类PhoneWindow
,在该类中,利用ViewRootImpl
实现了 创建我们指定的Layout
(包括测量布局绘制),以及将该Layout
挂载到DecorView
作为它的child
。- 其中涉及到了一个重要概念
WindowManagerService
(简称为WMS,系统级别的窗口管理服务),这个WMS
负责将我们的布局绘制到屏幕上。- 最后,如果屏幕上的布局要与用户手势发生交互,得手机屏幕的硬件层面先和
AndroidFramework
层进行socket
交互,然后WMS
得知了事件,最后通过binder
,告知了当前的app
进程,并将事件通过事件链表,传递给了 当前Activity
的DecorView
。- 最后执行了 我们很熟悉的
View.dispatchTouchEvent
将事件进行一步步下发。
重点关注ViewRootImpl,它 作为一个普通类,没有继承 View,Activity,Window
的任何一个,但是却联通了 View,Activity,Window
三者。在布局的渲染中起到了以下两个关键作用。
- 调用
requestLayout
方法,执行了View
的测量布局绘制,即View
的完整渲染流程。- 通过
binder
机制,将View
添加到Window
中
在ViewRootImpl
的作用下,我们的layout
才显示在屏幕上。
下面,详细关注以下ViewRootImpl
的源代码:
屏幕绘制
其中 requestLayout
就是在执行View
的渲染流程。
具体过程如下:
-
- 检查线程,其实就是检查是否为主线程,如果不是,就抛出异常。
-
- 将请求布局的参数
mLayoutRequestd
设置为true
。这个参数决定后续是否要执行measure,layout
等操作。
- 将请求布局的参数
-
- 执行
scheduleTraversals()
- 上图1处,利用
handler
,发送了一个syncBarrier
消息到queue
中。在looper
的next
获取消息时,如果发现没有target
的message
,则在一定时间内跳过同步消息,优先执行异步消息。这里调用此方法,保证UI
绘制优先执行。 - 上图2处,执行一个
postCallback
方法到 主线程的消息队列,发送到消息为:mTraversalRunnable
,具体如下:- 图中红框标记处,发送了一个异步类型的消息到主线程消息队列中。
mTraversalRunnable
的具体源代码为:- 就是在这个Runnable中,执行了 最为关键的
performTraversals()
, 这个方法中,执行了 View的完整渲染流程measure->layout->draw
.
- 就是在这个Runnable中,执行了 最为关键的
- 执行
ViewRootImpl 的 performTraversals()
源代码精简如下: 实际上也就对应了 measure->layout->draw
measureHierarchy
View
的测量是一个递归过程。递归执行了所有子view
的测量过程之后,最终决定了父的宽高。 那么递归的起源是哪里呢?猜到了吧,就是DecorView
。 我们Activity
上的最高级别的View
,就是DecorView
。
请看源代码:
红框内为 获取根部的MeasureSpec
,这里的 lp.width
和 lp.height
, 其实就是屏幕的宽高(Activity
默认全屏) 。然后将 获得到的 两个MeasureSpec
交给了 performMeasure()
.
上图中的 mView
就是DecorView
, mView.measure()
就是 递归测量的起点。
performLayout
也是一个递归调用。结论与 measure
的过程类似。都是 DecorView作为起点的 递归layout。
performDraw
图中1处,表示 开启了硬件加速,则使用 硬件渲染器来进行绘制。
图中2处,执行的是软件绘制。
ViewRootImpl 中有一个重要的元素:surface。它的核心功能就是 UI渲染。 ViewRootImpl会将 draw 方法中绘制的UI元素,绑定到 surface上。 如果说 Canvas是画版,那么 surface就是画布上的画纸。 Surface上的内容最终会传递给底层的 SurfaceFlinger 。 最终将surface中的内容合成并显示在 屏幕上。
软件绘制 drawSoftware
图中1处,就是执行了 DecorView的draw方法,将元素绘制到了 canvas上。 图中2处,unlock,其实就是将 surface上的内容提交给 SurfaceFlinger显示在屏幕上。
软件绘制和硬件绘制的区别是:
- 软件绘制 没有采用GPU,而全部是CPU完成绘制
- 硬件绘制,完全采用GPU
mView.draw()
- 图中1处,绘制背景
- 图-中2处,绘制自身内容
- 图中3处,对draw事件进行分发,递归执行子view的 draw事件。
关于硬件加速
硬件加速可以指定Application维度,Activity维度
或者 利用java代码设置到View维度。
之所以有这么多的维度划分,是因为并不是所有的2D绘制操作都支持硬件加速。比如如下draw方法:
使用了这些方法的自定义View,如果启用硬件加速,会导致程序运行不正常。
但是硬件加速能够提高 UI渲染的性能。 以下是 ViewRootImpl的draw
方法的部分代码:
硬件绘制的核心逻辑在 mThreadRender.draw方法中,源代码如下:
View的动态绘制
当我们自定义一个View
,需要它根据不同情况做出不同的动画效果时,那么一定离不开View
的invalidate
方法. 它是一个轻量级 的UI
刷新方式。 因为它大多数情况下只会执行 view
的draw
方法,而不会执行 measure
和 layout
。
invalidate
不会执行measure
的原因是: 要执行measure,必须设置 FLAG_FORCE_LAYOUT标志位。而 invalidate
没有设置这个标志位,所以不会执行。
invalidate
不会执行layout
的原因是:
当调用invalidate时,如果View的位置没有发生改变,那么View不会触发重新布局的操作。
如果要执行 measure
和 layout
,那么就应该执行 View.requestLayout()
.
invalidate 和 postInvalidate 的区别
前者只能在UI线程中使用,后者则可以在 子线程中使用。
postInvalidate
的源码如下:
其实就是利用 handler
发送了一个 MSG_INVALIDATE
到消息队列中。然后在 ActivityThread
的 mHandler中执行 view的 invalidate()
ViewRootImpl的 dispatchInvalidateDelay()
View的刷新操作 只能在UI线程,是因为有一个 checkThread,将当前线程与创建ViewRootImpl时的线程比较。 由于创建时,是在 UI线程,所以刷新操作也只能在 UI线程。
理论上,如果能在子线程中创建ViewRootImpl,那么也是能够在这个子线程中进行UI刷新操作的。
总结
- 本文介绍了ViewRootImpl是如何进行 View渲染的,其核心方法在 performTraversals,按顺序执行 measure-layout-draw
- 介绍了 软件绘制,硬件加速的区别
- 介绍了 view的两种刷新方式 invalidate / postInvalidate