📚 本文导读
本文将从基础概念出发,详细讲解Android中的离屏渲染技术,包括其工作原理、应用场景以及性能优化策略。通过代码示例和对比分析,帮助开发者深入理解这一重要的图形渲染技术。

📖 什么是离屏渲染?

离屏渲染(Off-Screen Rendering)
离屏渲染是指将图形内容渲染到一个不直接显示在屏幕上的缓冲区中,而不是直接渲染到屏幕上的帧缓冲区。这个过程创建了一个中间的渲染目标,可以在后续的渲染过程中被重复使用或进行额外的处理。

🎨 效果对比演示

📱 普通绘制效果

未使用离屏渲染的原始效果
下图展示了直接在画布上绘制红色方块、文本和绿色方块的效果,没有应用任何合成模式。

普通绘制效果

🎯 离屏渲染效果

绘制顺序分析

  1. 🔴 红色方块 → 📝 文本 → 🟢 绿色方块

下图展示了使用离屏渲染后的效果,绿色方块覆盖在文本上,并设置合成模式为 SRC_IN

离屏渲染效果图

🔍 合成模式详解

SRC_IN 模式含义
保持覆盖目标像素的源像素,丢弃剩余的源和目标像素。

  • 目标像素:绿色方块
  • 源像素:文本

图层分离机制
在绘制完红色方块后,通过 canvas.saveLayer() 创建了一个离屏渲染层。这个图层会在绘制完文本后,才会被合成到屏幕上。就是在一个新的Canvas上作画,这个图层只包含了文本和绿色方块。

⚡ saveLayer vs save 对比

关键区别

  • save():只保存变换矩阵、裁剪区域等状态
  • saveLayer():创建新的图层,合成模式仅影响该图层

上面效果所示,绿色方块覆盖在文本上。此时设置合成模式为SRC_IN,意思就是“保持覆盖目标像素的源像素,丢弃剩余的源和目标像素。这里目标像素是绿色方块,源像素是文本。这里说明一下为什么不是红色方块,因为在绘制完红色方块后,这里通过canvas.saveLayer()创建了一个离屏渲染的,这个图层会在绘制完文本后,才会被合成到屏幕上。就是在一个新的Canvas上作画,这个图层只包含了文本和绿色方块。如果这里不选择使用saveLayer而是save()这种操作的话只会保存变换矩阵、裁剪区域等状态。合成模式还是会针对这个主画布进行影响,而saveLayer恰好解决了这个问题

⚠️ 性能注意事项

性能警告
官方说明 saveLayer API 需要谨慎使用,性能、内存消耗过大,只有复杂视图才需要这种实现效果。

内存占用计算

1
内存占用 = width × height × 4字节(RGBA) × 采样倍数(例如抗锯齿)

双重绘制

  1. 在新的画布绘制一次
  2. 合成到主画布上绘制一次

💻 代码实现

🎯 方法一:使用 saveLayer

查看 saveLayer 实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class LayerView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, style: Int) : super(context, attrs, style)

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

// 绘制背景红色方块
canvas.withSave {
canvas.drawRect(200f, 200f, 600f, 600f, Paint().apply {
color = Color.RED
})
}

// 创建离屏渲染图层
val layerPointer = canvas.saveLayer(null, null)

// 在图层上绘制文本
canvas.drawText("文本A", 300f, 300f, Paint().apply {
color = Color.WHITE
textSize = 50f
})

// 在图层上绘制绿色方块(使用SRC_IN合成模式)
canvas.drawRect(300f, 280f, 500f, 500f, Paint().apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
color = Color.GREEN
})

// 恢复图层
canvas.restoreToCount(layerPointer)
}
}

🎯 方法二:使用 Bitmap 离屏渲染实现类似savelayer方式

替代方案
通过手动创建 Bitmap 和 Canvas 来实现离屏渲染,可以更精确地控制内存和性能。

查看 Bitmap 实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class LayerView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, style: Int) : super(context, attrs, style)

private var offscreenCanvas = Canvas()
private lateinit var offscreenBitmap: Bitmap

init {
MainScope().launch {
delay(1000)
invalidate()
}
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)

CoroutineScope(Dispatchers.IO).launch {
val createTime = measureTimeMillis {
// 创建离屏Bitmap
offscreenBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
offscreenCanvas = Canvas(offscreenBitmap)
}
Log.d("LayerView", "创建bitmap及canvas耗时: ${createTime}ms")

// 在离屏画布上绘制大量元素
repeat(1000) {
offscreenCanvas.drawText(
"数值A",
(0..300).random().toFloat(),
(0..400).random().toFloat(),
Paint().apply {
color = Color.WHITE
textSize = 50f
}
)

offscreenCanvas.drawRect(300f, 290f, 500f, 500f, Paint().apply {
color = Color.GREEN
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
})
}
}
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

// 绘制背景红色方块
canvas.withSave {
canvas.drawRect(200f, 200f, 600f, 600f, Paint().apply {
color = Color.RED
})
}

// 将离屏渲染的结果绘制到主画布
if (::offscreenBitmap.isInitialized) {
val drawTime = measureNanoTime {
canvas.drawBitmap(offscreenBitmap, 0f, 0f, Paint())
}
Log.d("LayerView", "绘制bitmap耗时: ${drawTime}ns")
}
}
}

📊 性能对比分析

saveLayer 特点

  • ✅ 使用简单,API直接支持
  • ❌ 内存占用大,系统自动管理
  • ❌ 性能开销较大
  • ⚠️ 适合简单的合成效果

Bitmap 特点

  • ✅ 内存可控,可手动回收
  • ✅ 性能上可控,特别是重复使用时
  • ✅ 可在子线程处理
  • ✅ 灵活性更高
  • ❌ 需要手动管理生命周期

🎯 最佳实践建议

选择建议

  1. 简单合成效果:使用 saveLayer
  2. 复杂图形处理:使用 Bitmap 离屏渲染
  3. 频繁重绘场景:优先考虑 Bitmap 方式
  4. 内存敏感应用:谨慎使用离屏渲染

优化技巧

  • 🔧 合理控制离屏画布尺寸
  • 🔧 及时回收 Bitmap 资源
  • 🔧 避免在 onDraw 中创建 Bitmap
  • 🔧 考虑使用硬件加速