Why do I NOT get an out of memory exception?

675 Views Asked by At

I have a high resolution image (2588*1603) in drawable folder. If I use below code (1) to set it for the imageView I do not get OOM exception and the image assigned as expected:

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();
            }   
          }
      });

    }
}

Note that rect size is exactly the same as image size.

But If I use other methods like for example 2 or 3 I get OOM.

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

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

What is the difference between 1 and 2,3 ?

(I know how to solve OOM, I just want to know the difference)

4

There are 4 best solutions below

5
On

This is the source of 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);
}

As you can see, it simply calls a native method. I do not understand enough C++ to see whether the method scales the bitmap down (according to your inDensity flag).

The other two methods use the same native method (nativeDecodeAsset) to get the bitmap.

Number 2 caches the drawable and thus needs more memory.
After lots of operations (checking if the bitmap is already preloaded or cashed and other things), it calls a native method to get the bitmap. Then, it caches the drawable and sets the background image.

Number 3 is pretty straight forward, it calls a native method after a few operations.


Conclusion: For me, it is hard to say which scenario applies here, but it should be one of these two.

  1. Your first attemp scales the bitmap down (the inDensity flag) and thus needs less memory.
  2. All three methods need more or less the same amount of memory, number 2 and 3 just a little bit more. Your image uses ~16MB RAM, which is the maximum heap size on some phones. Number 1 could be under that limit, while the other two are slightly above the threshold.

I suggest you to debug this problem. In your Manifest, set android:largeHeap="true" to get more memory. Then, run your 3 different attemps and log the heap size and the bytes allocated by the bitmap.

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

This will give you a better overview.

2
On
  1. You are not getting OOM exception because of this

    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    

It is already given here

    public Bitmap.Config inPreferredConfig

Added in API level 1

If this is non-null, the decoder will try to decode into this internal configuration. If it is null, or the request cannot be met, the decoder will try to pick the best matching config based on the system's screen depth, and characteristics of the original image such as if it has per-pixel alpha (requiring a config that also does). Image are loaded with the ARGB_8888 config by default.

3
On

Too many detail of the picture results the out of memory.

summary: 1 use the scaled bitmap; 2,3 load the full detailed drawable(this results the out of memory) then resize and set it to imageview.

1

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

the constructor(InputStream is, boolean isShareable) use the stream , which will not exhaust the memory.

use BitmapFactory.Options and BitmapRegionDecoder will scale down the bitmap.

Refer: 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);

there is no scale option, the whole picture will load to memory

Sorry for English.

Maybe help you.

0
On

Ok, down to core, single difference between 1 and 2,3 is that 1 doesn't support nine patches and purgeables. So most probably a bit of additional memory allocated for NinePatchPeeker to work during decoding is what triggers OOM in 2 and 3 (since they use same backend). In case of 1, it isn't allocated.

Other from that i don't see any other options. If you look at image data decoding, then tiled decoding uses slightly more memory due to image index, so if it was the case, situation would be opposite: 1 will be throwing OOMs and 2,3 is not.