android – Bitmap allocation, using BitmapFactory.Options.inBitmap throws IllegalArgumentException-ThrowExceptions

Exception or error:

I get the next exception: Problem decoding into existing bitmap, when setting inBitmap to true;

Caused by: java.lang.IllegalArgumentException: Problem decoding into existing bitmap
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:460)

The interesting thing is that the same code fails in different places when running on:

  • API: 4.4.2, Nexus 4
  • API: 4.3.1, Samsung s3

This is my code which is a copy as it shown in this DevBytes: Bitmap Allocation video.

private BitmapFactory.Options options;
private Bitmap reusedBitmap;

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

    final ImageView imageView = (ImageView) findViewById(R.id.image_view);

    // set the size to option, the images we will load by using this option
    options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    options.inMutable = true;
    BitmapFactory.decodeResource(getResources(), R.drawable.img1, options);

    // we will create empty bitmap by using the option
    reusedBitmap = Bitmap.createBitmap(options.outWidth, options.outHeight, Bitmap.Config.ARGB_8888);

    // set the option to allocate memory for the bitmap
    options.inJustDecodeBounds = false;
    options.inSampleSize = 1;
    options.inBitmap = reusedBitmap;

    // #1
    reusedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img1, options);
    imageView.setImageBitmap(reusedBitmap);

    imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            options.inBitmap = reusedBitmap;
            // #2
            reusedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img2, options);
            imageView.setImageBitmap(reusedBitmap);

        }
    });
}

  • Nexus 4, crashes on BitmapFactory.decodeResource() at // #1
  • S3, passes the #1 and display the first image, but crashes later by clicking on image at BitmapFactory.decodeResource() at // #2

Few notes:

  • The images are in the same size. I tried jpg and png. Fails on both.
  • Bitmaps are mutable.
  • I checked by using this canUseForInBitmap method, as described here.

Question:

How to use this inBitmap property properly?

If you met such problem or you see that I made something stupid, please comment/reply. Any help, will be appreciated. If you know about any workaround, it will be great.

– Edit (the question is still open) –

Sorry for not explaining the reason of why I am trying to reuse bitmaps in such way.
The reason for this is GC that locks every time he decides to free memory.
inBitmap feature should help us to reuse bitmap without allocating new memory which will cause GC to clean the already allocated memory.

For example if I use this common approach:

Log.i("my_tag", "image 1");
imageView.setImageResource(R.drawable.img1);
Log.i("my_tag", "image 2");
imageView.setImageResource(R.drawable.img2);
Log.i("my_tag", "image 3");
imageView.setImageResource(R.drawable.img3);

Then this will be GC work:

I/my_tag  ( 5886): image 1
D/dalvikvm( 5886): GC_FOR_ALLOC freed 91K, 2% free 9113K/9240K, paused 15ms, total 15ms
I/dalvikvm-heap( 5886): Grow heap (frag case) to 19.914MB for 11520016-byte allocation
D/dalvikvm( 5886): GC_FOR_ALLOC freed <1K, 1% free 20362K/20492K, paused 13ms, total 13ms
I/my_tag  ( 5886): image 2
D/dalvikvm( 5886): GC_FOR_ALLOC freed 11252K, 2% free 9111K/9236K, paused 15ms, total 15ms
I/dalvikvm-heap( 5886): Grow heap (frag case) to 19.912MB for 11520016-byte allocation
D/dalvikvm( 5886): GC_FOR_ALLOC freed <1K, 1% free 20361K/20488K, paused 35ms, total 35ms
I/my_tag  ( 5886): image 3
D/dalvikvm( 5886): GC_FOR_ALLOC freed 11250K, 2% free 9111K/9236K, paused 15ms, total 15ms
I/dalvikvm-heap( 5886): Grow heap (frag case) to 19.913MB for 11520016-byte allocation
D/dalvikvm( 5886): GC_FOR_ALLOC freed <1K, 1% free 20361K/20488K, paused 32ms, total 32ms

This is more than 100ms of locked main thread!

The same thing will happen if I will decodeResource() without the inBitmap option. So the question is still open, how to use this property ?

How to solve:

I tried your code with emulated Nexus 4.
I had default ic_launcher.png file which I copy and pasted it two times in drawable-mdpi (as I usually do). I renamed the two new files to match the names in your code (so that I have less changes to make there).
When I run the application I observed the same as you did.
After few different attempts I decided to copy the the new pngs to other drawable folders – so they were present into:

  • drawable-hdpi
  • drawable-mdpi
  • drawable-xhdpi
  • drawable-xxhdpi

I run the application and it is working!

I am not really sure why it really works, but obviously it has to be something with the correct screen resolution/density.

###

It’s a little weird that you create a new bitmap and then try to reuse it. Why not just let decodeResource create a new bitmap in the first place? I suspect your problem in #2 though is that once you’ve set the ImageView to use the bitmap, it’s not able to reuse it anymore (because it’s already in use). The IllegalArgumentException is mentioned in the docs:

If the decode operation cannot use this bitmap, the decode method will return null and will throw an IllegalArgumentException.

As far as a workaround, you might try to keep two bitmaps around and switch around which one the ImageView is pointing to, e.g.:

  1. decode to bitmap 1, point ImageView to bitmap 1
  2. decode to bitmap 2, point ImageView to bitmap 2
  3. repeat from step 1

###

You will need to set options.inMutable to true before #1 & #2 in your code.
This may sound a little add, but these are little mines left over that you have stepped upon.

Hope this helps

###

Why you are decoding bitmaps ? Just do:

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

    final ImageView imageView = (ImageView) findViewById(R.id.image_view);
    imageView.setImageResource(R.drawable.img1);

    imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            imageView.setImageResource(R.drawable.img2);
        }
    });
}

Are you sure that both your images have the same dimension ? According to documentation

If [inBitmap is] set, decode methods that take the Options object will
attempt to reuse this bitmap when loading content. If the decode
operation cannot use this bitmap, the decode method will return null
and will throw an IllegalArgumentException. The current implementation
necessitates that the reused bitmap be mutable, and the resulting
reused bitmap will continue to remain mutable even when decoding a
resource which would normally result in an immutable bitmap.

You should still always use the returned Bitmap of the decode method
and not assume that reusing the bitmap worked, due to the constraints
outlined above and failure situations that can occur. Checking whether
the return value matches the value of the inBitmap set in the Options
structure will indicate if the bitmap was reused, but in all cases you
should use the Bitmap returned by the decoding function to ensure that
you are using the bitmap that was used as the decode destination.

Usage with BitmapFactory

As of KITKAT, any mutable bitmap can be reused by BitmapFactory to
decode any other bitmaps as long as the resulting byte count of the
decoded bitmap is less than or equal to the allocated byte count of
the reused bitmap. This can be because the intrinsic size is smaller,
or its size post scaling (for density / sample size) is smaller.

Prior to KITKAT additional constraints apply: The image being decoded
(whether as a resource or as a stream) must be in jpeg or png format.
Only equal sized bitmaps are supported, with inSampleSize set to 1.
Additionally, the configuration of the reused bitmap will override the
setting of inPreferredConfig, if set.


EDIT:

If you have large resource bitmap, just load it in asynctask… More info HERE but it can be done much simplier.

###

I’m not sure about the problem you met with Samsung S3 at #2. But the problem you met with Nexus 4 may be because you put two image in wrong dpi drawable folder. So when it try to decode the bitmap, it can’t find the resources.

My phone has screen density is hdpi, at first I try to put two image in drawable-mdpi, and I face the problem with #1. So I changed it to drawable-hdpi, and it worked.

###

Just put largeHeap=”true” in application tag (AndroidManifest.xml)

Leave a Reply

Your email address will not be published. Required fields are marked *