Bitmap allocation, using BitmapFactory.Options.inBitmap throws IllegalArgumentException
Asked Answered
M

6

21

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 ?

Modeling answered 12/3, 2014 at 15:3 Comment(3)
What is your need? Why not setting image resource directly into your ImageView? Like calling imageView.setImageResource(R.drawable.img1) and then imageView.setImageResource(R.drawable.img2)Immemorial
can you add the log after getting exception.Altonaltona
@Immemorial I edited the question and explained why setImageResource() is bad and why I am trying to use inBitmap flagModeling
H
3

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.

Hepburn answered 25/3, 2014 at 14:40 Comment(6)
I've ask about that earlier. If author has only one drawable-dpi folder, then on other dpis bitmaps consumes large amount of memory.Ozan
I don't see where you have asked this before. Is it in another question?Hepburn
I think that the author wants to replace img1 with img2, and both of them are the same size (for example 200x100px) and with the same density, or at least in the same drawable-xxxxx folder. In my opinion the problem is that the author does not have img1 and img2 in the correct drawable-xxxxx folder, and this probably matters when decodeBitmap is used.Hepburn
That is what I mean :) Android can handle that if image is in lower DPI but such bitmap consumes huge memory amount of memory on some devicesOzan
@helleye I liked that you could reproduce this problem and found the solution. I had my images in drawable folder, and following your suggestion, I copied it to drawable-nodpi and it started working!Modeling
@Modeling I am glad I helped. I saw that it should not be hard to reproduce so I gave it a try.Hepburn
H
2

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
Hombre answered 14/3, 2014 at 18:29 Comment(6)
since I didn't explain the reason for using this feature from the beginning I gave you the boundry, but the question is still open. If you have other suggestions, you are more than welcome. thanksModeling
My point was actually regarding that first "reuse" that you're creating the bitmap right before that anyway, so you might as well just decode directly that very first time (but still try to reuse after that). Also, did you give the double buffering approach a shot?Hombre
I can't get it.. by the flow you show here, next will happen: 1. decode -> gc work, set bitmap 1 to imageview -> gc work. 2. the same -> gc work and work... or I am missing something. The double buffering approach is something I didn't try. it sounds interesting will try it today.Modeling
Oh, maybe I didn't word my answer clearly. I meant decode while reusing bitmap 1 and point to bitmap 1 then decode while reusing bitmap 2 and point to bitmap 2. It's similar to your current approach, but without reusing the bitmap that you're currently using by means of a double buffer.Hombre
It is not weird at all, in fact it is a recommendation from the official development documentation.Fitch
Reusing bitmaps is a recommendation, yes. "Reusing" a bitmap that you just created without ever using the initial one that you created not so much.Hombre
J
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

Jewelljewelle answered 25/3, 2014 at 8:37 Comment(0)
O
0

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.

Ozan answered 21/3, 2014 at 16:41 Comment(5)
all images in the same size. And, I edited the question to explain the reason of why I try to decode in such way. thanksModeling
The main issue is the GC locking time. GC locks everything, not matter on which thread you are running. once we need to allocate memory and there is not enough, GC will run and try to free as much as he can. Your edit can't help in this case. It is also not correct answer for this question, since imageView.setImageResource(R.drawable.img1); is something that have to be done on UI thread and this is what causes the GC to lockModeling
Yes, but I wrote that you can load resource in background thread. I did not write that you should call this method in non UI Thread. How big is your png in mdpi (kB)?Ozan
No my friend. setImageResource will decode in high resolution and this this very bad in terms of memory allocationAbsorptivity
I know that but he is reading from resource folder, not from the internet. I just assumed that he will got properly scaled bitmaps in proper subfolders (for mdpi, hdpi and so on) to not have any memory issues. This is rather common and good practice to do this that way. The things changes when you are downloading bitmaps and you cannot determine their sizes but this is another topic. Just check that because I see many 'theories' hereOzan
S
0

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.

Surrogate answered 10/11, 2015 at 15:25 Comment(0)
A
-4

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

Arvizu answered 28/3, 2014 at 15:50 Comment(1)
That is absolutely not recommended, and doesn't fix the main issue, which is triggering garbage collection.Cns

© 2022 - 2024 — McMap. All rights reserved.