当前位置: 首页 > news >正文

Android View 绘制流程 简述 (无限递归+BitMap问题)

绘制流程

在 Android 的 View 系统中,draw(canvas) 和 dispatchDraw(canvas) 是绘制流程中的两个关键方法:

1. draw(canvas) 方法的作用

draw(canvas) 是 View 类中的核心绘制方法,它的主要职责包括:

  1. 绘制背景 - 调用 drawBackground(canvas)
  1. 保存画布状态 - 调用 canvas.save()
  1. 绘制内容 - 调用 onDraw(canvas)
  1. 绘制子视图 - 调用 dispatchDraw(canvas)
  1. 绘制前景 - 调用 onDrawForeground(canvas)
  1. 恢复画布状态 - 调用 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) 时:

  1.  draw(canvas) 被调用
  2. draw(canvas) 内部调用 dispatchDraw(canvas)
  3. dispatchDraw(canvas) 被调用
  4. dispatchDraw(canvas) 内部调用 generateThumbnail()
  5. generateThumbnail() 内部调用 draw(canvas)
  6. 回到步骤 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 系统的标准绘制流程,这是为了确保:

  1. 父视图先绘制自己的内容
  1. 然后绘制所有子视图
  1. 最后绘制前景

当你在 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 的双重打击

之前的实现存在两个致命问题:

  1. 无限递归:draw(canvas) → dispatchDraw(canvas) → generateThumbnail() → draw(canvas)
  1. 大 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博客

http://www.dtcms.com/a/271360.html

相关文章:

  • android activity生命周期温习
  • Java HashMap 的 get 和 put 方法的实现流程
  • android——热修复(补丁)
  • 微软官方C++构建工具:历史演变、核心组件与现代实践指南
  • SQL注入:现象、本质与防御详解
  • 文本标签提取与大模型理解:方法论深度指南
  • Kubernetes 集群部署、配置和验证-使用kubeadm快速部署一个K8s集群_笔记
  • 【K8S】在 Kubernetes 上配置安装 Nginx Ingress 控制器指南
  • 使用LLaMA-Factory微调Qwen2.5-VL-3B 的目标检测任务-LLaMA-Factory训练数据配置
  • 图像处理中的霍夫变换:直线检测与圆检测
  • 【软件运维】前后端部署启动的几种方式
  • 区块链系统开发技术应用构建可信数字生态链
  • 股指期货交割日避坑指南
  • 【MkDocs踩坑】图片路径问题的排查与解决
  • 由 DB_FILES 参数导致的 dg 服务器无法同步问题
  • 【动手学深度学习】4.10 实战Kaggle比赛:预测房价
  • Android API Level 到底是什么?和安卓什么关系?应用发布如何知道自己的版本?优雅草卓伊凡
  • 深度学习预备知识
  • MyBatisPlus-03-扩展功能
  • 基于Matlab多特征融合的可视化指纹识别系统
  • 常见 HTTP 方法的成功状态码200,204,202,201
  • whitt算法之特征向量的尺度
  • 利用编码ai工具cursor写单元测试
  • springMVC06-注解+配置类实现springMVC
  • Java位运算
  • Electron的setContentProtection()会被哪个层级的API捕获?
  • 【TCP/IP】3. IP 地址
  • 储能系统防孤岛保护测试:电网安全的“守门人”
  • C#字符串相关库函数运用梳理总结 + 正则表达式详解
  • 基于YOLOv11的CF-YOLO,如何突破无人机小目标检测?