加载位图的最快方法,创建后重新使用位图

Posted

技术标签:

【中文标题】加载位图的最快方法,创建后重新使用位图【英文标题】:Fastest way to load bitmap, re-using bitmap once created 【发布时间】:2012-03-12 14:59:21 【问题描述】:

我有一个生成许多位图对象的应用程序。一旦我创建了一个位图,所有剩余的位图都将是相同的大小。

目前我可以在大约 50-80 毫秒的时间内在手机上加载/创建一个新位图,这可以满足我的需要。然而,由于创建这些的速度很快,我遇到了不断的 GC。

我想重用同一个位图对象,但不知道如何通过 sdk 来实现。

我确实编译了 libjpeg 并通过 NDK 加载我的图像并重新使用我的位图,但是我的加载速度下降到大约 200 毫秒,这太慢了。 待我有代码后,我会发布它。

问题:

有没有办法重复使用我的位图对象来避免 GC? 有没有更快的方法通过 NDK 加载我的图像?是否可以挂钩操作系统加载位图的方式?我知道 libjpegTurbo,但我目前无法编译它(另一天的另一个话题)。

关于最佳方式的任何其他想法。

【问题讨论】:

【参考方案1】:

为什么不使用哈希图来存储您的位图?然后,当您加载位图时,首先检查它是否在哈希图中,如果是,您可以重复使用它。如果不在hashmap中,正常保存后再插入hashmap中。

【讨论】:

我每秒创建大约 20 个位图,所以我无法将它们全部保存在内存中。我实际上有一个后台线程填充我从中提取新位图的队列。最大的问题是在我的后台线程中,而不是创建新的位图对象,我想从位图池中提取并重新使用它们,这样我就不必有那么多的 GC。 @broschb 如果您无法将它们保存在内存中,那么您如何重新使用它们呢?你意识到这没有意义吧?如果您有内存限制,那么最好的办法是使用哈希图和队列实现图像缓存,然后一旦加载的位图数量达到一定限制,就可以从队列中删除旧位图并提示 GC它们应该从内存中清除。哈希图将用于识别位图当前是否在队列中及其位置。 我可以通过改变位图的像素数据来重新使用位图,新的像素数据来自另一个位图。请参阅@Samuels 的回复,我认为这是我正在考虑的方向。 @broschb Bitmap 或 int 数组中的每个像素占用 4 个字节的内存。这意味着一个 700x400 的 int 数组已经占用了大约 273 KB。我非常怀疑 Bitmap 对象占用超过一千字节的内存,最多几千字节。如果您可以在内存中保存所有 int 数组,那么您几乎可以肯定可以保存所有 Bitmap 对象。 int 数组是 Bitmap 对象的绝大部分,所以如果你没有实现任何缓存,我怀疑它会解决你的 GC 问题。 @onit 700x400 位图应该占用大约 1.1MB。【参考方案2】:

我建议使用 IntBuffer(s),其中包含您需要换出的像素数据。然后,创建一个您需要大小的位图,当您需要换出像素时,使用 bitmap.copyPixelsFromBuffer()。我想它会比每次需要更改像素数据时分配/取消分配位图内存要快得多。如果您想将缓冲区保存在内存中以便快速检索,最好将它们保存在 hashmap 中。

您可以选择将 setPixels() 与 int 数组一起使用。 copyPixelsFromBuffer() 的好处是不尝试像素转换,而且选项较少,所以可能会快一点。

【讨论】:

我喜欢 int 缓冲区的想法,但我看到的唯一显着收益可能是内存空间。最快的方法是将对象保存在内存中,在这种情况下,如果 int 缓冲区明显小于位图对象,这将是一个显着的改进。我对此表示怀疑,除非位图相对较小。 像素数据从何而来?在某些时候它必须是一个 int 数组,对吧?因此,如果您每次都分配一个位图,那么您就有了 int 数组的内存和位图的内存。假设您已经完成了 int 数组,这是一个需要进行垃圾收集的对象。然后,当您完成位图时,还需要对它进行垃圾收集。这是需要进行垃圾收集的两个大对象。如果您只使用一个位图,您将不会拥有需要进行垃圾收集的额外对象。 Bitmap GC 需要更长的时间,因为它也有 JNI 调用。 如果你在内存中重用位图,GC 不再是一个大问题,除非你有大量的图像无法缓存在内存中(我个人在内存中很好地存储了数百个位图在较新的手机上)。我想位图使用的至少 95% 的内存也是实际的底层 int 数组,除非您有非常小的位图。如果他可以将位图缓存在内存中(这将是最大的性能提升),我怀疑这将是一个显着的性能提升。如果他需要不断地回收Bitmaps,我同意这将是一个改进。 感谢您的想法,我会研究其中的一些。我正在处理的图像大约是 700x400,在我开始遇到问题之前,我可以在内存中存储大约 8-10 个。我以前的实现是在位图上重用(替换像素数据),这大大减少了 GC,但我在我的应用程序中遇到了其他实现。方法,并且不得不移动到加载图像。 @Samuel 和 onit,感谢您的帮助和建议。看着这个,想用 IntBuffers 方法运行一些测试。我如何将图像直接加载到 IntBuffers 中?我能看到的唯一方法是加载位图,获取像素并加载到 intBuffer 中,但我确定我错过了一种明显的方法。

以上是关于加载位图的最快方法,创建后重新使用位图的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法将图像作为位图加载到 Glide

Egret 位图纹理 学习

在给定的指针地址读取 3 个字节的最快方法?

无法从内存中正确加载位图

是否可以在OnStop()上回收所有数据并使用保留片段?

修改位图大小