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

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

问题描述

我的应用程序显示了一个 AlertDialog 的ListView 里面。一切正常的发髻,然后我决定测试此内存泄漏。运行该应用程序一段时间后,我打开 MAT 并产生泄漏犯罪嫌疑人的报告。 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:

com.android.internal.app.AlertController $ RecycleListView一个实例<系统类加载器>加载占据...

我花了很多时间来搜索这个泄漏的原因。 code审查并没有帮助我,我开始使用Google。这就是我发现:

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消息导致内存泄漏

我决定去检查这个bug是否再现与否。为此,我创建了一个小程序,它由两个活动。 MainActivity 是一个enrty点。它仅包含一个按钮,它运行 LeakedActivity 。后者只是表明了 AlertDialog 的onCreate()方法。这里的code:

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 s的物品,以避免内存泄漏。为什么没有这个问题得到解决了吗?先谢谢了。

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.

推荐答案

(2012/2/12):请参见更新下面

这个问题实际上不是由 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.

我还没有开始研究更多关于什么是真正的原因的时候(我知道的什么的发生,但尚不清楚的为什么的,它的发生;可进行bug,或者设计)。但是,这里有一个解决方法,你可以做的,至少避免了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 。而当你设置 onClickListener() setItems()通话,在内部将被分配到的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().

然后,在放风活动的的onDestroy(),将 AlertDialog ListView控件 onItemClickListener(),它会释放出参考监听器无论内存侦听器内分配的补充,才有资格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.

下面是一个示例code为你的的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);
    }
}

更新(2012/2/12):经过进一步调查,这个问题其实并没有特别涉及到的ListView 也没有 OnItemClickListener ,但这样的事实,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。问题是你旋转之后,垃圾仍然保留,因为GC没有也不会发生,但(如果使用脚垫,你会看到这个垃圾仍由按钮的监听器保留从GCroot内心深处,这将需要一段时间的GC决定是否该垃圾是合格的,可以是GCed。)但在同一时间,一个新的垃圾需要旋转后才能创建,因为纪念品页头大小每个垃圾(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.

解决的办法就是要打破任何引用到监听器(在垃圾所有者),从按钮,这实际上让听者如同只有短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();
}

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

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