gpt4 book ai didi

Android: AlertDialog 导致内存泄漏

转载 作者:可可西里 更新时间:2023-11-01 18:57:24 35 4
gpt4 key购买 nike

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

One instance of "com.android.internal.app.AlertController$RecycleListView" loaded by "<system class loader>" occupies ...

我花了很多时间寻找这次泄漏的原因。代码审查对我没有帮助,我开始谷歌搜索。这就是我发现的:

Issue 5054: AlertDialog seems to cause a memory leak through a Message in the MessageQueue

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

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

每次关闭 AlertDialogLeakedActivity 时,MAT 都会报告此应用程序泄漏 com.android.internal.app.AlertController$RecycleListView完成。

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

最佳答案

(2/12/2012):请参阅下面的更新。

这个问题实际上不是由AlertDialog引起的,而是与ListView有关。您可以使用以下 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。

我没有时间进一步调查真正的原因(我知道发生了什么但不清楚为什么发生;可能是错误,或者设计)。但这里有一个您可以执行的解决方法,至少可以避免您的情况出现 OOM。

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

然后,在泄漏 Activity 的onDestroy()中,设置AlertDialogListViewonItemClickListener()null,这将释放对监听器的引用,并使在该监听器中分配的任何内存都符合 GC 的条件。这样就不会OOM了。这只是一个解决方法,真正的解决方案实际上应该合并到 ListView 中。

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

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

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

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。

解决方案是中断对监听器(垃圾所有者)的任何引用,在本例中是从按钮中断,这实际上使监听器成为 GCroot,只有很短的路径指向垃圾并使GC 决定更快地回收垃圾内存。这可以在 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 导致内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7083441/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com