13

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)

mmlooloo
  • 18,937
  • 5
  • 45
  • 64
  • what's the final output? Do you see the whole bitmap, or just part of it. My assumption is that decodeRegion crops the bitmap if the rect exceeds the screen's size – Blackbelt May 04 '15 at 09:38
  • @Blackbelt yes I see the whole bitmap as `ScaleType.FIT_CENTER` dose with an ordinary `Bitmap`. – mmlooloo May 04 '15 at 10:06
  • then probably `decodeRegion` is ignoring the screen's density – Blackbelt May 04 '15 at 10:09
  • @Blackbelt I set it `options.inDensity = getResources().getDisplayMetrics().densityDpi;` and what happens if it ignores. I do not see any connection with device density, the bitmap memory size is determined by config option `Bitmap.Config.ARGB_8888` – mmlooloo May 04 '15 at 10:16
  • I saw that you are settings the `inDensity` parameter. That's why I said that android is probably ignoring it. The connection with density is the amount of memory required to store the bitmap. `ARGB_8888` means that you are using 32 bits per pixel. But the amount of memory required is width * height * 4. Width and height are scaled with the density – Blackbelt May 04 '15 at 10:20
  • Do you also apply methods 2 and 3 asynchronously? – Dmide May 05 '15 at 13:44
  • @Dmide I do not understand what you mean by asynchronously? – mmlooloo May 05 '15 at 14:31
  • @mmlooloo I mean inside "mImageView.post(new Runnable() {..." – Dmide May 05 '15 at 14:53
  • No out of memory dose not concern to threads, also your code runs on main thread. – mmlooloo May 05 '15 at 15:06
  • 1
    I know. My thought is about the fact that heap size is increasing with time and you have different (how much different in your case is another question, maybe it's insignificant) heap size when executing this code immediately vs over some time when adding Runnable to queue. – Dmide May 05 '15 at 15:10
  • Please can you also share version of android and device. Thanks! – Pinser May 06 '15 at 08:34
  • @DeBuGGeR on genymotion galaxy Note 3 API 18 – mmlooloo May 06 '15 at 08:41
  • Check out this : http://developer.android.com/training/displaying-bitmaps/load-bitmap.html – Haresh Chhelana May 06 '15 at 10:59

4 Answers4

2

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.

Manuel Allenspach
  • 12,467
  • 14
  • 54
  • 76
  • "Your first attemp scales the bitmap down" other method also scales but implicitly. – mmlooloo May 06 '15 at 11:06
  • "All three methods need more or less the same amount of memory" no it is not correct. – mmlooloo May 06 '15 at 11:07
  • "android:largeHeap="true"" I do not want to solve it I want to know why I do not get out of memory. – mmlooloo May 06 '15 at 11:08
  • @mmlooloo Yeah, the others scale implicitly, which does not affect the bitmap size (in bytes). I suggest you to debug your app and by setting `android:largeHeap="true"`, you can debug it without getting an out of memory error. – Manuel Allenspach May 06 '15 at 11:10
  • `inDensity` only used as last part to adjust `Bitmap` object parameters that affect only bitmap rendering process, so it doesn't increase or decrease amount of memory allocated during decoding. And also, he asked why it happens, not how to resolve. – weaknespase May 10 '15 at 22:07
0

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.

weaknespase
  • 1,014
  • 8
  • 15
-1

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.

Ninja
  • 2,479
  • 3
  • 23
  • 32
  • look at this sentence in my post: **Note that rect size is exactly the same as image size.** – mmlooloo May 04 '15 at 09:03
  • "1 use the scaled bitmap" there is not any scaled option. – mmlooloo May 04 '15 at 09:05
  • @mmlooloo I suppose the options.inDensity is the scaled option, `This can be because the intrinsic size is smaller, or its size post scaling (for density / sample size) is smaller`, but its related with BitmapFactory, not the BitmapRegionDecoder, a little strange – Ninja May 04 '15 at 10:35
-1
  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.

  • sorry but I do not understand what you mean. `Bitmap.Config.ARGB_8888` causes to store the bitmap as large as possible (4byte per pixel ) so I must get OOM definitely but I do not get :-( – mmlooloo May 07 '15 at 18:56
  • And how setting explicitly some parameter to its default value should affect memory allocation? Or anything at all? – weaknespase May 10 '15 at 22:03