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

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

一、绘制流程总览

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

java 复制代码
// 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 需要实现)。
  • 保存位置信息到 mLeft, mTop, mRight, mBottom

常见场景

  • 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. 离线绘制

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

3. 强制重绘方法

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

常用方法:

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

七、自定义 View 的关键点

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

总结

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

更多分享

  1. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)
  2. 一文带你吃透Kotlin协程的launch()和async()的区别
  3. 一文带你吃透HolderFragment 实现ViewModel的生命周期穿透!
  4. 一文带你吃透Android中常见的高效数据结构
  5. 一文带你吃透Android中Service的种类和启动方式
相关推荐
liang_jy4 小时前
Android 窗口容器树(一)—— 窗口和窗口容器树
android·源码
HUGu RGIN4 小时前
MySQL--》如何在MySQL中打造高效优化索引
android·mysql·adb
Joseph Cooper7 小时前
Linux/Android 跟踪技术:ftrace、TRACE_EVENT、atrace、systrace 与 perfetto 入门
android·linux·运维
空中海7 小时前
安卓逆向03. 动态调试、抓包分析与 Frida Hook
android
一起搞IT吧8 小时前
相机Camera日志实例分析之二十:相机Camx【照片后置4800/5000/6400万拍照】单帧流程日志详解
android·嵌入式硬件·数码相机·智能手机
jinanwuhuaguo9 小时前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
千码君201611 小时前
Trae:一些关于flutter和 go前后端开发构建的分享
android·flutter·gradle·android-studio·trae·vibe code
jason.zeng@150220713 小时前
Androidr入门环境搭建
java·kotlin
重生之我是Java开发战士14 小时前
【MySQL】事务 & 用户与权限管理
android·数据库·mysql
怣疯knight16 小时前
Windows不安装 Android Studio如何打包安卓软件
android·windows·android studio