5

I have two activities: MainActivity and Activity2.

The MainActivity simply open the seconds one through Intent.

To return to MainActivity from the Activity2 I press the "back" button.

When I do these steps, the App crashes:

  • open the App: MainActivity appears
  • start the Intent: the Activity2 appears
  • press the "back" button: the MainActivity appears
  • start the Intent: my App crashes because of this error:

    IllegalArgumentException: Cannot draw recycled bitmaps

MainActivity.java:

Intent intent = new Intent(this, Activity2.class);
startActivity(intent);

Activity2.java:

@Override
public void onBackPressed() {
    super.onBackPressed();
}

@Override
protected void onDestroy() {
    super.onDestroy();

    for(Map.Entry<Integer, ImageView> entry : mapImageViews.entrySet()) {
        ImageView imageView = entry.getValue();
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            Bitmap bitmap = bitmapDrawable.getBitmap();
            if(bitmap != null) {
                bitmap.recycle();
            }
            bitmapDrawable = null;
            bitmap = null;
        }
        imageView.setOnClickListener(null);
        imageView.setImageDrawable(null);
        imageView.setImageBitmap(null);
        imageView = null;
        drawable = null;
    }
    mapImageViews.clear();
    mapImageViews = null;
}

Since the application uses high resolution images (already adapted with BitmapFactory and inSampleSize), to avoid memory leaks I invoke recycle() in the onDestroy() method.

As I learned by reading lots of SO answers and on the Web, calling recycle() on bitmap let they to be garbage collected early.

But many other posts advise against invoking recycle(), or at least advise doing it only when you are sure that the bitmap is no longer needed in the Activity, that is in the onDestroy() method.

Now I'm a little worried about what I've learned about it because if I remove the recycle() the error no longer happens.

The error occurs on a device with Android 4.4.2, but it does not occur on a device with Android 6.0 and on Nexus 7 (Android 5.1.1).

  • Is the problem about the activities' stack?
  • Does the GC is trying to free the bitmap's memory too late? In case of this, how to definitively destroy the Activity and ALL its contents?
  • There is any difference between these two Android versions?
  • Or there is something I'm missing/wrong?
user2342558
  • 5,567
  • 5
  • 33
  • 54

3 Answers3

2

try changing your onDestroy method like below

@Override
protected void onDestroy() {
    for(Map.Entry<Integer, ImageView> entry : mapImageViews.entrySet()) {
        ImageView imageView = entry.getValue();
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            Bitmap bitmap = bitmapDrawable.getBitmap();
            if(bitmap != null) {
                bitmap.recycle();
            }
            bitmapDrawable = null;
            bitmap = null;
        }
        imageView.setOnClickListener(null);
        imageView.setImageDrawable(null);
        imageView.setImageBitmap(null);
        imageView = null;
        drawable = null;
    }
    mapImageViews.clear();
    mapImageViews = null;

    super.onDestroy();
}

Also see this: How to recycle and reuse images in an effective way.

Rahul Khurana
  • 8,577
  • 7
  • 33
  • 60
  • Why should I do this? – user2342558 Sep 16 '19 at 10:44
  • Because You're calling the super method before recycling the bitmap and you take the Images Reference from the ImageViews which are destroyed when the super onDestroy got called. – Rahul Khurana Sep 16 '19 at 10:49
  • So, the super.onDestroy() creates others reference of those ImageViews? – user2342558 Sep 16 '19 at 10:53
  • @user2342558 No, when the super got called it destroy all the views references attached to the activity/fragment – Rahul Khurana Sep 16 '19 at 10:54
  • If it's true, why on an Android 6.0 device it works without exception? – user2342558 Sep 16 '19 at 11:00
  • @user2342558 because of [Memory Management](https://developer.android.com/topic/performance/memory-overview). See here https://stackoverflow.com/questions/14375947/is-there-a-minimal-heap-size-for-android-versions – Rahul Khurana Sep 16 '19 at 11:05
  • So, the cause is that the device with Android 4.4.2 has a lower memory heap and then the GC act after the Activity is destroyed? Instead in the device with Android >5 the GC does not act because the memory heap isn't reached, leaving them in the memory? – user2342558 Sep 16 '19 at 11:26
  • @user2342558 yep, upto some extent you're right. With the increase in OS versions they are reducing given memory limit to each app. Recently in Android 10 they have introduced Scoped storage as additional step. – Rahul Khurana Sep 16 '19 at 12:38
1

According to the documentation for recycle

The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap.

I cant see how you are assigning your bitmaps to your ImageView, but I assume you are trying to reuse the bitmaps when you start the intent again after they have been recycled. I only ran into the exception if I was using android:src=. If I set the ImageView bitmap using the following in oncreate, it ran fine on all targets you listed without throwing an exception.

imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.lake_park));

I highly recommend using glide to work with images. https://github.com/bumptech/glide

  • If you don't want to use a 3rd party library then try using https://developer.android.com/reference/java/lang/ref/WeakReference for bitmaps with LRU or disk cache as mentioned in https://developer.android.com/topic/performance/graphics/cache-bitmap – Susheel Tickoo Sep 17 '19 at 04:58
  • @MatthewWilliams I just tested the same behaviour: if I remove the `android:src` the exception does not happen. Now, did you know why? I suppose that it's because setContentView invokes getPixels()/setPixels() on the ImageView defined with `android:src` but it's recycled; instead defining the new bitmap through code in `oncreate` these methods aren't invoked. Isn't true? – user2342558 Sep 17 '19 at 20:21
  • Yeah I think what you are saying is right. I can't say for sure, but it seems the `Drawable` assigned to an `ImageView` with `android:src` is not recreated (if recycled) when you start the activity again. I assume that `Drawable` is allocated only once at the start of the program? – Matthew Williams Sep 17 '19 at 21:46
1

Actually I think you don't have to do this manually.

If Activity2 only has 1 image and already resize I think recycle can't help much if you really face memory issue.

And according the this doc only API level lower than 10 is recommended to use recycle(), and the ratio of user are quite small. On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. https://developer.android.com/topic/performance/graphics/manage-memory

And I would like to recommend use third party image library as they can free you from do this meaningless things to let you focus on more important part of your app.

Jintin
  • 1,426
  • 13
  • 22
  • The documentation recommends to use recycle() for Android API level <=10, but it does not discourage its use with API > 10. In other many SO answers, it's suggesting to use recycle() in my manner to free bitmap resource when the GC acts. – user2342558 Sep 17 '19 at 08:46
  • Yes, it don't reveal recommend or not when API > 10 but I think it's not that important to do so if it's only benefit to early GC(If it's important for all version it will not indicate the `On Android 2.3.3 (API level 10) and lower`). And it's require lots of boilerplate code and hard to read and maintain as well. If we can leverage on other library to do that, why not? ^^ – Jintin Sep 17 '19 at 09:04