After Honeycomb, Google said that bitmaps are managed by the heap (talked about here), so if a bitmap is no longer accessible, we can assume that GC takes care of it and frees it.
I wanted to create a demo that shows the efficiency of the ideas shown for the listView lecture (from here), so I made a small app. The app lets the user press a button, and then the listview scrolls all the way to the bottom, while it has 10000 items, which their content is the android.R.drawable items (name and image).
For some reason, I get out of memory even though I don't save any of the images, so my question is: How could it be? What is it that I'm missing?
I've tested the app on a Galaxy S III, and yet I keep getting out of memory exceptions if I use the native version of the adapter. I don't understand why it occurs, since I don't store anything.
Here's the code:
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: I know that there are optimizations for this code (using the convertView and the viewHolder design pattern) as I've mentioned the video of the listView made by Google. Believe me, I know what's better; this is the whole point of the code.
The code above is supposed to show that it's better to use what you (and the video) shows. But first I need to show the naive way; even the naive way should still work, since I don't store the bitmaps or the views, and since Google has done the same test (hence they got a graph of performance comparison).
There are two points your code:
As mentioned by previous answers, you are trying create so many new objects, well, that's the main reason of
OutOfMemory
problem.Your code is not efficient enough to load all objects continuously (like swipe up/down for scrolling), well, it's lagging.
Here a hint to fix those two common problems:
This is the simple
ViewHolder
for efficientListView
.Pretty much simple but very effective coding.
Your catching Exception e, but OutOfMemoryError is Error, not an Exception. So if your want to catch OutOfMemory your can write something like
or
I have been getting oom error for a long time, when I inflate my listview with too many images (even though the images were already compressed)
Using this in your manifest might solve your issue:
this will give your app a large memory to work on.
To Know about the drawbacks of using largeHeap check this answer
The comment from Tim is spot on. The fact that your code does not utilize
convertView
in itsBaseAdapter.getView()
method and keep inflating new views every time is the major cause of why it will eventually run out of memory.Last time I checked,
ListView
will keep all the views that are ever returned by thegetView()
method in its internal "recycle bin" container that will only be cleared if theListView
is detached from its window. This "recycle bin" is how it can produce all thoseconvertView
and supply it back togetView()
when appropriate.As a test, you can even comment out the code portion where you assign an image to your view:
And you will still get the memory allocation failure at one point :)