从“高效作画”到“无需作画”:Android 绘制优化的新旧哲学

394 阅读4分钟

一句话总结:

传统绘制优化的核心是“让每一次 onDraw 更快”,而现代优化的哲学则是“通过缓存和状态驱动,让 onDraw 根本不必被调用”。


第一篇章:“古典哲学”——让每一次绘制都物尽其用 (View 体系)

在传统的 View 体系中,我们的核心目标是降低 CPU 和 GPU 在每一帧的绘制开销。

1. 绘制的根源:invalidate()onDraw()

当调用 view.invalidate() 时,我们为这个 View 标记了一个“脏区”,并请求系统在下一个 Vsync 信号到来时,回调 onDraw() 方法来重绘它。我们的优化,就是围绕这个过程展开。

2. 核心技巧:削减绘制开销

总结以下技巧,它们是优化存量代码的基石:

  • 减少过度绘制 (Overdraw): 通过开发者选项的工具,移除所有不必要的 background,并使用 canvas.clipRect() 精确限定绘制区域,确保每个像素点只被绘制一次。
  • 简化 onDraw() 实现: 严禁在 onDraw() 中分配对象(new Paint(), new Rect()),所有绘图资源都应在初始化时创建并复用。
  • 拥抱硬件加速: 开启硬件加速,将绘制任务从 CPU 转移到更高效的 GPU。

3. 深入本质:硬件加速的“缓存”魔法——DisplayList

开启硬件加速后,onDraw() 的角色发生了根本性转变:

  • CPU 不再是“画家”,而是“导演”: 它将 canvas.drawXX() 等命令录制成一个名为 DisplayList 的“剧本”。
  • GPU 成为“演员”: RenderThread 将这个“剧本”交给 GPU,由 GPU 高效地执行并渲染。

优化的关键在于复用这个“剧本” 。一旦 DisplayList 被创建,只要 View 的内容本身不变,即便是对其进行平移、缩放、旋转、修改透明度等操作,系统都无需重新调用 onDraw() ,而是直接在 GPU 层面复用已缓存的 DisplayList 并应用新的变换。

启示:

  • 优先使用属性动画(view.animate()...),因为它能最大化地利用 DisplayList 缓存。
  • 避免不必要的 invalidate(),因为每一次调用都可能导致 DisplayList 的重新录制。

第二篇章:“现代哲学”——从源头上避免绘制 (Jetpack Compose)

Jetpack Compose 带来了范式革命。它不再关心“如何画”,而是关心“画什么”。它通过状态驱动智能重组,将绘制优化的思维提升到了新的高度。

1. 核心理念:UI 是状态的函数 UI = f(State)

在 Compose 中,你不再手动调用 invalidate()。你只负责描述 UI 应该如何根据某个状态(State)来呈现。当且仅当这个状态发生变化时,Compose 会自动、精准地找到依赖这个状态的 UI 部分,并仅仅重绘这一部分。

2. 智能重组 vs. invalidate()

  • invalidate() (View 体系): 像一个“全城警报”,通知范围大(整个 View),需要开发者手动优化 onDraw 内部的逻辑来减少实际工作量。
  • 智能重组 (Compose): 像一把“手术刀”,精准定位到数据变化的最小 UI 单元。如果一个 Text 的文本状态变了,那么只有这个 Text 对应的 Composable 函数会重新执行。如果状态不变,相关的绘制代码根本不会被执行。 这就是“无需作画”的真谛。
@Composable
fun MyScreen(viewModel: MyViewModel) {
    // a. nameState 是一个 State<String>
    val name by viewModel.nameState.collectAsState()
    
    // b. counterState 是一个 State<Int>
    val counter by viewModel.counterState.collectAsState()

    Column {
        // 这个 Text 只订阅了 nameState
        Text(text = name) 
        
        // 这个 Text 只订阅了 counterState
        Text(text = "Counter: $counter")
        
        Button(onClick = { viewModel.incrementCounter() }) {
            Text("Increment")
        }
    }
}

当点击按钮,只有 counterState 变化时,Compose 只会重组(重绘)第二个 Text,第一个 TextButton 的代码完全不会再次执行。


三、总结:你的优化工具箱

时代核心哲学关键工具/概念适用场景
View 体系让每一次绘制更高效DisplayList 缓存, 避免 Overdraw, 优化 onDraw维护和优化现有的基于 XML 的项目
Compose 体系通过状态管理避免不必要的绘制智能重组 (Recomposition), State, Modifier开发新功能和新应用,追求极致性能和开发效率

最终建议:

对于存量项目,请用“古典哲学”精细打磨你的 View 绘制性能。对于新项目和新功能,请拥抱“现代哲学”,用 Jetpack Compose 从架构层面构建一个无需过度优化的、天然高性能的 UI。