Android View 绘制流程 简述 (无限递归+BitMap问题)
绘制流程
在 Android 的 View 系统中,draw(canvas) 和 dispatchDraw(canvas) 是绘制流程中的两个关键方法:
1. draw(canvas) 方法的作用
draw(canvas) 是 View 类中的核心绘制方法,它的主要职责包括:
- 绘制背景 - 调用 drawBackground(canvas)
- 保存画布状态 - 调用 canvas.save()
- 绘制内容 - 调用 onDraw(canvas)
- 绘制子视图 - 调用 dispatchDraw(canvas)
- 绘制前景 - 调用 onDrawForeground(canvas)
- 恢复画布状态 - 调用 canvas.restore()
2. dispatchDraw(canvas) 的作用
dispatchDraw(canvas) 专门负责绘制子视图,它的调用链是:
===========================================
draw(canvas)
↓
dispatchDraw(canvas) ← 在这里被调用
↓
onDraw(canvas) ← 子视图的绘制
===========================================
无限递归的问题示例
自定义View 的部分代码:
override fun dispatchDraw(canvas: Canvas) {// 绘制矩形// ...// 调用父类的 dispatchDraw 来绘制子视图super.dispatchDraw(canvas)// 缩略图回调post { generateThumbnail() }
}private fun generateThumbnail() {try {// 创建缩略图val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)val canvas = Canvas(bitmap)// 这里调用 draw(canvas) 会导致问题draw(canvas)// ← 这里会再次触发 dispatchDraw!val thumbnail = Bitmap.createScaledBitmap(bitmap, 120, 80, true)thumbnailCallback?.onThumbnailUpdated(thumbnail)} catch (e: Exception) {Log.e(TAG, "Error generating thumbnail", e)}
}
4. 无限递归的原因
当你调用 draw(canvas) 时:
- draw(canvas) 被调用
- draw(canvas) 内部调用 dispatchDraw(canvas)
- dispatchDraw(canvas) 被调用
- dispatchDraw(canvas) 内部调用 generateThumbnail()
- generateThumbnail() 内部调用 draw(canvas)
- 回到步骤 1,形成无限循环
5. 解决方案
这就是为什么我们修改了 generateThumbnail() 方法,直接绘制矩形而不调用 draw(canvas)
private fun generateThumbnail() {// 直接绘制矩形,避免调用 draw()for (rect in rects) {paint.color = rect.colorval left = rect.cx - rect.width / 2val top = rect.cy - rect.height / 2val right = rect.cx + rect.width / 2val bottom = rect.cy + rect.height / 2canvas.drawRect(left, top, right, bottom, paint)}
}
总结
draw(canvas) 调用 dispatchDraw(canvas) 是 Android View 系统的标准绘制流程,这是为了确保:
- 父视图先绘制自己的内容
- 然后绘制所有子视图
- 最后绘制前景
当你在 dispatchDraw() 中调用 draw() 时,就会打破这个流程,导致无限递归。正确的做法是直接操作 Canvas 进行绘制,而不是调用 View 的 draw() 方法。
bitMap问题
/*** 生成缩略图*/private fun generateThumbnail() {try {// 创建缩略图大小的 Bitmapval thumbnailWidth = 120val thumbnailHeight = 80val bitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888)val canvas = Canvas(bitmap)// // 设置白色背景
// canvas.drawColor(Color.WHITE)// 计算缩放比例val scaleX = thumbnailWidth.toFloat() / widthval scaleY = thumbnailHeight.toFloat() / heightval scale = minOf(scaleX, scaleY)// 计算居中偏移val offsetX = (thumbnailWidth - width * scale) / 2val offsetY = (thumbnailHeight - height * scale) / 2// 应用变换canvas.save()canvas.translate(offsetX, offsetY)canvas.scale(scale, scale)// 应用当前视图的变换(平移和缩放)val cx = width / 2fval cy = height / 2fcanvas.translate(this.offsetX, this.offsetY)canvas.scale(scaleFactor, scaleFactor, cx, cy)// 直接绘制矩形,避免调用 draw()for (rect in rects) {paint.color = rect.colorval left = rect.cx - rect.width / 2val top = rect.cy - rect.height / 2val right = rect.cx + rect.width / 2val bottom = rect.cy + rect.height / 2canvas.drawRect(left, top, right, bottom, paint)}canvas.restore()thumbnailCallback?.onThumbnailUpdated(bitmap)} catch (e: Exception) {Log.e(TAG, "Error generating thumbnail", e)}}
内存消耗的巨大差异
之前的实现(大 Bitmap):
- 假设屏幕尺寸:1080×1920 像素
- Bitmap 大小:1080 × 1920 × 4 bytes = 8.3MB
- 每次生成缩略图都需要 8.3MB 内存
- 频繁创建和销毁大 Bitmap
现在的实现(小 Bitmap):
- 缩略图尺寸:120×80 像素
- Bitmap 大小:120 × 80 × 4 bytes = 38.4KB
- 内存使用量减少了 99.5%
问题总结
1. 无限递归 + 大 Bitmap 的双重打击
之前的实现存在两个致命问题:
- 无限递归:draw(canvas) → dispatchDraw(canvas) → generateThumbnail() → draw(canvas)
- 大 Bitmap 创建:每次递归都创建 8.3MB 的 Bitmap
这导致:
- CPU 使用率 100%
- 内存快速耗尽(每秒可能创建几十个 8.3MB 的 Bitmap)
- 应用崩溃或重启
2. Bitmap 缩放的开销
之前的实现还需要额外的缩放操作:
kotlin
Apply to DeepLearnVie...
// 额外的缩放操作
val scaledBitmap = Bitmap.createScaledBitmap(bitmap, 120, 80, true)
这个操作本身就很耗 CPU 和内存。
3. 频繁的垃圾回收
大 Bitmap 的频繁创建和销毁会触发:
- 频繁的垃圾回收
- 内存碎片化
- 系统卡顿
再进一步优化:
Android View 绘制流程 优化 (Bitmap 复用+内容变化检测+防抖调度策略)-CSDN博客