【问题标题】:Android: AlertDialog causes a memory leakAndroid:AlertDialog 导致内存泄漏
【发布时间】:2011-10-28 08:19:17
【问题描述】:

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

""加载的"com.android.internal.app.AlertController$RecycleListView"的一个实例占用...

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

Issue 5054: AlertDialog seems to cause a memory leak through a Message in the 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);
    }
}

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

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

【问题讨论】:

    标签: android memory-leaks android-alertdialog


    【解决方案1】:

    (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。

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

    UPDATE (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。

    解决方案是中断对侦听器(junkowner)的任何引用,在本例中是从按钮中,这实际上使侦听器成为 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();
    }
    

    【讨论】:

    • 太棒了。您是否介意将您的问题标记为已回答,以便其他有类似问题的人可以轻松找到解决方案。
    • 当然可以,但要晚一点。如果四天内没有人给出更好的答案,那么我会接受你的答案并奖励它。
    • 当然可以。我的观点是,stackoverflow 中有很多问题的答案很好,但没有被标记为已回答(答案未被接受)。如果每个提问的人都有点负责并标记/接受答案,这对社区会有好处,这样对有类似问题的其他人会更有帮助。
    • 你的答案是最好的,所以赏金就是你的。
    • 你好,在我的情况下,不是关于 SetOnClickListiner ,而是关于每个 ListViewItem 中使用的 Views 和 Image 资源,我该如何解决这个问题?
    【解决方案2】:

    无法为 Android 2.3.4 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
    

    【讨论】:

    • 感谢您的回答。你在什么设备上测试过?
    • 我刚刚在带有 Android 2.3.4 的 Google Nexus S 上对其进行了测试,但它泄漏了。这很容易确定是否存在泄漏。用new byte[10*1204*1024] 初始化junk,运行LeakActivity 并旋转设备数次。第二次轮换后,该应用在 Nexus S 上因 OOM 而崩溃。
    • HTC Sensation w/Revolution HD 1.1.8。
    • 我检查了增加垃圾大小(5 MB)的代码。那里没问题。最大堆(在我的情况下)报告为 32 MByes(ActivityManager.getMemoryClass())。请记住,GC 通常需要一些时间来回收内存。我不知道的是,如果内存块必须在一个块中,即未分段。
    • 完成:8 * 1024 * 1024。改变方向 30 次没有任何问题。内存被迅速回收。即使有 2 或 3 个累积大小 link 感兴趣
    【解决方案3】:

    我已经运行了您的代码,当我第一次按下按钮时,它显示带有对话框的 LeakedActivity,而 onClick 正在删除对话框,但该活动仍处于前台并显示黑屏。在按下返回键然后再次启动活动时,它显示内存不足错误异常:

    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 完成后进行 GC,但事实并非如此。这就是重点。
    【解决方案4】:

    您需要从 onClick 方法中关闭/取消对话框,如下面的示例所示:Alert Dialogs in Android

    【讨论】:

    • 你试过了吗?如您所见,此对话框由 Activity 管理,并在 Activity 销毁时自动关闭。
    猜你喜欢
    • 2015-07-06
    • 2014-06-07
    • 2013-11-20
    • 2016-01-18
    • 2012-12-13
    • 1970-01-01
    • 2011-01-08
    • 2011-02-01
    • 2016-01-07
    相关资源
    最近更新 更多