Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/dart/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android listview出现内存不足异常,但没有内存泄漏?_Android_Garbage Collection_Android 3.0 Honeycomb_Out Of Memory_Android 4.0 Ice Cream Sandwich - Fatal编程技术网

Android listview出现内存不足异常,但没有内存泄漏?

Android listview出现内存不足异常,但没有内存泄漏?,android,garbage-collection,android-3.0-honeycomb,out-of-memory,android-4.0-ice-cream-sandwich,Android,Garbage Collection,Android 3.0 Honeycomb,Out Of Memory,Android 4.0 Ice Cream Sandwich,在Honeycomb之后,Google说位图是由堆管理的(如前所述),因此如果位图不再可访问,我们可以假设GC会处理并释放它 我想创建一个演示,展示listView讲座(从)中展示的想法的效率,所以我制作了一个小应用程序。该应用程序允许用户按下一个按钮,然后listview一直滚动到底部,同时它有10000个项目,它们的内容是android.R.drawable项目(名称和图像) 出于某种原因,即使我没有保存任何图像,我也会失去记忆,所以我的问题是:怎么可能?我错过了什么 我已经在Galaxy

在Honeycomb之后,Google说位图是由堆管理的(如前所述),因此如果位图不再可访问,我们可以假设GC会处理并释放它

我想创建一个演示,展示listView讲座(从)中展示的想法的效率,所以我制作了一个小应用程序。该应用程序允许用户按下一个按钮,然后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视频。相信我,我知道什么更好;这就是代码的全部要点

上面的代码应该表明,最好使用您(和视频)显示的内容。但首先我需要表现出天真的方式;即使是最简单的方法也应该仍然有效,因为我不存储位图或视图,而且谷歌也做了同样的测试(因此他们得到了性能比较图)。

这是正确的。您的代码没有在其
BaseAdapter.getView()
方法中使用
convertView
,并且每次都不断膨胀新视图,这是导致它最终耗尽内存的主要原因

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

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

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

您仍然会在某一点上遇到内存分配失败:)

您的代码有两点:

  • 正如前面的答案所提到的,您正在尝试创建如此多的新对象,这是
    OutOfMemory
    问题的主要原因

  • 您的代码没有足够的效率来连续加载所有对象(如向上/向下滑动以滚动),嗯,它是滞后的

  • 下面是修复这两个常见问题的提示:

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

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

    捕获异常e,但OutOfMemoryError是错误,而不是异常。所以,若你们想抓住记忆的机会,你们可以这样写

    catch(Throwable e){}
    


    很长一段时间以来,当我用太多的图像膨胀我的listview时(即使这些图像已经被压缩),我一直收到oom错误

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

    android:largeHeap="true"
    
    这将为你的应用程序提供一个大内存

    仅当没有其他方法可用于所需输出时才使用此选项


    要了解使用largeHeap的缺点,请检查这个

    我已经知道了。但是,正如你所看到的,我没有存储任何位图,而且它们非常小,并且被系统使用。哎呀,忽略另一个。您的问题是,您没有使用传递给getView()方法的convertView,因此您试图创建10000个视图对象,而不是仅仅足够填充屏幕。如果这是我记得看过Romain的ListView课程,那么Romain肯定会在其中谈到如何使用convertView。关于“因为我不存储位图或视图”,我在下面的回答中解释了为什么内存不足不是由位图分配引起的(您可以通过注释掉我提到的部分代码来测试它)但是,由于ListView在忽略convertView参数后积累了大量的视图。但是根据他们的视频,谷歌也做了同样的测试。他们是如何设法使用10000个项目而不出现这个问题的?@androiddeveloper你看到的问题正是你所做的是“幼稚”的原因事实上,它对你不起作用,而且确实对他们起作用(如果真的起作用,我不记得有什么特别的)这可能更多地与所讨论的特定设备有关,也许他们的内存足够,而你的内存不够。不管怎样,整个练习的目的是要看到忽略convertView是不好的,这应该是相当明显的,因为它会使你的应用程序崩溃,甚至比缓慢和“笨拙”还要糟糕当滚动=)时,为什么listView有这个回收站,而不是使用它以前使用的视图,这些视图只是超出了它的范围?这没有道理。你能在代码中给我一个证明吗?有没有办法禁用它或使用不同的视图(如listView)?正如我已经说过的,这个回收站实际上是它试图重用现在超出范围的视图的方式(通过将
    convertView
    作为
    getView()
    方法提供回来)。由于您的代码忽略了提供的
    convertView
    ,因此此优化将被浪费。如果您感兴趣,代码就是。只要搜索一下
    mRecycler
    ,你就会知道它是如何被使用的。希望你看了之后会觉得更有意义。那么,当谷歌测试了适配器的原始版本(如他们讲座的图表所示)时,他们是如何克服这个问题的呢?我的代码的全部目的是重新测试他们所做的事情,这样我就可以教其他人使用optimi有多好
    android:largeHeap="true"