You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为TileProvider创建自定义Bitmap时如何优化内存占用?

解决Tile绘制时Bitmap引发的OutOfMemoryException优化方案

问题根源

每次调用objectImage都会创建新的Bitmap,当迭代绘制大量对象时,短时间内会生成大量内存占用高的Bitmap实例,GC无法及时回收,最终触发OOM。你考虑的Bitmap池是针对性的解决方案,同时还有更直接的优化手段。


优化方案

1. 直接在Tile画布上绘制,消除中间Bitmap(最优解)

完全跳过objectImage中创建中间Bitmap的步骤,将对象绘制逻辑直接迁移到Tile的Canvas上,彻底避免中间Bitmap的内存开销。

修改后的代码示例:

class ObjectTileProvider @Inject constructor(
   val application: Application,
   val repository: ObjectRepository
) : TileProvider {
   override fun getTile(x: Int, y: Int, z: Int): Tile {
      val objects = repository.getObjectsInTile(x, y, z)
      val bitmap = Bitmap.createBitmap(512, 512, Bitmap.Config.RGB_565) // 改用更省内存的配置
      val canvas = Canvas(bitmap)

      objects.forEach { obj ->
          drawObjectToCanvas(obj, canvas) // 直接在Tile画布上绘制
      }
      return Tile(z, x, y, bitmap.toByteArray()) // 补充Tile返回逻辑(原代码遗漏)
   }
   
   private fun drawObjectToCanvas(obj: Object, canvas: Canvas) {
      // 原objectImage中的绘制逻辑,直接操作传入的canvas
      // 比如绘制形状、文字等,无需创建新Bitmap
      canvas.drawRect(...)
      canvas.drawText(...)
   }
}

2. 实现线程安全的Bitmap复用池

如果必须保留中间Bitmap(比如对象需要预渲染后再组合),用复用池重复利用相同尺寸的Bitmap,避免频繁创建销毁。

示例实现(基于LruCache):

class ObjectTileProvider @Inject constructor(
   val application: Application,
   val repository: ObjectRepository
) : TileProvider {
   // 初始化Bitmap复用池,根据内存限制设置缓存大小
   private val bitmapPool = LruCache<Int, Bitmap>(calculateMaxCacheSize())

   override fun getTile(x: Int, y: Int, z: Int): Tile {
      val objects = repository.getObjectsInTile(x, y, z)
      val bitmap = Bitmap.createBitmap(512, 512, Bitmap.Config.RGB_565)
      val canvas = Canvas(bitmap)

      objects.forEach { obj ->
          val objBitmap = getReusableBitmap(size, size)
          val objCanvas = Canvas(objBitmap)
          // 绘制对象到objBitmap
          drawObjectToCanvas(obj, objCanvas)
          // 绘制到Tile画布
          canvas.drawBitmap(objBitmap, ...)
          // 用完放回池
          recycleBitmapToPool(objBitmap)
      }
      return Tile(z, x, y, bitmap.toByteArray())
   }
   
   private fun getReusableBitmap(width: Int, height: Int): Bitmap {
      val key = width * 1000 + height // 用尺寸作为缓存key
      var bitmap = bitmapPool.get(key)
      if (bitmap == null || bitmap.isRecycled) {
          bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
          bitmapPool.put(key, bitmap)
      } else {
          // 重置Bitmap内容,避免残留绘制痕迹
          bitmap.eraseColor(Color.TRANSPARENT)
      }
      return bitmap
   }
   
   private fun recycleBitmapToPool(bitmap: Bitmap) {
       if (!bitmap.isRecycled) {
           val key = bitmap.width * 1000 + bitmap.height
           bitmapPool.put(key, bitmap)
       }
   }
   
   private fun calculateMaxCacheSize(): Int {
       // 获取应用可用内存的1/8作为缓存上限
       val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
       return maxMemory / 8
   }
   
   private fun drawObjectToCanvas(obj: Object, canvas: Canvas) {
      // 原绘制逻辑
   }
}

3. 优化Bitmap配置

将ARGB_8888替换为RGB_565,每个像素内存占用从4字节降到2字节,直接减少一半内存开销。如果不需要透明通道,这个优化效果显著。

4. 及时清理无用Bitmap

如果无法复用Bitmap,在使用完毕后立即调用bitmap.recycle()并置为null,主动告知GC可以回收该内存,但注意不要回收仍在被Canvas或其他组件引用的Bitmap。


内容的提问来源于stack exchange,提问作者lostintranslation

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 21:45:28