3

在 Honeycomb 之后,Google 表示位图由堆管理(在此处讨论),因此如果不再可访问位图,我们可以假设 GC 处理并释放它。

我想创建一个演示来展示 listView 讲座所展示的想法的效率(来自这里),所以我制作了一个小应用程序。该应用程序让用户按下一个按钮,然后列表视图一直滚动到底部,同时它有 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 的视频。相信我,我知道什么是更好的;这是代码的重点。

上面的代码应该表明最好使用您(和视频)显示的内容。但首先我需要展示幼稚的方式;即使是幼稚的方式也应该仍然有效,因为我不存储位图或视图,并且因为谷歌已经完成了相同的测试(因此他们得到了性能比较图)。

4

4 回答 4

7

蒂姆的评论很到位。您的代码没有convertView在其BaseAdapter.getView()方法中使用并且每次都不断膨胀新视图这一事实是它最终会耗尽内存的主要原因。

上次我检查时,ListView会将方法返回的所有视图保留在其内部“回收站”容器中,只有在从其窗口分离getView()时才会清除该容器。ListView这个“回收站”是它如何生产所有这些并在适当的时候convertView将其送回的方式。getView()

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

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

而且您仍然会在某一时刻遇到内存分配失败:)

于 2012-07-09T02:15:34.580 回答
2

您的代码有两点:

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

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

这里提示解决这两个常见问题:

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

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

于 2012-07-09T04:37:11.087 回答
1

当我用太多图像膨胀我的列表视图时,我已经收到 oom 错误很长时间了(即使图像已经被压缩)

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

android:largeHeap="true"

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

仅当您想要的输出没有替代方法时才使用它!

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

于 2018-01-22T19:32:58.913 回答
0

您捕获的异常 e,但 OutOfMemoryError 是错误,而不是异常。所以如果你想赶上 OutOfMemory 你可以写类似

catch(Throwable e){}

或者

catch(OutOfMemoryError e){}
于 2012-12-07T08:40:45.673 回答