Android窗口显示过程分析3

98 阅读6分钟

我们来深入解析 Android窗口显示过程的阶段三:预测量 View 树与构建显示资源。这个阶段是连接 “窗口注册” 和 “正式绘制” 的关键桥梁,重点解决 “如何确定 UI 元素的大小” 和 “如何准备屏幕显示所需的底层资源” 两大问题。

一、阶段三的核心目标:从 “窗口存在” 到 “UI 可见” 的过渡

在阶段二完成窗口注册和尺寸预计算后,系统已经知道 “有一个窗口要显示,且大概占多大地方”。但此时窗口的 UI 内容(DecorView 及其子控件)还没确定具体尺寸,也没有分配用于绘制的 “画布”(Surface)。阶段三的主要任务是:

  1. 预测量 UI 元素的大小:根据预计算的窗口尺寸,先算一遍 DecorView 及其子控件的期望大小。
  2. 与窗口管理器(WMS)同步最终尺寸:WMS 可能因系统限制(如多窗口、权限)调整窗口尺寸,需要获取最终结果。
  3. 创建底层显示资源:生成 SurfaceControl 和 Layer,为后续绘制提供画布,并初始化缓冲区队列。

二、流程起点:requestLayout 触发 UI 更新流程

当 ViewRootImpl 的 setView 方法调用 requestLayout 时,相当于给 UI 系统发了一个 “我需要布局” 的信号。这个过程通过 Android 的 Choreographer(编舞者) 实现,确保 UI 更新与屏幕刷新(Vsync 信号)同步,避免画面撕裂:

  1. 注册同步屏障:通过 Looper 的消息队列插入同步屏障,优先处理布局任务。

  2. 申请 Vsync 信号:Choreographer 会在下次屏幕刷新周期开始时,触发 TraversalRunnable 回调,执行 doTraversal 方法。

通俗理解:这一步就像给 UI 线程安排了一个 “紧急任务”,告诉它 “等屏幕刷新信号来了,马上处理布局和绘制”。

三、核心方法 performTraversals:分阶段处理 UI 布局

performTraversals 是 ViewRootImpl 的核心方法,代码很长,但可以按 “关注点” 拆分成几个关键阶段:

1. 关注点 1:预测量 View 树(measureHierarchy)

  • 目标:用阶段二预计算的窗口尺寸(如屏幕大小),先算一遍 DecorView 的期望大小。

  • 过程

    • 从 mWinFrame 获取预计算的窗口宽度(如 1080px)和高度(如 2400px)。

    • 生成 MeasureSpec(测量规格),告诉 DecorView “父容器希望你多大”。MeasureSpec 有三种模式:

      • EXACTLY:必须用指定尺寸(如 match_parent 或具体数值)。
      • AT_MOST:最大不超过指定尺寸(如 wrap_content)。
      • UNSPECIFIED:无限制(很少用,系统内部使用)。
    • 调用 performMeasure,触发 DecorView 的 measure 方法,递归测量所有子控件,确定每个 View 的宽高,结果保存在 mMeasuredWidth 和 mMeasuredHeight 中。

举例:如果 Activity 是全屏,DecorView 的测量规格就是 EXACTLY 1080x2400,子控件(如 TextView、Button)会根据这个规格计算自己的位置和大小。

2. 关注点 2:布局窗口(relayoutWindow)

  • 目标:与 WMS 通信,获取窗口的最终尺寸(可能因系统调整而变化),并创建底层显示资源。

  • 过程

    • 通过 Binder 远程调用 WMS 的 relayout 方法,告诉 WMS“我已经预测量了 UI,现在需要最终布局”。

    • WMS 会根据系统状态(如是否有其他窗口遮挡、权限限制)调整窗口尺寸,返回最终的布局结果(如实际可用宽度可能因导航栏减少 20px)。

    • 创建 SurfaceControl 和 Layer:SurfaceControl 是 Surface 的管理者,Layer 是 WMS 中用于分层显示的单元,两者结合为窗口分配一块 “画布”,后续绘制的内容会填充到这里。

    • 初始化 BLASTBufferQueue:BLAST 是 Android 的缓冲区队列优化方案,用于管理图像缓冲区,提高渲染效率。

通俗理解:这一步是 “和系统确认最终方案”,WMS 作为管理者,可能会说 “你的窗口不能完全全屏,底部要留出导航栏的空间”,然后给你一块合适大小的画布。

3. 关注点 3:最终测量(performMeasure)

  • 目标:根据 WMS 返回的最终尺寸,重新测量 UI,确保 UI 适应实际窗口大小。
  • 为什么需要重新测量?
    预测量时用的是 “理想尺寸”(如全屏),但 WMS 可能返回 “实际尺寸”(如减去导航栏后的尺寸)。例如,预测量是 1080x2400,WMS 可能返回 1080x2200(底部留出 200px 导航栏),此时需要用 2200px 重新计算 DecorView 的高度,确保子控件不会超出窗口。

4. 其他关键步骤

  • 布局 View 树(performLayout) :确定每个控件的位置(如左上角坐标),基于最终测量的尺寸。
  • 绘制准备(performDraw) :将 UI 内容渲染到 Surface 上,为阶段五的显示做准备。

四、MeasureSpec:View 测量的 “规则说明书”

MeasureSpec 是理解测量过程的关键,它用一个 32 位整数封装了 “父容器对 View 的尺寸要求”:

  • 高 2 位:表示模式(EXACTLY/AT_MOST/UNSPECIFIED)。

  • 低 30 位:表示具体尺寸(像素值)。

举例:
如果父容器要求子 View “宽高必须为 100px”,MeasureSpec 就是EXACTLY | 100(二进制高 2 位是 01,低 30 位是 100)。
如果是 “宽高最大 100px”(wrap_content),就是AT_MOST | 100(高 2 位是 10,低 30 位是 100)。

View 的 measure 方法会根据 MeasureSpec 递归计算自身尺寸,例如:

  • 对于 EXACTLY 模式,直接使用指定尺寸。
  • 对于 AT_MOST 模式,取自身所需尺寸和指定尺寸的较小值。
  • 对于 UNSPECIFIED 模式,取自身最小尺寸(如背景图的最小宽高)。

五、阶段三的核心价值:承上启下的 “桥梁”

  1. 连接应用端与系统端:预测量基于应用需求(如全屏),最终测量基于系统限制(如导航栏),确保 UI 既符合预期又不违反系统规则。
  2. 准备显示资源:SurfaceControl 和 Layer 是图形系统的基础,后续的绘制(阶段四)和显示(阶段五)都依赖这些资源。
  3. 性能优化:通过预测量和缓存(如 mMeasureCache)减少重复计算,Vsync 机制确保 UI 更新与屏幕刷新同步,提升流畅度。

六、总结:阶段三做了什么?

  1. 预测量 UI:用预计算的窗口尺寸先算一遍控件大小,得到期望尺寸。

  2. 与 WMS 确认最终方案:获取调整后的窗口尺寸,创建 Surface 和 Layer,准备画布。

  3. 最终测量与布局:根据实际尺寸重新计算控件大小和位置,确保适应屏幕。

这一步就像 “装修前的二次测量”:先按理想方案设计(预测量),再根据实际房间尺寸(WMS 调整)修改设计图(最终测量),最后准备好装修材料(Surface 资源),为后续的 “装修施工”(绘制)做好准备。理解阶段三,就能明白 Android 如何在复杂的系统环境中,动态调整 UI 布局并高效利用显示资源。

参考资料