Android - 位图缓存占用大量内存

12

我对内存管理这一主题还很陌生,有很多东西我不理解。
我试图在我的应用程序中缓存图像,但是我遇到了它的内存消耗问题:

所有Bitmap缓存代码基本上都是从这里复制粘贴的:http://developer.android.com/training/displaying-bitmaps/index.html

我调试了这段代码,并在Eclipse的DDMS视图中检查了堆大小,在这些代码行之后堆大小增加了约15MB:

        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
在“decodeSampledBitmapFromResource”方法中。
这张图片是1024x800的,大小为75kb的jpg文件。根据我在互联网上看到的信息,这张图片应该占用大约1024*800*4(每个像素字节数)=3.125mb的内存。
所有关于这个问题的线程都没有说清楚为什么它占用的内存比它应该的多。有没有一种方法可以使用合理数量的内存缓存一张图片?
编辑:
我尝试了在@ArshadParwez的答案下建议的decodeFile方法。使用这种方法,在BitmapFactory.decodeStream方法之后,内存只增加了3.5mb——问题得到了解决,但我想直接从资源中缓存位图。
我注意到在decodeResource方法期间有两个内存“跳跃”——一个大约为3.5mb——这是合理的,另一个奇怪的是14mb。这14mb是用来做什么的?为什么会发生这种情况?

将其缓存到文件系统而不是活动内存中,怎么样? - Paul Nikonowicz
@PaulNikonowicz 即使它解决了问题,次要存储器很可能是闪存或类似类型,频繁写入会缩短其寿命,而主存储器则不会出现这种情况。 - Diego C Nascimento
@PaulNikonowicz 把图像缓存到存储中是没有帮助的,因为 sooner or later 他需要展示这张图片(此外,它还会占用应用程序已经使用的存储空间,为什么要有重复文件?),而且为了这个目的,他需要将其解码为真实位图,未压缩。高内存的原因是密度,欢迎您阅读我撰写的文章了解更多信息。 - android developer
@gunar 每像素4个字节是针对ARGB_8888格式的。对于不推荐使用的ARGB_4444格式,每像素为2个字节,并且包含alpha通道。 - android developer
你是正确的!谢谢你指出这个问题! - gunar
显示剩余12条评论
3个回答

8

图片也会根据密度进行缩放,因此可能会使用大量内存。

例如,如果图像文件位于drawable文件夹中(即mdpi密度),并在xhdpi设备上运行,则宽度和高度都会加倍。也许这个链接可以帮助您,或者这个链接

因此,在您的示例中,图像文件占用的字节数为:

(1024*2)*(800*2)*4 = 13,107,200 字节。

如果在xxhdpi设备上运行它(如HTC One和Galaxy S4),情况将更糟。

你可以怎么做?要么将图像文件放入正确密度的文件夹中(drawable-xhdpidrawable-xxhdpi),要么将其放入drawable-nodpi(或资产文件夹中)并根据您的需求缩小图像。

顺便说一下,你不需要设置options.inJustDecodeBounds = false,因为这是默认行为。实际上,您可以将位图选项设置为null。

关于缩小,您可以使用Google的方法我的方法,每种方法都有其优点和缺点。

关于缓存,有很多方法可以实现。最常见的是LRU缓存。我最近创建了一种替代方案(链接在此处在此处),它允许您缓存更多的图像并避免OOM,但它也给您带来了很多责任。


1
好的。我忘了关于位图的另一个提示:您可以设置它们的格式,因此如果您不需要透明度并且质量不太重要,则可以使用RGB_565(每像素使用2个字节)而不是默认的ARGB_8888(每像素使用4个字节)。链接在这里:http://developer.android.com/reference/android/graphics/Bitmap.Config.html。还有一些关于内存和位图的视频,甚至在Google IO网站上也有。我建议观看它们,即使它们没有涉及您的问题。 - android developer

7
你可以使用这种方法传递图片并得到位图:
public Bitmap decodeFile(File f) {
    Bitmap b = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        BitmapFactory.decodeStream(fis, null, o);
        fis.close();
        int IMAGE_MAX_SIZE = 1000;
        int scale = 1;
        if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
            scale = (int) Math.pow(
                    2,
                    (int) Math.round(Math.log(IMAGE_MAX_SIZE
                            / (double) Math.max(o.outHeight, o.outWidth))
                            / Math.log(0.5)));
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        b = BitmapFactory.decodeStream(fis, null, o2);
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return b;
}

我在处理图片时也遇到了很多像OutOfMemoryError这样的崩溃。所以我使用了一些数学方法编写了这个函数,之后即使是5MB大小的图片也可以无崩溃地使用了。 - arshu
并不是因为我使用了decodeStream,而是因为我输入的是一个图像文件,并且它使用了FileInputStream。如果它是一个资源文件,那么我会使用decodeResource。 - arshu
那么,这种方法与 http://developer.android.com/training/displaying-bitmaps/load-bitmap.html#load-bitmap 中展示的 decodeSampledBitmapFromResource 相比有何优势? - Ori Wasserman
那个链接中的方法和我使用的不同,因为我将图像的最大尺寸固定为1000像素,而且如果您使用我的代码,您不会看到输出图像有很多降级,而那个链接中的代码会大大降低输出图像的质量。 - arshu
你的方法确实更好,但我并不满意。必须有一种直接从资源缓存位图的方法。我刚刚发现了一些奇怪的东西。我会在几分钟内更新我的问题。 - Ori Wasserman
显示剩余2条评论

1
根据您的要求,我使用了一种方法从资源文件夹获取图像,并且我使用了一个7 MB的图像。我将7 MB的图像放在“res->drawable”文件夹中,并使用以下代码,它没有崩溃,图像显示在图像视图中:

@Ori Wasserman:

 Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.image_7mb);
 loBitmap = Bitmap.createScaledBitmap(image, width_of_screen , height_of_screen, true);
 imageview.setImageBitmap(loBitmap);

实际上,它将使用比你说的更多的RAM,但只是短暂的。原因是你创建了原始位图和一个小位图。由于大图像将在下一个GC中被处理掉,所以你不会注意到它。你同时拥有两者的那个点是最关键的。 - android developer
你正在创建两个位图来实现一个单一的目标,这对内存非常不友好。尝试在这个问题中使用 BitmapFactory.Option。 - neferpitou

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