Android:AlertDialog 导致内存泄漏 [英] Android: AlertDialog causes a memory leak

查看:57
本文介绍了Android:AlertDialog 导致内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序显示了一个 AlertDialog,里面有一个 ListView.一切正常,然后我决定测试它是否存在内存泄漏.运行应用程序一段时间后,我打开了 MAT 并生成了 Leak Suspects 报告.MAT 发现了几个类似的漏洞:

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:

"<system class loader>" 加载的 "com.android.internal.app.AlertController$RecycleListView" 的一个实例占用...

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:

问题 5054:AlertDialog 似乎通过 MessageQueue 中的 Message 导致内存泄漏

我决定检查这个错误是否重现.为此,我创建了一个包含两个活动的小程序.MainActivity 是一个入口点.它只包含一个运行 LeakedActivity 的按钮.后者只是在其 onCreate() 方法中显示了一个 AlertDialog.代码如下:

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 报告此应用程序泄漏 com.android.internal.app.AlertController$RecycleListView 每次 AlertDialog 被解除并且 LeakedActivity 是完成.

MAT reports this application leaks com.android.internal.app.AlertController$RecycleListView every time the AlertDialog is dismissed and the LeakedActivity is finished.

我在这个小程序中找不到任何错误.它看起来是使用 AlertDialog 的一个非常简单的例子,它必须运行良好,但似乎没有.所以我想知道如何在将 AlertDialog 与项目一起使用时避免内存泄漏.为什么这个问题还没有解决?提前致谢.

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):请参阅下面的更新.

这个问题实际上不是由AlertDialog引起的,而是与ListView有关.您可以使用以下活动重现相同的问题:

This problem is not actually caused by the AlertDialog but more related to the ListView. You can reproduce the same problem by using the following activity:

public class LeakedListActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Use an existing ListAdapter that will map an array
    // of strings to TextViews
    setListAdapter(new ArrayAdapter<String>(this,
            android.R.layout.simple_list_item_1, mStrings));
    getListView().setOnItemClickListener(new OnItemClickListener() {
        private final byte[] junk = new byte[10*1024*1024];
        @Override
        public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                long arg3) {
        }
    });     
}
    private String[] mStrings = new String[] {"1", "2"};
}

将设备旋转几次,就会出现 OOM.

Rotate the device several times, and you'll get OOM.

我没有时间更多地调查真正的原因(我知道发生了什么,但不清楚为什么发生;可能是错误,或者设计).但这里有一种解决方法,您可以这样做,至少可以避免在您的情况下出现 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.

首先,您需要保留对泄露的AlertDialog 的引用.您可以在 onCreateDialog() 中执行此操作.当您使用 setItems() 时,AlertDialog 将在内部创建一个 ListView.当您在 setItems() 调用中设置 onClickListener() 时,它会在内部分配给 ListView onItemClickListener().

First, you'll need to keep a reference to your leaked AlertDialog. You can do this in the onCreateDialog(). When you're using setItems(), the AlertDialog will internally create a ListView. And when you set the onClickListener() in your setItems() call, internally it will be assigned to the ListView onItemClickListener().

然后,在泄露的activity的onDestroy()中,设置AlertDialogListViewonItemClickListener()code> 到 null,这将释放对侦听器的引用,并使该侦听器内分配的任何内存都符合 GC 的条件.这样你就不会出现 OOM.这只是一个解决方法,真正的解决方案实际上应该包含在 ListView 中.

Then, in the leaked activity's onDestroy(), set the AlertDialog's ListView's onItemClickListener() to null, 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 the ListView.

这是您的 onDestroy() 的示例代码:

Here's a sample code for your onDestroy():

@Override
protected void onDestroy() {
    super.onDestroy();
    if(leakedDialog != null) {
            ListView lv = leakedDialog.getListView();
            if(lv != null)  lv.setOnItemClickListener(null);
    }
}

UPDATE (2/12/2012):经过进一步调查,这个问题其实与ListViewOnItemClickListener并没有特别的关系,但事实上 GC 不会立即发生,需要时间来决定哪些对象符合条件并准备好进行 GC.试试这个:

UPDATE (2/12/2012): After further investigation, this problem is actually not particularly related to ListView nor to OnItemClickListener, 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:

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // this will create reference from button to 
        // the listener which in turn will create the "junk"
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            private byte[] junk = new byte[10*1024*1024];
            @Override
            public void onClick(View v) {
                // do nothing
            }
        });
    }
}

旋转几次,你就会得到 OOM.问题是你旋转后,junk 仍然保留,因为 GC 还没有发生,也不会发生(如果你使用 MAT,你会看到这个 junk按钮的监听器仍然保留在 GCroot 深处,GC 需要时间来决定这个 junk 是否符合条件并且可以被 GC.)但同时,一个新的 junk 需要在轮换后创建,并且由于mem alloc大小(每个垃圾10M),这会导致OOM.

Rotate a couple of times, and you'll get the OOM. The problem is after you rotate, the junk is still retained because GC hasn't and can't happen yet (if you use MAT, you'll see that this junk is still retained by the button's listener deep down from the GCroot, and it will take time for the GC to decide whether this junk is eligible and can be GCed.) But at the same time, a new junk needs to be created after the rotation, and because of the mem alloc size (10M per junk), this will cause OOM.

解决方案是中断对侦听器(junkowner)的任何引用,在这种情况下是从按钮开始的,这实际上使侦听器成为 GCroot,只有很短的路径到垃圾,并使GC 决定更快地回收垃圾内存.这可以在 onDestroy() 中完成:

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 the onDestroy():

@Override
protected void onDestroy() {
    // this will break the reference from the button
    // to the listener (the "junk" owner)
    findViewById(R.id.button).setOnClickListener(null);
    super.onDestroy();
}

这篇关于Android:AlertDialog 导致内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆