My application shows an AlertDialog with a ListView inside. Everything worked fine bun then I decided to test this for memory leaks. After running the app for some time I opened MAT and generated Leak Suspects report. MAT found several similar leaks:
One instance of “com.android.internal.app.AlertController$RecycleListView” loaded by “<system class loader>” occupies …
I spent a lot of time searching for the reason of this leak. Code review didn’t help me and I started googling. That’s what I found:
Issue 5054: AlertDialog seems to cause a memory leak through a Message in the MessageQueue
I decided to check whether this bug reproduces or not. For this purpose I created a little program which consists of two activities. MainActivity is an enrty point. It contains only a buttons which runs LeakedActivity. The latter just shows an AlertDialog in its onCreate() method. Here’s the code:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
new Intent(MainActivity.this, LeakedActivity.class));
}
});
}
}
public class LeakedActivity extends Activity {
private static final int DIALOG_LEAK = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
showDialog(DIALOG_LEAK);
}
}
@Override
protected Dialog onCreateDialog(int id) {
if (id == DIALOG_LEAK) {
return new AlertDialog.Builder(this)
.setTitle("Title")
.setItems(new CharSequence[] { "1", "2" },
new OnClickListener() {
private final byte[] junk = new byte[10*1024*1024];
@Override
public void onClick(DialogInterface dialog, int which) {
// nothing
}
})
.create();
}
return super.onCreateDialog(id);
}
}
MAT reports this application leaks com.android.internal.app.AlertController$RecycleListView every time the AlertDialog is dismissed and the LeakedActivity is finished.
I can’t find any error in this small program. It looks like a very simple case of using AlertDialog and it must work well but seems it doesn’t. So I’d like to know how to avoid memory leaks when using AlertDialogs with items. And why hasn’t this problem been fixed yet? Thanks in advance.
(2/12/2012): see UPDATE below.
This problem is not actually caused by the
AlertDialogbut more related to theListView. You can reproduce the same problem by using the following activity:Rotate the device several times, and you’ll get OOM.
I haven’t got the time to investigate more on what is the real cause (I know what’s happening but not clear why it’s happening; can be bug, or designed). But here’s one workaround that you can do, at least to avoid the OOM in your case.
First, you’ll need to keep a reference to your leaked
AlertDialog. You can do this in theonCreateDialog(). When you’re usingsetItems(), theAlertDialogwill internally create aListView. And when you set theonClickListener()in yoursetItems()call, internally it will be assigned to theListViewonItemClickListener().Then, in the leaked activity’s
onDestroy(), set theAlertDialog‘sListView‘sonItemClickListener()tonull, which will release the reference to the listener an make whatever memory allocated within that listener to be eligible for GC. This way you won’t get OOM. It’s just a workaround and the real solution should actually be incorporated in theListView.Here’s a sample code for your
onDestroy():UPDATE (2/12/2012): After further investigation, this problem is actually not particularly related to
ListViewnor toOnItemClickListener, but to the fact that GC doesn’t happen immediately and need time to decide which objects are eligible and ready for GC. Try this:Rotate a couple of times, and you’ll get the OOM. The problem is after you rotate, the
junkis still retained because GC hasn’t and can’t happen yet (if you use MAT, you’ll see that thisjunkis still retained by the button’s listener deep down from the GCroot, and it will take time for the GC to decide whether thisjunkis eligible and can be GCed.) But at the same time, a newjunkneeds to be created after the rotation, and because of the mem alloc size (10M per junk), this will cause OOM.The solution is to break any references to the listener (the
junkowner), in this case from the button, which practically makes the listener as a GCroot with only short path to the junk and make the GC decide faster to reclaim the junk memory. This can be done in theonDestroy():