加载大背景图时出现内存溢出问题

9
我正在使用android:background为Android布局提供背景图像。
在放置一些图像后,我遇到了以下异常:
08-24 00:40:19.237: E/dalvikvm-heap(8533): Out of memory on a 36000016-byte allocation.


如何在Android上使用大图片作为背景?
我能扩展应用程序堆内存吗?或者这样做不好吗?


3
请勿使用巨大的背景图像(> 30MB在您的情况下),这也被提及在许多类似的问题中。 - kabuko
2
在StackOverFlow上有很多类似的问题。我尝试总结了许多解决方案的替代方法:https://dev59.com/jWgt5IYBdhLWcg3wygWb#16528487 - Paulo Cheque
除了不使用非常大的图像外,应考虑设备的容量,真正的问题是设备没有像背景那样充电的能力。 - Maria Mercedes Wyss Alvarez
30MB 不仅仅是一张图片,感谢 @PauloCheque - Evan Lévesque
@jahroy 我说的是“放置一些图片”所以不止一张图片。而且没必要给我点踩。 - Evan Lévesque
6个回答

9
请看我的相关问题: 高分辨率图像-OutOfMemoryError 尝试通过尽可能减小背景图像的大小来最小化应用程序的内存使用。
可以通过以下方式实现:
  • 裁剪图像以适合屏幕
  • 在将图像用于应用程序之前进一步压缩图像(例如使用Photoshop)
  • 使用下面的方法加载位图
  • 尽早回收位图
  • 确保不要在内存中保留多个位图实例
  • 在使用位图后将引用设置为null
确保将设置为背景的图像正确加载(例如裁剪大小,适合屏幕大小),并在不再需要时立即从内存中释放。
确保你只有一个Bitmap实例在内存中。显示它后,调用recycle()并将其引用设置为null。
以下是如何加载图像:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }

    return inSampleSize;
}

感谢Adam Stelmaszczyk提供这个优美的代码片段。

嘿,使用位图后将引用设置为null是什么意思? - Kala J

7
我曾经遇到过由于布局中的背景图片导致类似的问题。图像的正常内存分配大小应为高度*宽度*4字节(在ARGB_8888模式下,这是默认模式)。
如果您看到显示活动时有30MB的分配,那么肯定存在一些问题。请检查是否将背景图像放置在drawable文件夹中。在这种情况下,系统必须将该图像缩放到屏幕的特定密度,从而导致大量的内存开销。
解决方案:
  1. 在每个drawable文件夹(mdpi、hdpi、xhdpi等)中放置特定版本的背景图像。
  2. 将背景图像放置在名为“drawable-nodpi”的特殊资源文件夹中。系统不会尝试缩放放置在此目录中的图像,因此只进行一个拉伸过程,分配的内存将是预期的。
更多信息请参见答案
希望这可以帮助您。

1
这将我的图形内存分配减少了一半!谢谢! - Allan Veloso

6
我遇到了同样的问题,并通过以下方法解决:
  1. 创建一个drawable-nodpi文件夹,将你的背景图片放入其中。

  2. 使用Picasso来显示这些图片 http://square.github.io/picasso/

使用.fit()和.centerCrop()方法展示它们,Picasso会自动缩放图片大小以适应需要的尺寸。

ImageView ivLogo = (ImageView) findViewById(R.id.ivLogo);
Picasso.with(getApplicationContext())
   .load(R.drawable.logo)
   .fit()
   .centerCrop()
   .into(ivLogo);

如果你的内存不足,Picasso将不会显示图片,而不是给你一个OOM错误。希望这能帮到你!

1

我能扩展应用程序的堆内存吗?

是的,您可以。只需在Manifest.xml中设置android:largeHeap:="true"即可。

   <application
        android:name="com.pepperonas.libredrive.App"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:largeHeap="true"
        android:label="@string/app_name"
        android:launchMode="standard">

这并不起作用,应用程序仍然可能会因为开启了largeHeap而耗尽内存,并且减缓应用程序的运行速度。https://developer.android.com/training/articles/memory.html 此外,大堆大小在所有设备上都不相同,当在具有有限RAM的设备上运行时,大堆大小可能与常规堆大小完全相同。因此,即使您请求大堆大小,也应调用getMemoryClass()来检查常规堆大小,并始终努力保持在该限制以下。 - Nickmccomb
对我来说,设置这个功能解决了地图片段中的一个问题(显示多达30k地理标记)......是的,很明显,每个设备都有有限的RAM - 因此我们应该编写消耗尽可能少资源的代码(有时缓存数据更好,有时更建议使用CPU)。 在这一点上,没有人能给出简短的答案。 但正如我所说,上面的代码片段在某些情况下对我奏效...因此说“这行不通”是错误的。 - Martin Pfeffer
我不同意。我认为解决方案不应该减慢您的应用程序并在未来引入更多问题。该应用程序无法正常工作,因为它对位图的使用不当,导致内存不足错误。您的“解决方案”无法解决此问题,因为即使使用您的修复程序,位图的大小仍可能超出可用内存(这可能不是您的解决方案),一旦引入更多位图,OOM错误仍将发生。那不是一个解决方案。 - Nickmccomb
就在一分钟前,我在应用程序介绍中加载大型图像时遇到了内存问题。在Nexus 6p(真实设备)上完全没有问题,但是genymotion由于内存问题而崩溃。更改清单中的参数后,模拟器运行稳定,就像应该的那样。嘿嘿,这就是刚刚发生在我身上的事情。也许下次你应该试试这个技巧 :) - Martin Pfeffer
以下是logcat的输出: 进程:com.pepperonas.m104,PID:21668 java.lang.OutOfMemoryError:无法分配32778252字节的内存,当前可用空间为15391484字节,距离OOM还有14MB。错误发生在dalvik.system.VMRuntime.newNonMovableArray(Native Method)函数中。 - Martin Pfeffer
我可以重现这个错误。请看这里:https://youtu.be/myerOO5Gr38(请注意,我正在运行AS 2.0,因此第一次尝试重新启动应用程序时,应用程序并没有完全重新启动) - Martin Pfeffer

0
使用Glide可以帮助您缓存图像并从内存中加载它们。
Glide  
    .with(/*context*/)
    .load(/*urlToImage*/)
    .thumbnail(0.5f/*scale*/)  //you can even downscale the image
    .into(imageView);

其次,使用 Glide,您可以通过使用 OnScrollChangedListener 观察滚动行为来暂停和恢复请求。
if(scrollState == SCROLL_STATE_IDLE){
    Glide.with(this /*context*/).resumeRequests();
} else if(scrollState == SCROLL_STATE_TOUCH_SCROLL ||
          scrollState == SCROLL_STATE_FLING){
 Glide.with(this).pauseRequests();
}

0

对于位图,Glide 是最好的选择。

Glide.with(getContext()).load(img_url_1).apply(new RequestOptions().override(Target.SIZE_ORIGINAL)).into(imageView1);

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