Android: Bitmap recycle() 是如何工作的?

98

假设我已经在一个位图对象中加载了一张图片,例如:

Bitmap myBitmap = BitmapFactory.decodeFile(myFile);

现在,如果我加载另一个位图,会发生什么?

myBitmap = BitmapFactory.decodeFile(myFile2);

第一个 myBitmap 会发生什么?它会被垃圾回收还是我需要在加载另一个位图之前手动进行垃圾回收,例如 myBitmap.recycle()

此外,在回收图片的同时有更好的方法来加载大图片并逐一显示它们吗?

5个回答

85

28

我认为问题在于:在Android的Honeycomb版本之前,实际的原始位图数据不是存储在VM内存中,而是存储在本地内存中。这种本地内存在对应的Java Bitmap 对象被GC回收时会被释放。

然而,当你耗尽本地内存时,Dalvik GC不会被触发,因此你的应用程序可能很少使用Java内存,因此Dalvik GC从未被调用,但它使用了大量的本地内存用于位图,最终导致OOM错误。

至少这是我的猜测。幸运的是,在Honeycomb及更高版本中,所有位图数据都存储在VM中,因此你不需要使用recycle()。但是对于数百万的2.3用户(碎片化抖拳头),你应该在可能的情况下使用recycle()(这是一个巨大的麻烦)。或者你也可以调用GC。


21
你需要在加载下一张图片之前调用myBitmap.recycle()。
根据你的myFile来源(例如,如果原始大小是你无法控制的),在加载图像时,你应该将图像缩放到显示大小,而不仅仅是简单地重新抽样一些任意数字。
if (myBitmap != null) {
    myBitmap.recycle();
    myBitmap = null;
}
Bitmap original = BitmapFactory.decodeFile(myFile);
myBitmap = Bitmap.createScaledBitmap(original, displayWidth, displayHeight, true);
if (original != myBitmap)
    original.recycle();
original = null;

我将displayWidth和displayHeight缓存在一个静态变量中,在Activity启动时进行初始化。

Display display = getWindowManager().getDefaultDisplay();
displayWidth = display.getWidth();
displayHeight = display.getHeight();

3
你不需要调用recycle(),但如果你想立即释放内存,这是一个好主意。 - Karu
16
被接受的答案说:“如果你想尽快释放内存,你应该调用recycle()”。而你的答案则是“你需要调用myBitmap.recycle()”。“应该”和“需要”之间有所不同,在这种情况下后者是不正确的。 - Karu
1
上下文很重要。问题是“还有更好的方法来加载大图像并在途中循环显示它们吗?” - djunod
4
从Android 4.1开始,以上示例可能会出现问题,因为createScaledBitmap在某些情况下可能会返回与原始实例相同的实例。这意味着您必须在回收原始实例之前检查original != myBitmap。 - Jeremyfa
1
@Jeremyfa 只有在指定的宽度和高度与原始图像完全相同时,它才会返回原始图像。在这种情况下,缩放是无意义的,因此可以通过跳过缩放并返回原始图像来节省一些处理时间。但这不应该“破坏”任何东西... - Jabari

11

一旦位图被加载到内存中,实际上它由两个部分的数据组成。第一部分包含有关位图的某些信息,另一部分包含有关位图像素的信息(它由字节数组组成)。第一部分存在于Java使用的内存中,第二部分存在于C++使用的内存中。它们可以直接使用彼此的内存。Bitmap.recycle()用于释放C++的内存。如果只是这样做,GC将收集Java的一部分,并且C的内存仍然被使用。


给一个有趣但十分精准的描述,说明为什么内存不可立即进行垃圾回收。不错! - Richard Le Mesurier

8

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接