listview内存异常,但没有内存泄漏?

时间:2012-07-08 22:35:06

标签: android garbage-collection android-3.0-honeycomb out-of-memory android-4.0-ice-cream-sandwich

在Honeycomb之后,谷歌表示位图是由堆管理的(谈到here),所以如果不再可以访问位图,我们可以假设GC处理它并释放它。

我想创建一个演示,显示为listView讲座(来自here)显示的想法的效率,所以我做了一个小应用程序。该应用程序允许用户按下按钮,然后列表视图一直滚动到底部,而它有10000个项目,其内容是android.R.drawable项目(名称和图像)。

出于某种原因,即使我没有保存任何图像,我也会内存不足,所以我的问题是:它怎么可能?我错过了什么?

我已经在Galaxy S III上测试了应用程序,但是如果我使用适配器的原生版本,我会不断出现内存异常。我不明白为什么会这样,因为我不存储任何东西。

以下是代码:

public class MainActivity extends Activity
  {
  private static final int LISTVIEW_ITEMS =10000;
  long                     _startTime;
  boolean                  _isMeasuring   =false;

  @Override
  public void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ListView listView=(ListView)findViewById(R.id.listView);
    final Field[] fields=android.R.drawable.class.getFields();
    final LayoutInflater inflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    // listen to scroll events , so that we publish the time only when scrolled to the bottom:
    listView.setOnScrollListener(new OnScrollListener()
      {
        @Override
        public void onScrollStateChanged(final AbsListView view,final int scrollState)
          {
          if(!_isMeasuring||view.getLastVisiblePosition()!=view.getCount()-1||scrollState!=OnScrollListener.SCROLL_STATE_IDLE)
            return;
          final long stopTime=System.currentTimeMillis();
          final long scrollingTime=stopTime-_startTime;
          Toast.makeText(MainActivity.this,"time taken to scroll to bottom:"+scrollingTime,Toast.LENGTH_SHORT).show();
          _isMeasuring=false;
          }

        @Override
        public void onScroll(final AbsListView view,final int firstVisibleItem,final int visibleItemCount,final int totalItemCount)
          {}
      });
    // button click handling (start measuring) :
    findViewById(R.id.button).setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(final View v)
          {
          if(_isMeasuring)
            return;
          final int itemsCount=listView.getAdapter().getCount();
          listView.smoothScrollToPositionFromTop(itemsCount-1,0,1000);
          _startTime=System.currentTimeMillis();
          _isMeasuring=true;
          }
      });
    // creating the adapter of the listView
    listView.setAdapter(new BaseAdapter()
      {
        @Override
        public View getView(final int position,final View convertView,final ViewGroup parent)
          {
          final Field field=fields[position%fields.length];
          // final View inflatedView=convertView!=null ? convertView : inflater.inflate(R.layout.list_item,null);
          final View inflatedView=inflater.inflate(R.layout.list_item,null);
          final ImageView imageView=(ImageView)inflatedView.findViewById(R.id.imageView);
          final TextView textView=(TextView)inflatedView.findViewById(R.id.textView);
          textView.setText(field.getName());
          try
            {
            final int imageResId=field.getInt(null);
            imageView.setImageResource(imageResId);
            }
          catch(final Exception e)
            {}
          return inflatedView;
          }

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

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

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

@all:我知道这个代码有优化(使用convertView和viewHolder设计模式),因为我已经提到过Google制作的listView视频。相信我,我知道什么更好;这是代码的重点。

上面的代码应该表明最好使用你(和视频)显示的内容。但首先我需要表现出天真的态度;即使是天真的方式仍然可以工作,因为我没有存储位图或视图,因为Google已经完成了相同的测试(因此他们得到了性能比较图表)。

4 个答案:

答案 0 :(得分:7)

comment from Tim是现货。您的代码在其convertView方法中不使用BaseAdapter.getView()并且每次都持续膨胀新视图的事实是导致它最终耗尽内存的主要原因。

上次检查时,ListView将保留getView()方法在其内部“回收站”容器中返回的所有视图,只有在ListView时才会清除与窗户分离。这个“回收站”就是它如何生成所有convertView并在适当时将其提供回getView()

作为测试,您甚至可以注释掉为视图指定图像的代码部分:

                // final int imageResId = field.getInt(null);
                // imageView.setImageResource(imageResId);

你仍然会在某一点上获得内存分配失败:)

答案 1 :(得分:2)

您的代码有两点:

  1. 如前面的答案所述,您正在尝试创建这么多新对象,这就是OutOfMemory问题的主要原因。

  2. 您的代码效率不足以连续加载所有对象(例如向上/向下滑动以进行滚动),嗯,它是滞后的。

  3. 这里有一个提示来解决这两个常见问题:

    Field field = fields[position % fields.length];
    View v = convertView;
    ViewHolder holder = null;
    
    if (v == null) {
        v = inflater.inflate(R.layout.list_item,null);
        holder = new ViewHolder();
        holder.Image = (ImageView) inflatedView.findViewById(R.id.imageView);
        holder.Text = (TextView)inflatedView.findViewById(R.id.textView);
        v.setTag(holder);
    } else {
        holder = (ViewHolder) v.getTag();
    }
    return v;
    

    这是有效ViewHolder的简单ListView

    static class ViewHolder {   
        ImageView Image;
        TextView  Text;
    }
    

    非常简单但非常有效的编码。

答案 2 :(得分:0)

您的捕获异常e,但OutOfMemoryError是错误,而不是异常。因此,如果您想要捕获OutOfMemory,您可以编写类似

的内容
catch(Throwable e){}

catch(OutOfMemoryError e){}

答案 3 :(得分:0)

我长时间收到oom错误,当我用很多图片给我的列表视图充气时(即使图像已经压缩了)

在清单中使用此功能可能会解决您的问题:

android:largeHeap="true"

这将为您的应用程序提供大量内存供您使用。

  

仅在没有其他方式获得所需输出时才使用此功能!

要了解使用largeHeap的缺点,请查看此answer