Android listview出现内存不足异常,但没有内存泄漏?
在Honeycomb之后,Google说位图是由堆管理的(如前所述),因此如果位图不再可访问,我们可以假设GC会处理并释放它 我想创建一个演示,展示listView讲座(从)中展示的想法的效率,所以我制作了一个小应用程序。该应用程序允许用户按下一个按钮,然后listview一直滚动到底部,同时它有10000个项目,它们的内容是android.R.drawable项目(名称和图像) 出于某种原因,即使我没有保存任何图像,我也会失去记忆,所以我的问题是:怎么可能?我错过了什么 我已经在Galaxy S III上测试过该应用程序,但是如果我使用本机版本的适配器,我会不断出现内存不足的异常。我不明白为什么会这样,因为我没有存储任何东西 代码如下: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
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"