3

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).

halfer
  • 19,824
  • 17
  • 99
  • 186
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • i already know of it . however , as you can see , i don't store any bitmaps , and they are really tiny anyway and used by the system. – android developer Jul 08 '12 at 23:18
  • 1
    Whoops, disregard that other one. Your problem is that you are not using the convertView that is being passed to your getView() method, thus you are trying to create 10,000 view objects instead of just enough to fill the screen. If that is the same ListView lesson that I recall watching Romain definitely talks a lot in there about how you should be using the convertView. – FoamyGuy Jul 08 '12 at 23:26
  • Regarding "since i don't store the bitmaps or the views", in my answer below I explained why the out of memory is **not** caused by the bitmap allocations (and you can test it by commenting out parts of your code that I mentioned) but instead due to the huge amount of views that is accumulated by the ListView since you ignore the convertView argument. – Joe Jul 09 '12 at 12:50
  • but google has done the same testing , according to their video . how did they manage to use 10000 items without having this problem? – android developer Jul 09 '12 at 12:58
  • @androiddeveloper The issues you are seeing is precisely why what you are doing is the "naive" way. The fact that it is not working for you and did work for them (If it did, I don't recall specifically) is probably more to do with the specific device in question, perhaps theirs had enough ram to keep up, and yours does not. Either way though the whole point of the exercise is to see that ignoring the convertView is bad which should be fairly apparent since it crashes your app which is far worse even than just being slow and "cludgy" while scrolling =) – FoamyGuy Jul 09 '12 at 13:14

4 Answers4

7

The comment from Tim is spot on. The fact that your code does not utilize convertView in its BaseAdapter.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 the getView() method in its internal "recycle bin" container that will only be cleared if the ListView is detached from its window. This "recycle bin" is how it can produce all those convertView and supply it back to getView() when appropriate.

As a test, you can even comment out the code portion where you assign an image to your view:

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

And you will still get the memory allocation failure at one point :)

Community
  • 1
  • 1
Joe
  • 14,039
  • 2
  • 39
  • 49
  • why does the listView have this recycle bin , instead of using the views that it used before that were simply out of its bounds? it doesn't make sense . can you show me a proof of this behavior in its code ? is there a way to disable it or use a different view like listView ? – android developer Jul 09 '12 at 12:22
  • 1
    As I stated already, this recycle bin **is** actually how it is trying to reuse the views that are now out of bounds (by supplying back as `convertView` to your `getView()` method). Since your code **ignores** the supplied `convertView`, this optimization is wasted instead. The code is [available here](https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/AbsListView.java) if you are interested. Just do a search for `mRecycler` and you should see how it is being used. Hopefully it make more sense after you look at it. – Joe Jul 09 '12 at 12:40
  • so how did google overcome this problem when they've tested the naive version of the adapter (as shown in the graph of their lecture) ? the whole point of my code is to re-test what they've done , so that i could teach others how much better it is to use the optimizations that google has shown in the lecture. they even tested it on the nexus one ... – android developer Jul 09 '12 at 13:34
  • I am fairly positive that the graph is meant to just illustrate the relative speed (maybe fps or items per second? not clearly stated in the document) between the different adapter versions and should not be interpreted to say that they were able to run all three versions without memory issues through 10,000 items in the list :) – Joe Jul 09 '12 at 20:32
  • you think they wrote a lie? why would they? also , how did they do their test? how did they measure the FPS on the android framework itself? how come we can't have more info about it? – android developer Jul 09 '12 at 20:56
  • I am merely stating that I don't think the document mention anything about the "naive adapter" being able to handle scrolling through 10,000 items without any problems. If you think they do, please point it out to me since I am certainly missing it. – Joe Jul 09 '12 at 22:14
  • well the graph says that the test was on a 10000 items for all algorithms : http://dl.google.com/googleio/2010/android-world-of-listview-android.pdf (page 17) . in the video , it's talked about here: http://www.youtube.com/watch?feature=player_embedded&v=wDBM6wVEO70#t=748s – android developer Jul 09 '12 at 22:35
  • you know , they also talk a bit about the recycler , but according to what i understand from it , it doesn't suppose to store dead views that aren't used : "due to the implementation of the recycler inside listview , if you are not passing back either the new view that was just created or exactly the convertView instance that was passed to you , it's going to assume that anything else that was ever attached to the listView is dead ,and it's just goingo to go ahead and throw it away ." – android developer Jul 09 '12 at 22:48
  • 1
    Thank you for linking to those bits. Unfortunately, I still think that there is nothing in the presentation that specifically said "you can use the dumb adapter on a 10,000-item ListView without running into memory problems". Of course it could be just me, but I have my reasons (that I already described in my answer and comments above). I also understand the quoted part about the recycler differently from you (again, maybe just me). Let's just agree to disagree, shall we? :) – Joe Jul 10 '12 at 01:21
  • congrats, brilliant +1 – Lisa Anne May 13 '15 at 07:38
2

There are two points your code:

  1. As mentioned by previous answers, you are trying create so many new objects, well, that's the main reason of OutOfMemory problem.

  2. 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:

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;

This is the simple ViewHolder for efficient ListView.

static class ViewHolder {   
    ImageView Image;
    TextView  Text;
}

Pretty much simple but very effective coding.

Pete Houston
  • 14,931
  • 6
  • 47
  • 60
1

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:

android:largeHeap="true"

this will give your app a large memory to work on.

Use this only when there are no alternate ways for your desired output!

To Know about the drawbacks of using largeHeap check this answer

Arshdeep Singh
  • 316
  • 4
  • 15
0

Your catching Exception e, but OutOfMemoryError is Error, not an Exception. So if your want to catch OutOfMemory your can write something like

catch(Throwable e){}

or

catch(OutOfMemoryError e){}
Robert
  • 3,471
  • 3
  • 21
  • 24