一文带你吃透Android View绘制流程与原理详解

632 阅读4分钟

Android 中 View 的绘制流程是 UI 框架的核心机制,主要分为 Measure(测量)、Layout(布局)、Draw(绘制)  三个阶段。以下是详细的流程和实现原理分析:

一、绘制流程总览

View 的绘制流程由 ViewRootImpl(Android 3.0+)触发,通过 performTraversals() 方法协调以下三个阶段:

// ViewRootImpl.java
private void performTraversals() {
    performMeasure();  // 测量
    performLayout();   // 布局
    performDraw();      // 绘制
}

二、Measure 阶段:确定 View 的尺寸

1. 核心方法

  • measure(int widthMeasureSpec, int heightMeasureSpec)
    由父 View 调用,触发子 View 的测量逻辑。
  • onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    自定义 View 需重写此方法,确定自身尺寸。

2. MeasureSpec

  • 组成:32位 int 值,高 2 位表示 Mode(测量模式),低 30 位表示 Size

  • 三种模式

    • EXACTLY:精确尺寸(如 match_parent 或具体数值)。
    • AT_MOST:最大尺寸(如 wrap_content)。
    • UNSPECIFIED:未指定(如 ScrollView 的子 View)。

3. 流程细节

  • 父 View 通过 measureChild() 计算子 View 的 MeasureSpec
  • ViewGroup 需递归测量所有子 View(如 LinearLayout 根据方向计算子 View 尺寸)。
  • 最终通过 setMeasuredDimension() 保存测量结果。

关键问题

  • wrap_content 为何需要特殊处理?
    默认 onMeasure 不会处理 AT_MOST 模式,需要开发者手动设置尺寸。

三、Layout 阶段:确定 View 的位置

1. 核心方法

  • layout(int l, int t, int r, int b)
    由父 View 调用,确定子 View 的位置。
  • onLayout(boolean changed, int l, int t, int r, int b)
    ViewGroup 需重写此方法,排列子 View(如 FrameLayout 默认堆叠子 View)。

2. 流程细节

  • 父 View 根据子 View 的测量结果,计算其左上右下坐标。
  • 调用子 View 的 layout() 方法,触发其 onLayout()(仅 ViewGroup 需要实现)。
  • 保存位置信息到 mLeftmTopmRightmBottom

常见场景

  • RelativeLayout 需要两次测量(依赖关系复杂)。
  • 自定义 ViewGroup 需手动计算子 View 位置。

四、Draw 阶段:绘制内容

1. 核心方法

  • draw(Canvas canvas)
    总控绘制流程,内部调用以下步骤:
    1. 绘制背景drawBackground()
    2. 绘制自身内容onDraw(Canvas canvas)
    3. 绘制子 ViewdispatchDraw(Canvas canvas)
    4. 绘制装饰(如滚动条)onDrawForeground()

2. 关键点

  • onDraw() :自定义 View 在此绘制内容(如文本、图形)。
  • dispatchDraw() :ViewGroup 通过此方法分发绘制到子 View。
  • 硬件加速:Android 4.0+ 默认开启,使用 GPU 优化绘制(需注意 API 兼容性)。

优化技巧

  • 避免在 onDraw() 中创建对象(频繁调用导致 GC)。
  • 使用 Canvas.clipRect() 减少过度绘制。

五、实现原理与底层机制

1. ViewRootImpl 与 Choreographer

  • ViewRootImpl:连接 WindowManager 和 DecorView 的桥梁,通过 performTraversals() 触发绘制。

  • Choreographer:协调 VSYNC 信号(垂直同步),确保绘制按帧率(如 60Hz)执行。

    • 收到 VSYNC 后,通过 Handler 触发 doFrame(),进而执行 performTraversals()

2. 消息循环机制

  • UI 线程通过 Handler 处理 MessageQueue 中的绘制任务。
  • invalidate() 和 requestLayout() 会向队列插入任务,触发重绘。

3. 双缓冲与 SurfaceFlinger

  • 双缓冲:Surface 包含一个 Front Buffer(显示)和一个 Back Buffer(绘制),减少画面撕裂。
  • SurfaceFlinger:合成多个 Surface 的内容,提交给 Display 显示。

六、常见问题与优化

1. 性能瓶颈

  • 布局嵌套过深:导致多次 Measure/Layout(可用 ConstraintLayout 优化)。
  • 过度绘制:可通过开发者选项中的  "Show GPU Overdraw"  检测。

2. 离线绘制

  • 场景:复杂静态内容(如图表)。
  • 方案:使用 Bitmap 或 Canvas 预渲染,直接绘制缓存。

3. 强制重绘方法

  • invalidate() :请求重绘(主线程调用)。
  • postInvalidate() :非 UI 线程调用重绘。
  • requestLayout() :触发重新测量和布局。

常用方法:

方法作用触发流程
invalidate()标记脏区域,触发重绘(Draw 阶段)只走 draw() 流程
requestLayout()强制重新测量和布局(可能重绘)触发 measure() → layout() → draw()

七、自定义 View 的关键点

  1. 重写 onMeasure() :正确处理 wrap_content
  2. 避免在 onDraw() 中分配内存:防止卡顿。
  3. 支持 padding 和 margin:在测量和绘制时需考虑。

总结

View 的绘制流程是一个递归的树形遍历过程,通过 Measure → Layout → Draw 确定每个 View 的尺寸、位置和内容。理解其底层机制(如 VSYNC、双缓冲)和优化手段,是开发高性能 UI 的关键。

更多分享

  1. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)
  2. 一文带你吃透Kotlin协程的launch()和async()的区别
  3. 一文带你吃透HolderFragment 实现ViewModel的生命周期穿透!
  4. 一文带你吃透Android中常见的高效数据结构
  5. 一文带你吃透Android中Service的种类和启动方式