【问题标题】:Why do I NOT get an out of memory exception?为什么我没有出现内存不足异常?
【发布时间】:2015-07-11 09:35:49
【问题描述】:

我在可绘制文件夹中有一个高分辨率图像 (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 是的,我将整个位图视为ScaleType.FIT_CENTER 与普通的Bitmap 剂量。
  • 那么可能decodeRegion 忽略了屏幕的密度
  • @Blackbelt 我将它设置为options.inDensity = getResources().getDisplayMetrics().densityDpi;,如果它忽略会发生什么。我没有看到与设备密度有任何联系,位图内存大小由配置选项Bitmap.Config.ARGB_8888 决定
  • 我知道。我的想法是关于堆大小随着时间而增加的事实,并且在立即执行此代码时与在将 Runnable 添加到队列时的一段时间内,您有不同的(在您的情况下有多大不同是另一个问题,也许它无关紧要)堆大小。

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


【解决方案1】:

这是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) 来获取位图。

数字 2 缓存可绘制对象,因此需要更多内存。
经过大量操作(检查位图是否已经预加载或兑现等),它会调用本机方法来获取位图。然后,它缓存drawable并设置背景图像。

数字 3 非常简单,它在几次操作后调用本机方法。


结论:对我来说,很难说哪种情况适用于此,但应该是这两种情况之一。
  1. 您的第一次尝试会缩小位图(inDensity 标志),因此需要更少的内存。
  2. 所有三种方法都需要或多或少相同的内存量,数字 2 和 3 稍微多一点。您的图像使用 ~16MB RAM,这是某些手机上的最大堆大小。第 1 个可能低于该限制,而其他两个则略高于阈值。

我建议你调试这个问题。在你的 Manifest 中,设置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();

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

【讨论】:

  • “您的第一次尝试缩小位图”其他方法也可以缩放但隐式。
  • “所有三种方法都需要或多或少相同数量的内存”不,这是不正确的。
  • "android:largeHeap="true"" 我不想解决它我想知道为什么我没有内存不足。
  • @mmlooloo 是的,其他的都是隐式缩放的,这不会影响位图大小(以字节为单位)。我建议你调试你的应用程序,通过设置android:largeHeap="true",你可以调试它而不会出现内存不足的错误。
  • inDensity 仅用作调整仅影响位图渲染过程的Bitmap 对象参数的最后一部分,因此它不会增加或减少解码期间分配的内存量。而且,他问的是为什么会发生,而不是如何解决。
【解决方案2】:

好的,归根结底,1 和 2,3 之间的唯一区别是 1 不支持九个补丁和可清除对象。因此,在解码过程中为 NinePatchPeeker 分配的一些额外内存很可能是触发 OOM 的原因 2 和 3(因为它们使用相同的后端)。 1的情况下不分配。

除此之外,我没有看到任何其他选项。如果你看图像数据解码,那么由于图像索引,平铺解码使用的内存略多,所以如果是这种情况,情况就会相反:1会抛出OOM,而2,3不会。

【讨论】:

    【解决方案3】:

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

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

    1

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

    the constructor(InputStream is, boolean isShareable) use the stream ,不会耗尽内存。

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

    参考:BitmapRegionDecoder will draw its requested content into the Bitmap provided, clipping if the output content size (post scaling) is larger than the provided Bitmap. The provided Bitmap's width, height, and Bitmap.Config will not be changed

    2,3

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

    没有缩放选项,整张图片都会加载到内存中

    对不起英语。

    或许能帮到你。

    【讨论】:

    • 看我帖子里的这句话:注意rect size和image size是一模一样的。
    • "1 使用缩放位图" 没有任何缩放选项。
    • @mmlooloo 我想 options.inDensity 是缩放选项,This can be because the intrinsic size is smaller, or its size post scaling (for density / sample size) is smaller,但它与 BitmapFactory 相关,而不是 BitmapRegionDecoder,有点奇怪
    【解决方案4】:
    1. 因此,您没有收到 OOM 异常

      options.inPreferredConfig = Bitmap.Config.ARGB_8888;
      

    已经给here

        public Bitmap.Config inPreferredConfig
    

    在 API 级别 1 中添加

    如果这是非空的,解码器将尝试解码到这个内部配置。如果它为空,或者无法满足请求,解码器将尝试根据系统的屏幕深度和原始图像的特征(例如它是否具有每像素 alpha)来选择最佳匹配配置(需要一个配置也做)。图像默认加载 ARGB_8888 配置。

    【讨论】:

    • 对不起,我不明白你的意思。 Bitmap.Config.ARGB_8888 导致存储位图尽可能大(每像素 4 字节)所以我必须肯定得到 OOM 但我没有得到:-(
    • 以及如何将某些参数显式设置为其默认值会影响内存分配?还是什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-10
    • 1970-01-01
    • 1970-01-01
    • 2015-03-29
    • 1970-01-01
    • 2011-03-08
    • 1970-01-01
    相关资源
    最近更新 更多