【问题标题】:Memory leak when using ListView in Android在 Android 中使用 ListView 时的内存泄漏
【发布时间】:2010-10-22 14:50:15
【问题描述】:

我正在努力解决与 ListView 相关的内存泄漏问题。我创建了以下展示这种行为的小程序。

我所做的是创建 2 个线性布局。第一个有一个 Button 和一个 GListView 控件。 GListView 的代码如下,但它只是 ListView 的子类,并实现了 ListAdapter 接口。创建 GListView 时,它会将其适配器设置为自身。

现在,当您按下按钮时,我切换到第二个 LinearLayout。此布局只有一个按钮。当您按下此按钮时,我会使用新的 GListView 创建一个新的第一个布局并将其设置为活动视图。

运行程序,在两个视图之间切换 20 次。然后启动 DDMS 并强制进行垃圾收集。然后转储内存,使用内存分析器,你会发现剩下 21 个 GListView 对象。也就是说,不再连接任何东西的 20 个 GListView 还没有被释放。

如果我进行内存转储并查看应该已回收的 GListView 之一,并使用内存分析器列出传入的引用,我会得到以下信息:

Class Name                                                              | Shallow Heap | Retained Heap 
-------------------------------------------------------------------------------------------------------
com.gabysoft.memoryleak.GListView @ 0x43e72270 Unknown                  |          672 |         3,528 
|- host android.view.View$ScrollabilityCache @ 0x43e72560               |           80 |           584 
|- this$0 android.widget.AbsListView$RecycleBin @ 0x43e72830            |           40 |           160 
|- mCallback android.graphics.drawable.StateListDrawable @ 0x43e728a8   |           64 |         1,464 
|- this$0 android.widget.AdapterView$AdapterDataSetObserver @ 0x43e730b8|           16 |            16 
-------------------------------------------------------------------------------------------------------

现在,如果我在 GListView 构造函数中注释掉 'setAdapter(this)' 函数并重复上述操作,我发现只剩下 1 个 GListView。也就是说,在这种情况下,所有未使用的 GListView 都已正确回收。

有人建议我在 GListView 中创建一个私有类来处理 ListAdapter 接口,我尝试过,但没有帮助。我也尝试过创建一个完全独立的公共类来处理 ListAdapter,但是,唉,这似乎也不起作用。

当这些对象不再在任何地方使用时,肯定有一些方法可以让它们消失。 (这不就是垃圾收集的全部内容吗?)

任何帮助将不胜感激。我真的在这个上拔了头发。

谢谢。

/*
 * Activity
 */

package com.gabysoft.memoryleak;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;

public class MemoryLeak extends Activity implements android.view.View.OnClickListener 
{
    LinearLayout ll2;
    boolean page2 = false;

    private LinearLayout CreateLayout()
    {
        LinearLayout ll = new LinearLayout(this);

        Button btn1 = new Button(this);
        ListView    lv    = new GListView(this);

        btn1.setText("Press");
        btn1.setLayoutParams(new LinearLayout.LayoutParams(100, 40));
        btn1.setOnClickListener(this);

        ll.addView(btn1);
        ll.addView(lv);

        return(ll);
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);

        CreateLayout();

        LinearLayout ll = CreateLayout();
        ll2 = new LinearLayout(this);

        Button btn2 = new Button(this);

        btn2.setText("Back");
        btn2.setLayoutParams(new LinearLayout.LayoutParams(100, 40));
        btn2.setOnClickListener(this);

        ll2.addView(btn2);

        setContentView(ll);
    }

    @Override
    public void onClick(View v) 
    {
        if (page2)
        {
            LinearLayout ll = CreateLayout();

            setContentView(ll);

            page2 = false;
        }
        else
        {
            setContentView(ll2);
            page2 = true;
        }
    }

}

/*
 * GListView
 */
package com.gabysoft.memoryleak;

import android.content.Context;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class GListView extends ListView implements ListAdapter
{
    Context m_context;
    DataSetObserver m_observer = null;

    public GListView(Context context) 
    {
        super(context);

        m_context    = context;

        setAdapter(this);

        setChoiceMode(CHOICE_MODE_SINGLE);
    }

    /*
     * ListAdapter 
     */


    @Override
    public boolean areAllItemsEnabled() 
    {
        return true;
    }

    @Override
    public boolean isEnabled(int position) 
    {
        return true;
    }

    @Override
    public int getCount() 
    {
        return(0);
    }    

    @Override
    public Object getItem(int position) 
    {
        return null;
    }

    @Override
    public long getItemId(int position) 
    {
        return(position);
    }

    @Override
    public int getItemViewType(int position) 
    {
        return 0;
    } 

    @Override
    public View getView(int position, View convertView, ViewGroup parent) 
    {
        TextView tv = new TextView(m_context);

        tv.setText("Item");

        return(tv);
    }

    @Override
    public int getViewTypeCount() 
    {
        return 1;
    }

    @Override
    public boolean hasStableIds() 
    {
        return false;
    }

    @Override
    public boolean isEmpty() 
    {
        return false;
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) 
    {
        m_observer    = observer;
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) 
    {
        m_observer    = null;
    }
}

【问题讨论】:

  • @John Gaby:发布一个 ZIP 文件的链接,该文件包含一个带有此代码的完整项目,我可以下载并使用它,我会看看它。
  • 感谢您的浏览。我看不到如何将文件与此评论一起发布,因此我将其上传到我的网站。您可以在gabysoft.com/download/memoryleak.zip 下载它。我正在使用 Android 2.2 在模拟器上运行它(尽管我相信它也发生在真实设备上)
  • @John Gaby:是的,你不能将文件附加到 SO 问题中,所以你在那里做的很好。给我几个小时,因为我有新书更新,我想先出门。
  • 谢谢,非常感谢您的帮助。我尝试了很多不同的东西,似乎无论我做什么,我都无法让我的 GListView 对象被回收。
  • 另请注意,实际应用程序要复杂得多,并且做的事情与这个简单示例中的有所不同。我在这里尝试做的是创建一个尽可能简单的程序,以展示我在完整应用程序中看到的行为。

标签: android memory-leaks


【解决方案1】:

您的GListView 对象正在被垃圾回收。至少,当从 DDMS 完成手动 GC 时,它们会以 finalize() 调用。不幸的是,DDMS HPROF 文件似乎与我的jhat 版本不兼容,而且我不是 Eclipse 用户,因此我无法访问 MAT。

【讨论】:

  • 奇怪,这不是我看到的行为。我向我的 GListView 类添加了一个覆盖 finalize 方法,但我只看到它调用过一次,即使我创建了许多需要回收的 GListView 对象。我将带有日志的版本上传到gabysoft.com/download/MemoryLeak.zip。有没有可能我看到这个是因为我使用的是 Eclipse 而你没有?
  • @John Gaby:我下载了您最新的 ZIP,编译并安装到 2.2 模拟器中,单击按钮 40 次,从 DDMS 进行手动 GC,并收到 21 条最终调试消息。您可能想尝试从 Eclipse 外部运行测试一次。为此,请转到您的项目目录,然后运行 ​​android update project -p .(假设您的 SDK tools/ 目录在您的 PATH 中)。如果没有 Apache Ant,请安装它,通过android 命令启动模拟器,然后运行ant clean install。通过ddms 运行 DDMS。
  • 再次感谢您的帮助。我将不得不追踪 Apache Ant 并尝试一下。我可以告诉你,即使在我的真实应用程序中,GListView 也是我问题的根源。我有一个包含 GListView 的页面,其中有一个大图形作为背景。如果我重新加载该页面的次数足够多,程序就会因内存不足错误而崩溃。我修复了它,以便从它的父 ViewGroup 中删除 GListView,虽然 GListView 没有被释放,但父视图是。进行此更改后,我可以多次加载页面而不会崩溃。我不明白为什么我会在 Eclipse 中看到这个。
  • @John Gaby:“如果我重新加载该页面的次数足够多,程序就会因内存不足而崩溃。” - 我的猜测是与您的“大图形作为背景”有关。尽量不要每次都重新创建 Bitmap
  • 谢谢,我知道。问题是旧页面没有被 GC 释放。我追查到 GListView 没有被释放的事实。如果在完成该页面后,我从 ViewGroup 中删除了 GListView,则该页面确实被释放,并且我不再看到 Out Of Memory 情况。但是,使用内存分析器,我可以看到 GListViews 仍然没有被释放。我试图让 Ant 工作,以便我可以从那个方向调查问题。很奇怪。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-09
  • 2012-03-05
  • 1970-01-01
  • 2011-12-31
相关资源
最近更新 更多