一文带你吃透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的种类和启动方式
相关推荐
iOS阿玮2 小时前
碰到一个不听劝的老板,喜提4.3a!
uni-app·app·apple
c***21293 小时前
Springboot3学习(5、Druid使用及配置)
android·学习
修炼者3 小时前
【Android 进阶】别再强转 Context 了!手把手教你优雅解耦 View 与 Activity
android·android studio
x***01063 小时前
SpringSecurity+jwt实现权限认证功能
android·前端·后端
程序员江同学4 小时前
线下活动|2025 Kotlin 中文开发者大会北京分会场
android·kotlin
李坤林5 小时前
Android Vulkan 开启VK_GOOGLE_DISPLAY_TIMING 后,一个vsync 会释放两个imageBuffer现象分析
android·vulkan
Jerry5 小时前
Compose 状态思维
android
k***45995 小时前
MySQL----case的用法
android·数据库·mysql
r***86987 小时前
Plugin ‘mysql_native_password‘ is not loaded`
android·数据库·mysql
v***59837 小时前
MySQL-mysql zip安装包配置教程
android·mysql·adb