Android:AlertDialog 导致内存泄漏

16

我的应用程序使用AlertDialog展示包含ListView的内容。一切都运行良好,但之后我决定测试内存泄漏。在运行了一段时间的应用程序后,我打开了MAT并生成了泄漏嫌疑报告。MAT发现了几个类似的泄漏:

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

我花费了很多时间寻找这个泄漏的原因。代码审查没有帮助我,于是我开始搜索。这就是我找到的:

问题5054:AlertDialog通过MessageQueue可能导致内存泄漏

我决定检查此错误是否会复制。为此,我创建了一个由两个活动组成的小程序。 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);
    }
}

MAT报告说每次关闭AlertDialog并完成LeakedActivity时,此应用程序会泄漏com.android.internal.app.AlertController$RecycleListView

我找不到这个小程序中的任何错误。看起来使用AlertDialog是一个非常简单的情况,应该能够正常工作,但实际上似乎并不是这样。因此,我想知道在使用带有项目的AlertDialog时如何避免内存泄漏。为什么这个问题还没有被修复呢?谢谢。

4个回答

29

(2012年2月12日): 请查看下面的更新。

这个问题实际上并不是由 AlertDialog 引起的,而更多与 ListView 相关。您可以通过使用以下活动复现相同的问题:

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(内存不足)。

我没有时间深入调查真正的原因(我知道正在发生什么,但不清楚为什么会发生;可能是bug或设计问题)。但是这里有一个解决方法,至少可以避免你的情况下出现OOM。

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

然后,在泄漏活动的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);
    }
}

更新(2012年2月12日): 经过进一步调查,发现这个问题实际上与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)。但与此同时,旋转后需要创建一个新的junk,由于内存分配大小(每个junk为10M),这将导致OOM。

解决方法是断开对监听器(junk所有者)的任何引用,在这种情况下是从按钮中断开,这实际上使监听器成为只有通向垃圾并且路径短暂的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();
}

2
当然可以。我的观点是,stackoverflow上有很多问题都有好的答案,但并没有标记为已回答(答案未被采纳)。如果每个提问者都能负起责任将答案标记/采纳,那对于遇到类似问题的其他人来说就更有帮助了,也为社区做出了贡献。 - Ricky Lee
1
你的回答是最好的,所以赏金归你所有。 - Michael
1
嗨,在我的情况下,问题并不在于SetOnClickListiner,而是关于每个ListViewItem中使用的Views和Image资源,我该如何解决这个问题呢? - Ata
@Ata 我也遇到了你的问题,你找到任何解决办法了吗? - user4o01
@user4o01,我记不清了,因为这太旧了,但我认为你必须将图像置空,imgView.setImageBitmap(null); 这会导致移除已加载的图像指针,当GC被调用时,它会释放图像资源。 - Ata
显示剩余2条评论

1

无法在 Android 2.3.3 上重现。我在实际设备上测试了您的代码,并从 LogCat 中看到堆大小随时间保持恒定。不幸的是,我无法对我的转储进行 hprof-conf(错误:期望 1.0.3)。

08-19 08:41:58.026: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 13K, 50% free 2698K/5379K, external 83K/519K, paused 16ms
08-19 08:41:58.056: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1K, 43% free 3720K/6471K, external 83K/519K, paused 18ms
08-19 08:45:30.113: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1076K, 58% free 2723K/6471K, external 595K/1042K, paused 18ms
08-19 08:45:30.143: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3740K/6471K, external 507K/1019K, paused 19ms
08-19 08:45:35.869: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1087K, 58% free 2726K/6471K, external 595K/1019K, paused 18ms
08-19 08:45:35.899: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3744K/6471K, external 552K/1019K, paused 18ms
08-19 08:45:39.112: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2734K/6471K, external 595K/1019K, paused 17ms
08-19 08:45:39.152: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3752K/6471K, external 530K/1019K, paused 20ms
08-19 08:46:14.186: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2736K/6471K, external 595K/1019K, paused 23ms
08-19 08:46:14.216: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3755K/6471K, external 552K/1019K, paused 21ms
08-19 08:46:16.519: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1092K, 58% free 2736K/6471K, external 595K/1019K, paused 23ms
08-19 08:46:16.549: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3753K/6471K, external 530K/1019K, paused 22ms
08-19 08:47:15.686: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:47:15.716: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 3K, 42% free 3756K/6471K, external 561K/1019K, paused 18ms
08-19 08:48:01.391: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2736K/6471K, external 595K/1019K, paused 19ms
08-19 08:48:01.421: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3753K/6471K, external 530K/1019K, paused 19ms
08-19 08:48:09.409: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2737K/6471K, external 758K/1019K, paused 18ms
08-19 08:48:09.449: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3756K/6471K, external 561K/1019K, paused 21ms
08-19 08:48:11.771: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1076K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:48:11.811: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 530K/1019K, paused 20ms
08-19 08:48:13.653: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2737K/6471K, external 595K/1019K, paused 18ms
08-19 08:48:13.683: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 3K, 42% free 3758K/6471K, external 561K/1019K, paused 19ms
08-19 08:48:15.785: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:48:15.825: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 530K/1019K, paused 19ms
08-19 08:48:18.227: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1088K, 58% free 2737K/6471K, external 595K/1019K, paused 19ms
08-19 08:48:18.257: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3756K/6471K, external 552K/1019K, paused 20ms
08-19 08:49:06.575: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1075K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:06.605: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 530K/1019K, paused 17ms
08-19 08:49:09.668: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1097K, 58% free 2729K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:09.708: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 43% free 3748K/6471K, external 552K/1019K, paused 20ms
08-19 08:49:12.440: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:12.470: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3753K/6471K, external 530K/1019K, paused 17ms
08-19 08:49:15.473: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1088K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:15.503: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3756K/6471K, external 561K/1019K, paused 17ms
08-19 08:49:18.476: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1091K, 58% free 2737K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:18.506: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 507K/1019K, paused 20ms
08-19 08:49:21.289: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1090K, 58% free 2737K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:21.319: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 484K/996K, paused 20ms
08-19 08:51:43.307: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1071K, 58% free 2723K/6471K, external 595K/996K, paused 17ms
08-19 08:51:43.338: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed <1K, 43% free 3747K/6471K, external 595K/996K, paused 20ms
08-19 08:51:45.620: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1086K, 58% free 2729K/6471K, external 595K/974K, paused 18ms
08-19 08:51:45.660: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 7K, 43% free 3745K/6471K, external 462K/974K, paused 20ms
08-19 08:51:47.421: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1080K, 58% free 2738K/6471K, external 595K/974K, paused 17ms
08-19 08:51:47.452: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3755K/6471K, external 484K/974K, paused 19ms
08-19 08:52:56.949: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 27K, 58% free 2733K/6471K, external 83K/595K, paused 18ms
08-19 08:52:56.979: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed <1K, 42% free 3757K/6471K, external 83K/595K, paused 17ms
08-19 08:53:01.233: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1072K, 58% free 2727K/6471K, external 595K/1107K, paused 18ms
08-19 08:53:01.274: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 2K, 43% free 3749K/6471K, external 578K/1090K, paused 20ms
08-19 08:53:04.046: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1081K, 58% free 2740K/6471K, external 595K/1064K, paused 18ms
08-19 08:53:04.086: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3758K/6471K, external 530K/1042K, paused 20ms
08-19 08:53:25.948: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1090K, 58% free 2740K/6471K, external 595K/1042K, paused 19ms
08-19 08:53:25.978: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3758K/6471K, external 552K/1042K, paused 19ms
08-19 08:57:51.246: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1099K, 58% free 2753K/6471K, external 595K/1042K, paused 18ms
08-19 08:57:51.286: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 13K, 42% free 3764K/6471K, external 561K/1042K, paused 20ms
08-19 09:00:47.699: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1106K, 58% free 2731K/6471K, external 595K/1019K, paused 18ms
08-19 09:00:47.729: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 7K, 43% free 3748K/6471K, external 484K/996K, paused 17ms
08-19 09:00:56.817: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1080K, 58% free 2741K/6471K, external 595K/996K, paused 18ms
08-19 09:00:56.848: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3758K/6471K, external 530K/996K, paused 23ms
08-19 09:01:00.701: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2739K/6471K, external 595K/996K, paused 19ms
08-19 09:01:00.731: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3756K/6471K, external 530K/996K, paused 22ms
08-19 09:01:25.916: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2739K/6471K, external 595K/996K, paused 18ms
08-19 09:01:25.946: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3756K/6471K, external 530K/996K, paused 20ms

谢谢你的回答。你在哪个设备上测试过它? - Michael
我已经检查了增加垃圾大小(5 MBytes)的代码。没有问题。最大堆大小(在我的情况下)报告为32 MByes(ActivityManager.getMemoryClass())。请记住,GC通常需要一些时间来回收内存。我不知道的是,内存块是否必须在一个块中,即未被分段。 - Horst Dehmer
仍然没有问题。请在日志中验证每次“翻转”大约释放了10 MBytes的空间。08-20 15:46:38.944: DEBUG/dalvikvm(13335): GC_FOR_MALLOC freed 12093K, 50% free 14843K/29575K, external 0K/0K, paused 80ms 08-20 15:46:44.169: DEBUG/dalvikvm(13335): GC_FOR_MALLOC freed 12100K, 50% free 14843K/29575K, external 0K/0K, paused 68ms 08-20 15:46:53.498: DEBUG/dalvikvm(13335): GC_FOR_MALLOC freed 12099K, 50% free 14843K/29575K, external 0K/0K, paused 69ms - Horst Dehmer
1
完成:8 * 1024 * 1024。已经旋转30次,没有任何问题。内存被快速回收。即使有2或3个累计大小小于11兆字节的缓冲区也能正常工作。您可能还对“Google I/O 2011:Android应用程序的内存管理”[链接](http://www.youtube.com/watch?v=_CruQY55HOk)感兴趣。 - Horst Dehmer
为了确保我们使用相同的代码,请查看我的Git存储库git://github.com/dehmer/android.git。项目名为'com.frequentis.droid.alert'。 - Horst Dehmer
显示剩余8条评论

0
我已运行你的代码。当我第一次按按钮时,它显示了一个带有对话框的LeakedActivity,然后在点击后删除了对话框,但活动仍停留在前台且屏幕为黑色。按下返回键然后再次启动该活动,则会显示内存不足异常:
ERROR/AndroidRuntime(263): java.lang.OutOfMemoryError

然后我从对话框代码中删除了这一行 private final byte[] junk = new byte[10*1024*1024];,之后就没有出现这样的问题了...不知道为什么,如果有人能把这个事情说清楚,感谢他 / 她。
I tested the code in emulator android 2.1

是的,我可以告诉你原因。那是因为我问过的内存泄漏问题。junk字段被添加以更快地填充堆。它必须在LeakedActivity完成后进行垃圾回收,但实际上并没有。这就是问题所在。 - Michael

-2

你试过这个吗?正如你所看到的,这个对话框是由活动管理的,当活动被销毁时,它会自动关闭。 - Michael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接