Monday, 13 January 2014

Tricks to boost performance of list view

If you want to make your list view scroll very smoothly on each type of devices especially greater than api level 2.3 then there are several things in which you should look at -:


1) Use minimal conditions in getView of adapter.
2) Avoid garbage collector warnings in log (max as you can).
3) On scrolling do not load images to avoid load on UI thread.
4) Set scrollingCache and animateCache to false.
5) Keep Hierarchy of list view row layout as minimum as possible.
6) Use view holder pattern.

I was having a problem of lag so i will explain with the example of how i improved the performance and made the list view scroll like a butter!!.There is not a simple solution for each case because the solution of improving varies on the basis of your code and where you have mistaken.Someone might get solved by just adding view holder or some might get solved by flattened the hierarchy of layout.So we have to try out each solution and here are which i have tried and got it worked.


1) Use minimal conditions in getView of adapter -: Do not put so many complex conditions (excellent if you do not put any) instead you can add all those conditions in the main activity class  and get those values through any other class(mostly i use serializable).There were more complex conditions in this example but i removed to make it easier to understand.


Previous GetView =
@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
        Object current_event = mObjects.get(position);
        ViewHolder holder = null;
        if (convertView == null) {
                holder = new ViewHolder();
                convertView = inflater.inflate(R.layout.row_event, null);
                holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
                holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
                convertView.setTag(holder);

        } else {
                holder = (ViewHolder) convertView.getTag();
        }

       // If you have conditions like below then you may face problem
        if (doesSomeComplexChecking()) {
                holder.ThreeDimention.setVisibility(View.VISIBLE);
        } else {
                holder.ThreeDimention.setVisibility(View.GONE); 
        }

        // This method is setting layout parameters each time when getView is getting called which is wrong.
        RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
        holder.EventPoster.setLayoutParams(imageParams);

        return convertView;
}
New GetView =
@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
    Object current_event = mObjects.get(position);
    ViewHolder holder = null;

    if (convertView == null) {
            holder = new ViewHolder();
            convertView = inflater.inflate(R.layout.row_event, null);
            holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
            holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
            // This will now execute only for the first time of each row
            RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
            holder.EventPoster.setLayoutParams(imageParams);
            convertView.setTag(holder);
    } else {
            holder = (ViewHolder) convertView.getTag();
    }

    // This way you can simply avoid conditions
    holder.ThreeDimension.setVisibility(object.getVisibility());

    return convertView;
}

2) Garbage collector -: GC occurs frequently when you're creating lots of objects and then destroying them. So the general advice here would be to not create a lot of objects in getView(), and an even better advice - to not create objects at all except the view holder.If you are seeing "GC has freed some memory" message in your Log VERY frequently then you are doing something grievously wrong.You will have take a look at one of the following -:

a) Hierarchy of list row layout.
b) Check your GetView adapter.
c) List view properties in layout.


3) Loading images -: If you are downloading images then you can use the ImageLoader library from google IO 2013 open source app which is really fast in loading images.They are using volley library for image loader. We should not load images when fling motion event occurs because in fraction of seconds list view can not load all rows smoothly and without any lag.It may load but not on all devices which has low configuration.Here In this image loader is when a fling motion event occurs it stops its queue of an image loader and when scrolling stops then again it resumes its queue, for that we need to add scroll listener as -:

listView.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView listView, int scrollState) {
                    // Pause disk cache access to ensure smoother scrolling
                    if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
                            imageLoader.stopProcessingQueue();
                    } else {
                            imageLoader.startProcessingQueue();
                    }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    // TODO Auto-generated method stub

            }
    });
Here is the link of google IO 2013 src - http://code.google.com/p/iosched/

4) Scrolling and animate cache properties of listview-:  Scrolling cache is basically a drawing cache.In android, you can ask a View to store its drawing in a cache called drawing cache (basically a bitmap). By default, a drawing cache is disabled because it takes up memory but you can ask the View to explicitly to create one either via setDrawingCacheEnabled or through hardware layers (setLayerType). Drawing cache make your animation smooth compared to redrawing the view at every frame.


This type of animation can also be hardware accelerated because the rendering system can take this bitmap and upload it to the GPU as a texture (if using hardware layers) and do fast matrix manipulations on it (like change alpha, translate, rotation). Compare that to doing animation were you are redrawing (onDraw gets called) on every frame.In the case of a listview, when you scroll by flinging, you are in essence animating the views of your list (either moving them up or down). The listview uses the drawing cache of its visible children (and some potentially visible children near the edges) to animate them very quickly.


Is there a disadvantage to using drawing cache? Yes it consumes memory which is why by default it is turned off for in a View. In the case of ListView, the cache automatically created for you as soon as you touch the ListView and move a little (to differentiate a tap from scroll). In other words, as soon as ListView thinks you are about to scroll/fling it will create a scroll cache for you to animate the scroll/fling motion. This detailed information is from Numan Salati (http://stackoverflow.com/questions/15570041/scrollingcache)


AnimationCache -: Defines whether layout animations should create a drawing cache for their children. Enabling the animation cache consumes more memory and requires a longer initialization but provides better performance. The animation cache is enabled by default.You can set false values to these properties as it causes to invoke GC.


Previous Listview =
<ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:divider="@color/list_background_color"
        android:dividerHeight="0dp"
        android:listSelector="#00000000"
        android:smoothScrollbar="true"
        android:visibility="gone" /> 
 New Listview = 
<ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:divider="@color/list_background_color"
        android:dividerHeight="0dp"
        android:listSelector="#00000000"
        android:scrollingCache="false"
        android:animationCache="false"
        android:smoothScrollbar="true"
        android:visibility="gone" />

5) Hierarchy of list view row layout -: Try to keep hierarchy of list view row layout as minimum as possible because it directly related with measuring and drawing time of the layout which can cause in the lag while scrolling.You can remove unnecessary layouts of it.


6) View holder pattern -: This is the huge boost for the scroll as it avoids constant call to the findViewById() which slows down your performance when list scrolls.As lots of information has already been published on this topic so i recommend you to go through links below -:



AM I MISSING ANYTHING HERE THEN COMMENTS ARE WELCOME!! WE CAN EXPLORE MORE.

11 comments:

  1. https://github.com/lucasr/smoothie is still really relevant for removing jank from images in list/grid views

    ReplyDelete
  2. "GC occurs when your application is trying to use more memory than it has allocated by default.If you are seeing "GC has freed some memory" message in your Log very frequently then you are doing something grievously wrong." - can't really agree with these statements. GC occurs frequently when you're creating a lot of objects and then destroying them. So the general advice here would be to not create a lot of objects in getView(), and an even better advice - to not create objects at all. Otherwise, thanks for a nice article!

    ReplyDelete
  3. @Egor Yeah.i meant the same you are trying to.I guess i should rewrite the GC part.Thanks.

    ReplyDelete
  4. Hi Abhishek, thanks for the useful tips. One thing I didn't understand, is why deactivating the caches would improve scrolling. This is counterintuitive, so a detailed explanation would be helpful.

    ReplyDelete
    Replies
    1. Hey, I was having problem in my project where GC was getting called very frequently because of several factors as explained above and one of it was scrolling cache properties.If scrolling cache is set to true(by default) , framework saves each child view of list view in bitmap and stored in a cache and reading back those views is costly when you are low on heap memory and if it exceeds the heap limit within these operations then list view can lag while scrolling.You may feel that saving views in a cache and readback should be helpful for performance optimization but it is not true for each case.If you need more information you can read this -: http://android-developers.blogspot.in/2009/01/why-is-my-list-black-android.html

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Use minimal conditions in getView of adapter: what you told in this is make a separate method and object to hold all condition, is this would be really helpful as calling that method every time is a overhead.

    ReplyDelete
  7. Hello, I am using UIL and your solution on Loading images has improved the performance for Scrolling ListView!! THANKS!!!!!!!

    ReplyDelete