0

I wrote a method in order to resize drawables but I found it leaks. It's implemented in SinglePlayer activity of my game, so when I restart that activity (the Play Again button) the available heap size is getting reduced until it finally crashes. I want to close an activity and have no traces left. I'm certain the ResizeDrawable method is the only thing causing crashes because when I remove it, the problem is solved. Here is the code:

 //They don't have to be global, I guess it makes no difference
Bitmap temp;
Bitmap resbit;
Drawable resized;

Drawable ResizeDrawable(Drawable image,int width,int height) 
{
    temp = ((BitmapDrawable)image).getBitmap();
    resbit = Bitmap.createScaledBitmap(temp, width, height, true);
    resized = new BitmapDrawable(getResources(), resbit ); 
    return resized;
}

So why does this fill the memory up and how can I modify this method or an entire activity in order to free all the memory once the activity is closed?

UPDATE:

I tried using recycle() method. As far as I understood recycling frees the memory of the bitmap so it can be used again with no leaks. I added these lines:

            if (temp != null) 
        {
            temp.recycle();
            temp = null;
        }
        if (resbit != null) 
        {
            resbit .recycle();
            resbit  = null;
        }

1) At the beggining of the ResizeDrawable method 2) Before return statement

Both edits resulted in a "trying to use a recycled bitmap" crash. Where did I go wrong?

UPDATE:

I tried removing the "resbit recycle" part. Now I was able to restart my activity exactly 2 times and then it crashed with the same exception. How is that possible?

UPDATE:

I tried overriding OnDestroy() with:

Runtime.getRuntime().gc(); 

and:

finish();

Nothing changed. Also tried overriding OnDestroy() with the recycles. Same "exactly 2 times" crash.

LOGS:

So the original code crashes after exactly 16 restarts of the SinglePlayer activity. The log:

11-16 03:46:31.115: E/AndroidRuntime(9649): FATAL EXCEPTION: main
11-16 03:46:31.115: E/AndroidRuntime(9649): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.starena/com.example.starena.SinglePlayer}: android.view.InflateException: Binary XML file line #40: Error inflating class <unknown>
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1659)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1675)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.ActivityThread.access$1500(ActivityThread.java:121)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:943)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.os.Handler.dispatchMessage(Handler.java:99)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.os.Looper.loop(Looper.java:130)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.ActivityThread.main(ActivityThread.java:3701)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at java.lang.reflect.Method.invokeNative(Native Method)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at java.lang.reflect.Method.invoke(Method.java:507)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:624)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at dalvik.system.NativeStart.main(Native Method)
11-16 03:46:31.115: E/AndroidRuntime(9649): Caused by: android.view.InflateException: Binary XML file line #40: Error inflating class <unknown>
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.view.LayoutInflater.createView(LayoutInflater.java:518)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:568)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.view.LayoutInflater.rInflate(LayoutInflater.java:623)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:227)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.Activity.setContentView(Activity.java:1657)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at com.example.starena.SinglePlayer.onCreate(SinglePlayer.java:177)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1623)
11-16 03:46:31.115: E/AndroidRuntime(9649):     ... 11 more
11-16 03:46:31.115: E/AndroidRuntime(9649): Caused by: java.lang.reflect.InvocationTargetException
11-16 03:46:31.115: E/AndroidRuntime(9649):     at java.lang.reflect.Constructor.constructNative(Native Method)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at java.lang.reflect.Constructor.newInstance(Constructor.java:415)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.view.LayoutInflater.createView(LayoutInflater.java:505)
11-16 03:46:31.115: E/AndroidRuntime(9649):     ... 22 more
11-16 03:46:31.115: E/AndroidRuntime(9649): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:494)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:370)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:715)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.content.res.Resources.loadDrawable(Resources.java:1720)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.content.res.TypedArray.getDrawable(TypedArray.java:601)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.widget.ImageView.<init>(ImageView.java:122)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.widget.ImageButton.<init>(ImageButton.java:85)
11-16 03:46:31.115: E/AndroidRuntime(9649):     at android.widget.ImageButton.<init>(ImageButton.java:81)
11-16 03:46:31.115: E/AndroidRuntime(9649):     ... 25 more

How I'm using it:

So in my OnCreate() method I have initialized several ImageButtons and several Drawables they will use. It looks like this:

Drawable1 = context.getResources().getDrawable(R.drawable.drawable1);   
Drawable1 = ResizeDrawable(Drawable1,ButtonWidth,ButtonHeight);
ImgButton1 = (ImageButton) findViewById(R.id.ImageButton02);
ImgButton1.setOnClickListener(this);
ImgButton1.setImageDrawable(Drawable1);

Also, every Image Button has two drawables and I alternate between them via the OnClick() method( I just setImageDrawable() one or another).

I hope this helps.

SOLVED: I had to overide OnDestroy with:

Drawable1 = null;
ImgButton1.setImageDrawable(null);

Of course I did it for every (ImgButton,Drawable) pair and it stopped consuming memory. I didn't recycle bitmaps.

  • Can't tell based on what you've posted. – duffymo Nov 16 '13 at 02:04
  • What info do you need? I can't paste my whole activity here. – RandomUsername Nov 16 '13 at 02:04
  • If you move the variable declarations within the method does it still leak? – doublesharp Nov 16 '13 at 02:05
  • From what you give, I can only say,use recycle method of bitmaps. – Devrim Nov 16 '13 at 02:06
  • @doublesharp It does. when do I use a recycle method? I guess I should override OnDestoy() – RandomUsername Nov 16 '13 at 02:10
  • Check out http://stackoverflow.com/questions/3823799/android-bitmap-recycle-how-does-it-work – doublesharp Nov 16 '13 at 02:14
  • @doublesharp I added more info, it doesn't seem to work. – RandomUsername Nov 16 '13 at 02:23
  • 1
    Do you actually mean stack size, or do you mean heap size? Also, show us the exception and its full trace. – Jason C Nov 16 '13 at 02:38
  • Heap, of course. Sorry for the typo. Do I copy the logcat exception? I have little experience regarding the logcat. – RandomUsername Nov 16 '13 at 02:41
  • 1
    Sure, show us that. Also, btw, if you are out of heap space, you are out of heap space, and no amount of forcing (well, technically its just "suggesting") the GC to run will change that -- it runs automatically. Show us how you are using `ResizeDrawable`. Are you keeping unused references to the `Drawable` it returns around somewhere (preventing the GC from discarding old unused objects)? Perhaps strategic print outs of `Runtime.freeMemory()` will help you trace the culprit? – Jason C Nov 16 '13 at 02:45
  • 1
    Adding to keeping unused references around: Look carefully, it could be tricky. Perhaps you store the `Drawable` as a member field of some class that you, say, register as some event handler or store in a list or as another field somewhere. Look at *every* place you store a reference to that `Drawable` and think carefully about whether or not you are keeping references around unnecessarily. Perhaps judicious resets to `null` or use of `WeakReference`s when appropriate may be a solution. – Jason C Nov 16 '13 at 02:47
  • 1
    BTW, you wrote *"Both edits resulted in a "trying to use a recycled bitmap" crash. Where did I go wrong?"*. This implies that the Drawables are still being used somewhere after *you* think you are done with them, which is also evidence that points to references being kept around unintentionally. – Jason C Nov 16 '13 at 02:50
  • @JasonC The Drawables? If I recycle the Bitmaps how can the "drawables being used" be a problem? The bitmaps are used only in ResizeDrawables method, and nowhere else. I understand still using Drawables somewhere can be a problem in general, but when we talk about recycling bitmaps, why is it an issue? I have added some more info so you can help me find the mistake. – RandomUsername Nov 16 '13 at 02:53
  • 1
    Ok, then, the Bitmaps. The point is, if you recycled the bitmaps at the point you thought you were done with them, but you still got an error about using recycled bitmaps, then one of the objects you were creating was still in use after you thought you were done with it. The implication, then, is that something you are unaware of is holding references to objects that you think you are done with, preventing them from being GC'd. `resized` holds a reference to `resbit`, which may also hold a reference to `temp` depending on how it is implemented internally. – Jason C Nov 16 '13 at 02:57
  • Okay, thanks for your help. I have added the solution in my post (write it as an answer so I can put a tick on it). I have now fully understood how GC works and how some stuff remains linked even when the activity stops. Your comments were very helpful, cheers :) – RandomUsername Nov 16 '13 at 03:18
  • Btw. It turned out I never had to use Bitmap.recycle(). How is that possible? The GC picks Bitmaps when they are not linked anywhere? What is the purpose of recycling then? Thanks again. – RandomUsername Nov 16 '13 at 03:19

1 Answers1

2

Try to call Bitmap.recycle() for unused Bitmap instances.

Bitmap.recycle()
http://developer.android.com/reference/android/graphics/Bitmap.html#recycle()

Takahiko Kawasaki
  • 18,118
  • 9
  • 62
  • 105