BitmapFactory OOM让我疯狂

时间:2009-12-23 21:27:19

标签: android out-of-memory android-bitmap

我一直在做很多搜索,我认识很多其他人 遇到与BitmapFactory相同的OOM内存问题。我的 应用程序仅使用Runtime.getRuntime ().totalMemory()显示可用的总内存为4MB。如果限制是16MB,那么为什么不是总数 内存增长为位图腾出空间?相反,它会引发错误。

我也不明白,如果我有1.6MB的可用内存 到Runtime.getRuntime().freeMemory()为什么我会收到错误说“VM 不会让我们分配614400字节“?对我来说,我有很多 可用内存。

我的应用程序已经完成,除了这个问题,当我离开时,它就消失了 重新启动手机,以便我的应用程序是唯一运行的。我正在使用 用于设备测试的HTC Hero(Android 1.5)。

此时我认为解决这个问题的唯一方法是以某种方式 避免使用BitmapFactory

任何人对此有任何想法或解释为什么VM不会 当有1.6MB的可用内存时分配614KB?

5 个答案:

答案 0 :(得分:45)

[注意(如下面CommonsWare所述)此答案中的整个方法仅适用于2.3.x(Gingerbread)。从Honeycomb开始,位图数据在VM堆中分配。]

位图数据未在VM堆中分配。 VM堆中有一个引用(很小),但实际数据是由底层Skia图形库在Native堆中分配的。

不幸的是,虽然BitmapFactory.decode ...()的定义说如果无法解码图像数据它返回null,那么Skia实现(或者更确切地说是Java代码和Skia之间的JNI粘合剂)会记录消息你看到(“VM不会让我们分配xxxx字节”),然后抛出一个OutOfMemory异常,误导消息“位图大小超过VM预算”。

问题不在VM堆中,而是在Native堆中。 Natïve堆在运行的应用程序之间共享,因此可用空间量取决于正在运行的其他应用程序及其位图使用情况。但是,鉴于BitmapFactory不会返回,你需要一种方法来确定调用是否会成功。

有一些例程可以监视Native堆的大小(请参阅Debug类的getNative方法)。但是,我发现getNativeHeapFreeSize()和getNativeHeapSize()不可靠。因此,在我的一个动态创建大量位图的应用程序中,我执行以下操作。

本机堆大小因平台而异。因此,在启动时,我们检查允许的最大VM堆大小,以确定允许的最大本机堆大小。 [神奇数字是通过在2.1和2.2上测试确定的,并且在其他API级别上可能会有所不同。]

long mMaxVmHeap     = Runtime.getRuntime().maxMemory()/1024;
long mMaxNativeHeap = 16*1024;
if (mMaxVmHeap == 16*1024)
     mMaxNativeHeap = 16*1024;
else if (mMaxVmHeap == 24*1024)
     mMaxNativeHeap = 24*1024;
else
    Log.w(TAG, "Unrecognized VM heap size = " + mMaxVmHeap);

然后每当我们需要调用BitmapFactory时,我们会在调用之前检查表单。

long sizeReqd        = bitmapWidth * bitmapHeight * targetBpp  / 8;
long allocNativeHeap = Debug.getNativeHeapAllocatedSize();
if ((sizeReqd + allocNativeHeap + heapPad) >= mMaxNativeHeap)
{
    // Do not call BitmapFactory…
}

请注意,heapPad是一个神奇的数字,它允许以下事实:a)本机堆大小的报告是“软”的; b)我们希望在Native堆中为其他应用程序留出一些空间。我们目前正在运行3 * 1024 * 1024(即3Mbytes)的打击垫。

答案 1 :(得分:2)

1.6 MB的内存似乎很多,但可能是内存碎片如此严重以致无法一次性分配如此大的内存块(这听起来确实非常奇怪)。

使用图像资源时OOM的一个常见原因是人们正在解压缩具有高分辨率的JPG,PNG,GIF图像。你需要记住,所有这些格式都压缩得很好,占用的空间非常小,但是一旦你将图像加载到手机上,他们将要使用的内存就像width * height * 4 bytes。此外,当解压缩开始时,需要加载一些其他辅助数据结构用于解码步骤。

答案 2 :(得分:2)

似乎Torid's answer中提出的问题已在更新版本的Android中得到解决。

但是,如果您使用图像缓存(专用缓存或甚至只是常规HashMap),则通过创建内存泄漏很容易出现此错误。

根据我的经验,如果您无意中坚持使用Bitmap引用并造成内存泄漏,OP的错误(指的是BitmapFactory和本机方法)会导致您的应用崩溃(直到ICS - 14和+?)

为避免这种情况,请让您“放开”您的位图。这意味着在缓存的最后一层使用SoftReferences,以便Bitmaps可以从中收集垃圾。这应该有效,但如果您仍然遇到崩溃,可以尝试使用bitmap.recycle()明确标记某些位图以进行收集,只需记住永远不要返回位图,以便在您的应用中使用bitmap.isRecycled()。< / p>

顺便说一下, LinkedHashMaps 是一个很好的工具,可以轻松实现相当不错的缓存结构,特别是如果你将this example (starting line 308)中的硬引用和软引用相结合......如果你陷入困境,使用硬引用也是如何让你自己陷入内存泄漏的情况。

答案 3 :(得分:0)

虽然捕获一个错误通常没有意义,因为通常它们只是由vm抛出,但在这种特殊情况下,jni胶水代码抛出了Error,因此处理无法加载的情况非常简单image:抓住OutOfMemoryError。

答案 4 :(得分:0)

虽然这是一个相当高级别的答案,但对我来说问题是在我的所有观看中使用硬件加速。我的大多数视图都有自定义位图操作,我认为这是大本机堆大小的来源,但实际上在禁用硬件加速时,本机堆的使用量减少了4倍。

似乎硬件加速会对您的视图执行各种缓存,创建自己的位图,并且由于所有位图共享本机堆,因此分配大小可能会非常显着增长。