为什么我没有收到内存不足异常?

13

我在drawable文件夹中有一张高分辨率图像(2588*1603)。如果我使用以下代码(1)将其设置为imageView,我将不会收到OOM异常,而且图像也会按预期分配:

public class MainActivity extends ActionBarActivity{


    private ImageView mImageView;

    int mImageHeight = 0;
    int mImageWidth  = 0;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

      mImageView = (ImageView) findViewById(R.id.imageView);
      mImageView.setScaleType(ScaleType.FIT_CENTER);

      BitmapFactory.Options sizeOption = new BitmapFactory.Options();
      sizeOption.inJustDecodeBounds = true;
      BitmapFactory.decodeResource(getResources(), R.drawable.a, sizeOption);
      mImageHeight = sizeOption.outHeight;
      mImageWidth  = sizeOption.outWidth; 

      mImageView.post(new Runnable() {
          @Override
          public void run() {
              try {
                BitmapRegionDecoder bmpDecoder = BitmapRegionDecoder
                          .newInstance(getResources().openRawResource(R.drawable.a),true);
            Rect rect = new Rect(0,0,mImageWidth, mImageHeight);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            options.inDensity = getResources().getDisplayMetrics().densityDpi;
            Bitmap bmp = bmpDecoder.decodeRegion(rect, options);

            mImageView.setImageBitmap(bmp);  

            } catch (NotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }   
          }
      });

    }
}

请注意,矩形大小与图像大小完全相同。

但是如果我使用其他方法,例如2或3,就会出现OOM错误。

  2)  mImageView.setBackgroundResource(R.drawable.a);

  3) Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
     mImageView.setImageBitmap(bmp);

1和2,3之间的区别是什么?

(我知道如何解决OOM问题,我只是想知道它们之间的差异)


最终输出是什么?您是否可以看到整个位图,还是只能看到其中的一部分。我的假设是,如果矩形超出屏幕大小,则decodeRegion会裁剪位图。 - Blackbelt
@Blackbelt 是的,我看到整个位图与普通的 Bitmap 一样作为 ScaleType.FIT_CENTER 处理。 - mmlooloo
那么很可能是decodeRegion忽略了屏幕的密度。 - Blackbelt
@Blackbelt 我设置了 options.inDensity = getResources().getDisplayMetrics().densityDpi; 如果它被忽略会发生什么?我没有看到与设备密度的任何连接,位图内存大小由配置选项 Bitmap.Config.ARGB_8888 决定。 - mmlooloo
1
我知道。我的想法是关于堆大小随时间增加的事实,当您将Runnable添加到队列中时,立即执行此代码与一段时间后执行此代码时具有不同的堆大小(在您的情况下有多大的差异是另一个问题,也许它是微不足道的)。 - Dmide
显示剩余8条评论
4个回答

2
这是BitmapRegionDecoder#decodeRegion的源代码:

public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
    checkRecycled("decodeRegion called on recycled region decoder");
    if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
            || rect.bottom > getHeight())
        throw new IllegalArgumentException("rectangle is not inside the image");
    return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
            rect.right - rect.left, rect.bottom - rect.top, options);
}

如您所见,它只是调用了一个本地方法。我不太理解C++是否根据您的inDensity标志将位图缩小。
另外两种方法使用相同的本地方法(nativeDecodeAsset)获取位图。
第二种方法缓存可绘制对象,因此需要更多的内存。在执行大量操作(检查位图是否已经预加载或缓存等操作)后,它调用本地方法获取位图。然后,它缓存可绘制对象并设置背景图像。
第三种方法非常简单,它在执行几个操作后调用本地方法。
结论: 对我来说很难说这里适用哪种情况,但应该是这两种情况之一。
  1. 您的第一次尝试缩小了位图(使用了inDensity标志),因此需要更少的内存。
  2. 所有三种方法需要更多或更少相同数量的内存,2和3只需要稍微多一些。您的图像使用了约16MB的RAM,在某些手机上达到了最大堆大小。第一种方法可能低于该限制,而其他两种方法略高于阈值。

我建议您调试此问题。 在您的清单中,将android:largeHeap="true"设置为获取更多内存。然后,运行您的3个不同尝试,并记录堆大小和位图分配的字节数。

long maxMemory = Runtime.getRuntime().maxMemory();
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long freeMemory = maxMemory - usedMemory;
long bitmapSize = bmp.getAllocationByteCount();

这将为您提供更好的概述。

你的第一次尝试是将位图缩小,另一种方法也可以缩放,但是隐式地进行。 - mmlooloo
所有三种方法需要的内存多少相同,这是不正确的。 - mmlooloo
"android:largeHeap="true"" 我不想解决它,我想知道为什么我没有内存不足的问题。 - mmlooloo
@mmlooloo 是的,其他的会自动缩放,不会影响位图大小(以字节为单位)。我建议您通过设置 android:largeHeap="true" 来调试应用程序,这样可以避免出现内存不足的错误。 - Manuel Allenspach
inDensity 只用于调整 Bitmap 对象参数的最后一部分,这些参数仅影响位图渲染过程,因此在解码期间不会增加或减少分配的内存量。他也问了为什么会发生这种情况,而不是如何解决。 - weaknespase

0

好的,回到核心问题,1和2,3之间的单一差异就是1不支持九宫格和可清除性。因此,在解码期间为NinePatchPeeker分配额外的内存是触发2和3中OOM的原因(因为它们使用相同的后端)。对于1而言,则没有这种情况。

除此之外我没有看到其他选项。如果您看一下图像数据解码,那么由于图像索引,平铺解码会使用稍微更多的内存,因此如果是这种情况,情况将相反:1将抛出OOM,而2,3则不会。


-1
  1. 你不会因为这个而得到OOM异常

    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    

这里已经给出了这里

    public Bitmap.Config inPreferredConfig

API级别1中添加

如果此项非空,则解码器将尝试解码为此内部配置。如果为空或无法满足请求,则解码器将尝试根据系统屏幕深度和原始图像的特性(例如是否具有每像素Alpha,需要一个也具有的配置)选择最佳匹配配置。默认情况下,使用ARGB_8888配置加载图像。


抱歉,我不明白你的意思。Bitmap.Config.ARGB_8888 会导致位图尽可能地存储为最大值(每像素4字节),所以我一定会遇到OOM,但是我没有遇到 :-( - mmlooloo
设置某些参数为默认值会如何影响内存分配?或者说,是否会有任何影响? - weaknespase

-1

图片细节过多会导致内存不足。

总结:1. 使用缩放后的位图;2、3. 加载完整详细的可绘制对象(这会导致内存不足),然后调整大小并将其设置为ImageView。

1

Bitmap bmp = bmpDecoder.decodeRegion(rect, options);

构造函数(InputStream is, boolean isShareable)使用流,不会耗尽内存。

使用BitmapFactory.Options和BitmapRegionDecoder可以缩小位图。

参考:BitmapRegionDecoder将其请求的内容绘制到提供的位图中,如果输出内容大小(缩放后)大于提供的位图,则进行裁剪。提供的位图的宽度、高度和Bitmap.Config不会更改

2,3

Drawable d = mContext.getDrawable(mResource);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);

没有比例选项,整张图片将加载到内存中。

抱歉我的英语。

也许可以帮到你。


看一下我帖子中的这句话:请注意,矩形大小与图像大小完全相同。 - mmlooloo
  1. 使用缩放位图,但是没有任何缩放选项。
- mmlooloo
@mmlooloo 我猜测 options.inDensity 是缩放选项,"这可能是因为固有大小较小,或者其缩放后的大小(用于密度/采样大小)较小",但它与 BitmapFactory 相关,而不是 BitmapRegionDecoder,有点奇怪。 - Ninja

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