且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

安卓:AlertDialog导致内存泄漏

更新时间:2023-02-06 12:49:10

(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();
}