четверг, 23 июня 2011 г.

Android: Use the ViewHolder

This is going to be a quick hit blog post about the Android "ViewHolder" pattern. To really make use of the ViewHolder with Android you do need to know what theListView widget is (a helper for managing views of lists), and that ListViews and generally backed byAdapters (adapters provide data for lists and build views for said data, they can be made from lists of stuff in files, or from in memory arrays, or from databases, and so on).

We will look at the code, complete with ViewHolder, coming up. First a bit more background.

You are probably at least vaguely familiar with ListView/Adapter and the related concepts if you have done any Android development at all. ListView is *very* powerful and helpful widget. Nevertheless, ListView's are often misused and abused because there are so many options and settings and patterns that surround them. I don't have time to go into great depth on ListView itself, but for the quick hit I promised I want to address 2 key things to keep in mind when working with ListViews:
  • Creating views is expensive, you don't want to inflate or manually create new views for every view on the list. ListView can re-use views, if you let it.
  • Even if you are re-using views, you also don't want to find child views by id (findViewById) every time, because that is expensive too.
I would say that 90% of Android examples or open source apps I see (or developers I talk to) do the first item there properly. Depending on the adapter in play they are re-using views. It's pretty simple, use the "convertView" on simple adapters like ArrayAdapter (which we will see coming up) and use the "newView" on more involved adapters like CursorAdapter. I would also say that the same high percentage of examples and open source apps *do not* use the ViewHolder to avoid trips to findViewById. If your list is small, this might not be a big deal, but if the list is large it can make a difference in terms of frame rate, resource usage, and performance (which equals better experience for the user, longer battery life, etc.). Also, it's a good habit to be in even with small lists, more efficient code is better, especially on a mobile platform.
static class ViewHolder { 
TextView text; 
 ImageView icon; 
}

public View getView(int position, View convertView, ViewGroup parent) { 
         ViewHolder holder; 
    
         if (convertView == null) { 
             convertView = mInflater.inflate(R.layout.list_item_icon_text, 
                     parent, false);
             holder = new ViewHolder(); 
             holder.text = (TextView) convertView.findViewById(R.id.text); 
             holder.icon = (ImageView) convertView.findViewById(R.id.icon); 
   
            convertView.setTag(holder); 
        } else { 
            holder = (ViewHolder) convertView.getTag(); 
        } 
   
        holder.text.setText(DATA[position]); 
        holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2); 
   
        return convertView; 
}
What we are focusing on above is the Adapter to the ListView. Within it we see that we are making use of the convertView if present to grab our subsequent views, and to populate our own internal ViewHolder representation. If the convertView isn't present, then we do the extra work of inflating our views and building our ViewHolder.
      The "convertView" is a convenient helper object provided to you by the framework specifically for re-use (if the framework is ready to provide it, the first item in the list will have a null convertView, hence the check, and the work only if it is null). The framework knows the views that are passing by, and as it recycles them it hands you one that you can "convert."
      So what is the ViewHolder? Well, that's really just a simple object we have created (using a package scoped inner class) that stores child views (though you could store anything there, that is all we need for this example). View objects have a "Tag" property that you can use to store any object. If you are getting recycled views anyway, it's very easy and helpful to just stick child views you need *there* and retrieve later, as opposed to calling "findViewById" to pull views from resources for every view in the list. Really this just saves us the findViewById calls. That might not seem like much, but again, in large lists it can cut down on overhead.
      A little further down in the code, after we get past creating the convertView in the case that it's null, we see where we use the ViewHolder to get a reference to our subsequent child views, and set stuff (in this case just Strings, but could be anything).
      This is a simple example, and I certainly won't promise that it's perfect, but hopefully it does help to explain what ViewHolder is, and why it's useful (and a little about re-cycling views in general).

1 комментарий: