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

Android View 绘制流程 优化 (Bitmap 复用+内容变化检测+防抖调度策略)

Bitmap 复用

当前的 generateThumbnail 方法。每次调用都会创建一个新的 Bitmap 和 Canvas,这确实会造成频繁的内存分配。让我实现 Bitmap 和 Canvas 的复用机制:


// 修改前+++++++++++++++++++++++++++++++++++++++++++/*** 生成缩略图*/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 复用相关变量private var thumbnailBitmap: Bitmap? = nullprivate var thumbnailCanvas: Canvas? = nullprivate var lastThumbnailWidth = 0private var lastThumbnailHeight = 0/*** 生成缩略图*/private fun generateThumbnail() {try {val thumbnailWidth = 120val thumbnailHeight = 80// 检查是否需要创建新的 Bitmapif (thumbnailBitmap == null || lastThumbnailWidth != thumbnailWidth || lastThumbnailHeight != thumbnailHeight) {// 释放旧的 BitmapthumbnailBitmap?.recycle()// 创建新的 Bitmap 和 CanvasthumbnailBitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888)thumbnailCanvas = Canvas(thumbnailBitmap!!)lastThumbnailWidth = thumbnailWidthlastThumbnailHeight = thumbnailHeightLog.d(TAG, "Created new thumbnail bitmap: ${thumbnailWidth}x${thumbnailHeight}")}val bitmap = thumbnailBitmap!!val canvas = thumbnailCanvas!!// 清空画布canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)// 计算缩放比例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)}}

添加在 View 销毁时释放 Bitmap 的代码:

//修改前+++++++++++++++++++++override fun onDetachedFromWindow() {super.onDetachedFromWindow()Log.d(TAG, "View detached from window")rects.clear() // 清空矩形数据}//修改后+++++++++++++++++++++override fun onDetachedFromWindow() {super.onDetachedFromWindow()Log.d(TAG, "View detached from window")rects.clear() // 清空矩形数据// 释放 Bitmap 资源thumbnailBitmap?.recycle()thumbnailBitmap = nullthumbnailCanvas = nullLog.d(TAG, "Released thumbnail bitmap resources")}

Bitmap 复用优化总结

问题分析

之前的 generateThumbnail 方法每次调用都会:

  1. 创建新的 Bitmap.createBitmap(120, 80, Bitmap.Config.ARGB_8888)
  1. 创建新的 Canvas(bitmap)
  1. 这导致频繁的内存分配和垃圾回收

优化方案

我们实现了 Bitmap 和 Canvas 的复用机制:

        添加复用变量:
  1.    private var thumbnailBitmap: Bitmap? = null

       private var thumbnailCanvas: Canvas? = null

       private var lastThumbnailWidth = 0

       private var lastThumbnailHeight = 0

        智能创建 Bitmap:

  • 只在首次调用或尺寸变化时创建新的 Bitmap
  • 复用现有的 Bitmap 和 Canvas
  • 使用 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) 清空画布
        资源管理:
  • 在 onDetachedFromWindow() 中调用 bitmap.recycle() 释放内存
  • 避免内存泄漏

性能提升

  • 减少内存分配:从每次创建新 Bitmap 改为复用
  • 减少垃圾回收压力:避免频繁的 Bitmap 对象创建和销毁
  • 提高响应速度:减少内存分配时间

内容变化检测

容变化检测来避免不必要的缩略图生成。让我实现一个简单的哈希机制来检测内容是否真的发生了变化:

    // 内容变化检测private var lastContentHash = 0private var lastOffsetX = 0fprivate var lastOffsetY = 0fprivate var lastScaleFactor = 1.0f/*** 计算内容哈希值*/private fun calculateContentHash(): Int {var hash = rects.sizefor (rect in rects) {hash = hash * 31 + rect.colorhash = hash * 31 + rect.cx.toInt()hash = hash * 31 + rect.cy.toInt()hash = hash * 31 + rect.width.toInt()hash = hash * 31 + rect.height.toInt()}return hash}/*** 检查内容是否发生变化*/private fun hasContentChanged(): Boolean {val currentHash = calculateContentHash()val hasRectChanges = currentHash != lastContentHashval hasTransformChanges = offsetX != lastOffsetX || offsetY != lastOffsetY || scaleFactor != lastScaleFactorif (hasRectChanges || hasTransformChanges) {lastContentHash = currentHashlastOffsetX = offsetXlastOffsetY = offsetYlastScaleFactor = scaleFactorreturn true}return false}/*** 生成缩略图*/private fun generateThumbnail(){// 检查内容是否发生变化if (!hasContentChanged()) {return}}

内容变化检测优化 ✅

  • 问题:即使内容没有变化也会生成缩略图
  • 解决:添加哈希检测机制,检查矩形内容和视图变换是否变化
  • 效果:只有在内容真正变化时才生成缩略图

检测机制

  1. 矩形内容哈希:基于矩形数量、颜色、位置、尺寸计算哈希值
  2. 视图变换检测:检查 offsetX、offsetY、scaleFactor 是否变化
  3. 智能跳过:如果内容没有变化,直接返回,不执行缩略图生成

防抖调度策略

首先添加防抖相关的变量:

    // 内容变化检测private var lastContentHash = 0private var lastOffsetX = 0fprivate var lastOffsetY = 0fprivate var lastScaleFactor = 1.0f->// 内容变化检测private var lastContentHash = 0private var lastOffsetX = 0fprivate var lastOffsetY = 0fprivate var lastScaleFactor = 1.0f// 防抖相关变量private var thumbnailRunnable: Runnable? = nullprivate var lastThumbnailTime = 0Lprivate val THUMBNAIL_DEBOUNCE_DELAY = 200L // 200ms 防抖延迟private var isUserInteracting = false // 用户是否正在交互

防抖调度方法:

    /*** 检查内容是否发生变化*/private fun hasContentChanged(): Boolean {val currentHash = calculateContentHash()val hasRectChanges = currentHash != lastContentHashval hasTransformChanges = offsetX != lastOffsetX || offsetY != lastOffsetY || scaleFactor != lastScaleFactorif (hasRectChanges || hasTransformChanges) {Log.d(TAG, "Content changed - rects: $hasRectChanges, transform: $hasTransformChanges")Log.d(TAG, "Transform: offsetX=${offsetX}, offsetY=${offsetY}, scale=${scaleFactor}")lastContentHash = currentHashlastOffsetX = offsetXlastOffsetY = offsetYlastScaleFactor = scaleFactorreturn true} else {Log.d(TAG, "Content unchanged, skipping thumbnail generation")}return false}->/*** 检查内容是否发生变化*/private fun hasContentChanged(): Boolean {val currentHash = calculateContentHash()val hasRectChanges = currentHash != lastContentHashval hasTransformChanges = offsetX != lastOffsetX || offsetY != lastOffsetY || scaleFactor != lastScaleFactorif (hasRectChanges || hasTransformChanges) {Log.d(TAG, "Content changed - rects: $hasRectChanges, transform: $hasTransformChanges")Log.d(TAG, "Transform: offsetX=${offsetX}, offsetY=${offsetY}, scale=${scaleFactor}")lastContentHash = currentHashlastOffsetX = offsetXlastOffsetY = offsetYlastScaleFactor = scaleFactorreturn true} else {Log.d(TAG, "Content unchanged, skipping thumbnail generation")}return false}/*** 防抖调度缩略图生成*/private fun scheduleThumbnailGeneration(immediate: Boolean = false) {val currentTime = System.currentTimeMillis()// 如果距离上次生成时间太短,且不是立即生成,则使用延迟if (!immediate && currentTime - lastThumbnailTime < THUMBNAIL_DEBOUNCE_DELAY) {// 取消之前的任务thumbnailRunnable?.let { removeCallbacks(it)Log.d(TAG, "Cancelled previous thumbnail task")}// 重新调度延迟任务thumbnailRunnable = Runnable {Log.d(TAG, "Executing delayed thumbnail generation")generateThumbnail()thumbnailRunnable = null}postDelayed(thumbnailRunnable!!, THUMBNAIL_DEBOUNCE_DELAY)Log.d(TAG, "Scheduled delayed thumbnail generation in ${THUMBNAIL_DEBOUNCE_DELAY}ms")return}// 立即生成或时间间隔足够Log.d(TAG, "Executing immediate thumbnail generation")generateThumbnail()lastThumbnailTime = currentTime}

修改 dispatchDraw 方法,使用防抖调度:

        // 调用父类的 dispatchDraw 来绘制子视图super.dispatchDraw(canvas)// 缩略图回调post { generateThumbnail() }->// 调用父类的 dispatchDraw 来绘制子视图super.dispatchDraw(canvas)// 使用防抖调度缩略图生成scheduleThumbnailGeneration()

现在需要找到触摸事件处理方法,添加操作结束检测。让我先查看触摸事件处理:

    override fun onTouchEvent(event: MotionEvent): Boolean {scaleDetector.onTouchEvent(event)when (event.actionMasked) {MotionEvent.ACTION_DOWN -> {lastTouchX = event.xlastTouchY = event.yisDragging = trueisScaling = false// 判断是否点中某个矩形(优先最上层)draggingRect = findTouchedRect(event.x, event.y)if (draggingRect != null) {// 计算手指与矩形中心的偏移val (rx, ry) = viewToRectSpace(event.x, event.y)dragRectOffsetX = rx - draggingRect!!.cxdragRectOffsetY = ry - draggingRect!!.cy}}MotionEvent.ACTION_POINTER_DOWN -> {isScaling = trueisDragging = falsedraggingRect = null}MotionEvent.ACTION_MOVE -> {if (!isScaling && isDragging && event.pointerCount == 1) {if (draggingRect != null) {// 拖动矩形val (rx, ry) = viewToRectSpace(event.x, event.y)draggingRect!!.cx = rx - dragRectOffsetXdraggingRect!!.cy = ry - dragRectOffsetYrequestLayout()invalidate()} else {// 整体平移val dx = event.x - lastTouchXval dy = event.y - lastTouchYoffsetX += dxoffsetY += dylastTouchX = event.xlastTouchY = event.yrequestLayout()invalidate()}}}MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {isDragging = falseisScaling = falsedraggingRect = null}}return true}->override fun onTouchEvent(event: MotionEvent): Boolean {scaleDetector.onTouchEvent(event)when (event.actionMasked) {MotionEvent.ACTION_DOWN -> {lastTouchX = event.xlastTouchY = event.yisDragging = trueisScaling = falseisUserInteracting = true// 取消之前的延迟任务thumbnailRunnable?.let { removeCallbacks(it)Log.d(TAG, "Cancelled thumbnail task on touch down")}// 判断是否点中某个矩形(优先最上层)draggingRect = findTouchedRect(event.x, event.y)if (draggingRect != null) {// 计算手指与矩形中心的偏移val (rx, ry) = viewToRectSpace(event.x, event.y)dragRectOffsetX = rx - draggingRect!!.cxdragRectOffsetY = ry - draggingRect!!.cy}}MotionEvent.ACTION_POINTER_DOWN -> {isScaling = trueisDragging = falseisUserInteracting = truedraggingRect = null// 取消之前的延迟任务thumbnailRunnable?.let { removeCallbacks(it)Log.d(TAG, "Cancelled thumbnail task on pointer down")}}MotionEvent.ACTION_MOVE -> {if (!isScaling && isDragging && event.pointerCount == 1) {if (draggingRect != null) {// 拖动矩形val (rx, ry) = viewToRectSpace(event.x, event.y)draggingRect!!.cx = rx - dragRectOffsetXdraggingRect!!.cy = ry - dragRectOffsetYrequestLayout()invalidate()} else {// 整体平移val dx = event.x - lastTouchXval dy = event.y - lastTouchYoffsetX += dxoffsetY += dylastTouchX = event.xlastTouchY = event.yrequestLayout()invalidate()}}}MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {isDragging = falseisScaling = falseisUserInteracting = falsedraggingRect = null// 操作结束,立即生成缩略图Log.d(TAG, "Touch operation ended, generating thumbnail immediately")scheduleThumbnailGeneration(immediate = true)}}return true}

现在需要修改缩放监听器,也添加操作结束检测:

    private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {override fun onScale(detector: ScaleGestureDetector): Boolean {scaleFactor *= detector.scaleFactorscaleFactor = scaleFactor.coerceIn(0.2f, 5.0f)requestLayout()invalidate()return true}}->private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {isUserInteracting = true// 取消之前的延迟任务thumbnailRunnable?.let { removeCallbacks(it)Log.d(TAG, "Cancelled thumbnail task on scale begin")}return true}override fun onScale(detector: ScaleGestureDetector): Boolean {scaleFactor *= detector.scaleFactorscaleFactor = scaleFactor.coerceIn(0.2f, 5.0f)requestLayout()invalidate()return true}override fun onScaleEnd(detector: ScaleGestureDetector) {isUserInteracting = false// 缩放结束,立即生成缩略图Log.d(TAG, "Scale operation ended, generating thumbnail immediately")scheduleThumbnailGeneration(immediate = true)}}

最后,在 View 销毁时清理防抖任务:

    override fun onDetachedFromWindow() {super.onDetachedFromWindow()Log.d(TAG, "View detached from window")rects.clear() // 清空矩形数据// 释放 Bitmap 资源thumbnailBitmap?.recycle()thumbnailBitmap = nullthumbnailCanvas = nullLog.d(TAG, "Released thumbnail bitmap resources")}->override fun onDetachedFromWindow() {super.onDetachedFromWindow()Log.d(TAG, "View detached from window")rects.clear() // 清空矩形数据// 取消防抖任务thumbnailRunnable?.let { removeCallbacks(it)Log.d(TAG, "Cancelled thumbnail task on detach")}thumbnailRunnable = null// 释放 Bitmap 资源thumbnailBitmap?.recycle()thumbnailBitmap = nullthumbnailCanvas = nullLog.d(TAG, "Released thumbnail bitmap resources")}

防抖机制实现总结

核心变量

Apply to DeepLearnVie...

private var thumbnailRunnable: Runnable? = null

private var lastThumbnailTime = 0L

private val THUMBNAIL_DEBOUNCE_DELAY = 200L // 200ms 防抖延迟

private var isUserInteracting = false // 用户是否正在交互

防抖调度策略

  • 时间间隔检查:如果距离上次生成时间小于 200ms,使用延迟
  • 取消机制:新操作开始时取消之前的延迟任务
  • 立即生成:操作结束后立即生成缩略图

操作结束检测

  • 触摸操作:ACTION_UP 和 ACTION_CANCEL 时立即生成
  • 缩放操作:onScaleEnd 时立即生成
  • 操作开始:ACTION_DOWN、ACTION_POINTER_DOWN、onScaleBegin 时取消延迟任务

性能提升

  • 减少频率:从每秒几十次降到每秒几次
  • 保持响应性:操作结束后立即更新
  • 避免卡顿:减少主线程负担
  • 用户体验:缩略图更新更平滑

清理机制

  • View 销毁:取消所有延迟任务
  • 内存管理:释放 Bitmap 资源

现在你可以测试一下,观察日志来确认防抖机制是否正常工作:

  1. 拖动画布,应该看到频繁的取消和重新调度日志
  1. 缩放画布,应该看到类似的防抖效果
  1. 操作结束后,应该立即生成缩略图

这个实现应该能显著减少不必要的缩略图生成,提高应用性能!

结束!!!

http://www.lryc.cn/news/584360.html

相关文章:

  • Canny边缘检测(cv2.Canny())
  • 2025年语言处理、大数据与人机交互国际会议(DHCI 2025)
  • MD5有什么特点吗
  • Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
  • fastMCP基础(一)
  • 如何将多个.sql文件合并成一个:Windows和Linux/Mac详细指南
  • STM32F103C8T6驱动无源蜂鸣器详解:从硬件设计到音乐播放
  • 【研报复现】方正金工:(1)适度冒险 因子
  • Boost.Asio学习(3):异步读写
  • Pytest之收集用例规则与运行指定用例
  • 读取按键的四种方式
  • Thrust库介绍与使用
  • AI进校园:北京中小学9月起开设AI通识课,每学年不少于8课时
  • 【PTA数据结构 | C语言版】链式栈的3个操作
  • AI 对话实现打字机效果 Vue3 setup
  • SEO算法更新应对:5大实战案例与优化策略
  • 力扣刷题记录(c++)06
  • The 2023 ICPC Asia Hangzhou Regional Contest(G. Snake Move(最短路))
  • Map容器用map优化程序
  • 《一起出发,“春”不“晚”》特别行动踏梦武当,探寻新春奇境
  • 动态规划疑惑总结
  • 爬虫-正则使用
  • 8.2.3希尔排序
  • 【Bluedroid】蓝牙协议栈控制器能力解析与核心功能配置机制(decode_controller_support)
  • 【Nginx】Nginx 安装与 Sticky 模块配置
  • Android 13----在framworks层映射一个物理按键
  • FlashAttention 快速安装指南(避免长时间编译)
  • GoView 低代码数据可视化
  • JAVA JVM对象的实现
  • 机器学习与光子学的融合正重塑光学器件设计范式