Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Android leak memory due to static Drawable if it's callback is reset?

I was just following this article on how to avoid memory leaks : android developer blog Following is the code snippet used :

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);

  TextView label = new TextView(this);
  label.setText("Leaks are bad");

  if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);

  setContentView(label);
}

It is said that the drawable has a callback reference to textview (and indirectly to the activity); which will be preserved on rotation - and hence memory leak.

My query is that won't the drawable's callback be reset on rotation - it would get hold of the new textview (which will hold the new context).. hence allowing the previous instances of textview/context to be GC'ed.

EDIT : The answers I get are on how to "solve" the issue - I am not looking for that ! Please re-read the query. I am adding more details. When activity is launched, the references are :

Drawable1 -> TextView1 -> Activity1

When rotated, Activity1 and TextView1 are destroyed but not Drawable1

Drawable1 -> TextView2 -> Activity2

This means, Activity1 and TextView1 are free to be GC'ed - as no other object is having a reference to them. So what is leaking ?

Am I wrong in this understanding ? Or is it that the Drawable can have multiple views as callbacks ? (Looking at the source code, I dont see a list of callbacks on Drawable).

like image 467
dev Avatar asked Nov 22 '13 06:11

dev


1 Answers

If you rotate the device, the same MyActivity class (or whatever name you gave it) is recreated, the callback is overwritten and the leak exists until the next GC. The problem lies when you navigate to another activity, keeping a reference to the old one. Today, this is mitigated because the setCallback now stores the callback in a WeakReference as you can see in current Drawable code, but it was a strong reference once (search for setCallback(Callback cb)). Anyway, you're right, if you just look into one activity, the callback will be reset after rotating.

(edit, paragraph added): For example: MainAcivity@1 is the first instance. When you rotate, it's destroyed and a new MainActivity@2 is created. At first, there's a leak, but as soon as sDrawable is reassigned, MainActivity@1 is free to be collected and there's no problem. Now suppose that, instead of rotating, you navigate away to SecondActivity. Now, sDrawable is just for MainActivity and still holds a reference to MainActivity@2, so it leaks.

See this code:

package com.douglasdrumond.leaky;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.widget.TextView;

public class MainActivity extends Activity {
    private static Drawable sBackground;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextView label = new TextView(this);
        System.gc();
        long memory = Runtime.getRuntime().totalMemory()
                - Runtime.getRuntime().freeMemory();
        label.setText("Memory: " + memory / 1024f);

        if (sBackground == null) {
            sBackground = getResources().getDrawable(R.drawable.large_bitmap);
        }
        label.setBackgroundDrawable(sBackground);

        setContentView(label);
    }

}

Clearly, rotating doesn't increase memory usage.

like image 80
Douglas Drumond Kayama Avatar answered Oct 26 '22 20:10

Douglas Drumond Kayama