二十一、View绘制的核心流程

概述

上一章节,我们从 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三者。在布局的渲染中起到了以下两个关键作用。

  1. 调用 requestLayout方法,执行了View的测量布局绘制,即View的完整渲染流程。
  2. 通过binder机制,将View添加到Window中

在ViewRootImpl的作用下,我们的layout才显示在屏幕上。

下面,详细关注以下ViewRootImpl的源代码:

屏幕绘制

其中 requestLayout 就是在执行View的渲染流程。

具体过程如下:

    1. 检查线程,其实就是检查是否为主线程,如果不是,就抛出异常。
    1. 将请求布局的参数 mLayoutRequestd 设置为 true。这个参数决定后续是否要执行 measure,layout等操作。
    1. 执行 scheduleTraversals()
    • 上图1处,利用handler,发送了一个 syncBarrier消息到queue中。在looper的next获取消息时,如果发现没有target的message,则在一定时间内跳过同步消息,优先执行异步消息。这里调用此方法,保证 UI绘制优先执行。
    • 上图2处,执行一个 postCallback方法到 主线程的消息队列,发送到消息为:mTraversalRunnable,具体如下:
      • 图中红框标记处,发送了一个异步类型的消息到主线程消息队列中。
      • mTraversalRunnable的具体源代码为:
        • 就是在这个Runnable中,执行了 最为关键的 performTraversals(), 这个方法中,执行了 View的完整渲染流程 measure->layout->draw.

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
相关推荐
怕浪猫9 小时前
外包和大厂的前端代码,差在凌晨三点
前端·面试·github
十年Java程序媛15 小时前
Java this 与 super 详解|继承高频踩坑 + 面试题整理
java·spring boot·面试
萧瑟余晖15 小时前
Spring Cloud配置中心与动态刷新详解
spring cloud·面试
白露与泡影17 小时前
一线 Java 后端面试八股(2026 更新版,含完整答案)
java·开发语言·面试
学到老活到老21 小时前
集合换成线程安全的,就不用加锁了吗?
java·面试
萧瑟余晖1 天前
Spring Cloud Alibaba生态整合详解
spring·spring cloud·面试
Sailing1 天前
【封闭结束2】Agent 意图识别:从语义理解到稳定路由
人工智能·面试·agent
Sailing1 天前
【封闭结束1】端到端交付:Agent 一次请求如何走完全链路
人工智能·面试·agent
怕浪猫1 天前
红利窗口开着,程序员别在场边囤课
面试·github·ai编程
lll23526917792 天前
HR如何快速整理面试沟通内容?
面试·职场和发展