4

I'm getting my bitmap like so from XML:

//Get bitmap from drawable
bd = (BitmapDrawable) view.getResources().getDrawable(R.drawable.backgrounds);
backgrounds = bd.getBitmap();

//Do required work with bitmap (Will just use a log statement here for testing
Log.v("NewTag","Testing: "+bd.getBitmap().getPixel(0, 0));

//Now recycle this large bitmap
bd.getBitmap.recycle();
bd=null;
backgrounds.recycle();
backgrounds=null;

The first time I run this code, all is good. However when I exit my app (using the back key), then restart the app, it may or may not work. Sometimes, I get an error:

Can't call getPixel() on a recycled bitmap

Why? I've not even recycled it yet. Or more accurately, it seems to not be recreating the bitmap and remembering the recycle from last time.

This problem doesn't happen if I use BitmapFactory to obtain the bitmap (unfortunately, I can't do that as I have to get this particular bitmap from an XML alias).

Also prior to installing Lollipop, this worked OK (as long as I had bd = null).

I've been at this problem for 2 days straight so if anyone could throw any light on it I'd be very grateful.

Edit

I've attempted @aga's suggestion of simply not recycling/nulling bd, but this makes no difference. The bitmap is still 'already' recycled as soon it's been re-created (again, intermittently).

Also, when logging like so:

Log.v("NewTag","Backgrounds: "+backgrounds);

I've noticed that when it fails, the reference logged is the same as the previous time. So.....

enter image description here

Zippy
  • 3,826
  • 5
  • 43
  • 96

3 Answers3

5

The Resources class has caches for resources loaded from your APK. When you recycle the Drawable and Bitmap, you ruin the cached objects. There is no way for the Resources caches to know this, so they happily return you the same object the next time you ask for that resource.

When your app process dies, all memory state is lost, including the resource cache -- so things will work again (once). Note that "exiting" the app or destroying the activity doesn't necessarily mean that your process will die.

Snild Dolkow
  • 6,669
  • 3
  • 20
  • 32
  • 1
    Doesn't hitting back until the app closes means the app has been closed totally? – JayVDiyk Jan 02 '16 at 16:00
  • If you press back, your activity is still opened under recent applications? Try removing it from there, than it should be totally closed. – Marko Jan 02 '16 at 16:04
  • 1
    @JayVDiyk: Nope, it doesn't. In that situation, your `Activity` will be destroyed, but your *process* is most likely still alive. Android keeps it around to speed up future launches of the same app. This is what is known as a "cached process" or "empty process"in the memory stats. The system automatically cleans up such processes as needed. You can read more about the process lifecycle here: http://developer.android.com/guide/topics/processes/process-lifecycle.html – Snild Dolkow Jan 02 '16 at 16:09
  • @SnildDolkow so is there any workaround for this? Thank you for answering. – JayVDiyk Jan 02 '16 at 16:31
  • I think "don't recycle images fetched from `Resources`" is your best bet. – Snild Dolkow Jan 02 '16 at 16:33
  • @SnildDolkow, forgive my lack of understanding but is not recycling it inefficient from a memory management perspective? In my situation, I load the bitmap, then load it as an Open GL texture. From that point on I have no need for the original bitmap. Does it seem a good idea to not recycle it? Just curious. Thanks. – Zippy Jan 02 '16 at 18:27
  • The cached resources are only [weakly referenced from the cache](http://androidxref.com/6.0.0_r1/xref/frameworks/base/core/java/android/content/res/ThemedResourceCache.java#mThemedEntries) (at least on Marshmallow). So if nothing else is referencing those resources anymore, the garbage collector is allowed to free them. – Snild Dolkow Jan 02 '16 at 20:11
2

Interesting issue. I'm pretty sure, there were some optimization ideas behind it in Android.

If you do need XML's bitmaps:

    Resources res = new Resources(getAssets(), new DisplayMetrics(), new Configuration());

    bd = (BitmapDrawable) res.getDrawable(R.drawable.bitmap_drawable);
    backgrounds = bd.getBitmap();

    ......

    bd.getBitmap().recycle();
    bd=null;
    backgrounds.recycle();
    backgrounds=null;

Optionaly, you can first try to get Bitmap from standard getResources() and only then instantiate new resource instance.

If you don't need XML's bitmaps:

 Bitmap backgrounds = BitmapFactory.decodeResource(getResources(), R.drawable.test_image);

to get image and

 backgrounds.recycle();
 backgrounds=null;

to recycle it.

I hope, it helps.

Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
  • Thanks @KonstantinLoginov but please see this (quoted from the question): "This problem doesn't happen if I use BitmapFactory to obtain the bitmap (unfortunately, I can't do that as I have to get this particular bitmap from an XML alias)." – Zippy Jan 02 '16 at 12:28
  • @Zippy you havent figured the solution yet? I am having the same problem. I am removing all recycles code at the moment and wait for an answer – JayVDiyk Jan 02 '16 at 12:32
  • Unfortunately @JayVDiyk I never found a solution to this apart from, as you say, simply not recycling the bitmaps! – Zippy Jan 02 '16 at 12:35
  • @Zippy i think, i found it, check it out! – Konstantin Loginov Jan 02 '16 at 13:53
  • @JayVDiyk I think, I found it, check it out! – Konstantin Loginov Jan 02 '16 at 13:53
0

As @snild Dolkow stated above this behavior happens because the bitmap is being stored in the Resources cache and when you're trying to get it again you're getting the recycled bitmap.

For my opinion the solution is to save the bitmap to internal storage and reload it from storage when it's necessary.

The overhead of this solution is that you'll have to delete the image file after you finish using it.

Community
  • 1
  • 1
Nativ
  • 3,092
  • 6
  • 38
  • 69