It might be bad for the immediate user experience, if the list appears to be sluggish. More likely, though, it will be bad due to battery usage — every bit of CPU that is used eats up the battery. This is compounded by the extra work the garbage collector needs to do to get rid of all those extra objects you create. So the less efficient your code, the more quickly the phone’s battery will be drained, and the less happy the user will be. And you want happy users, right?
So, let us take a look at a few tricks to make your fancy ListView widgets more efficient.
Using convertView
The getView() method receives, as one of its parameters, a View named, by convention, convertView. Sometimes convertView will be null. In those cases, you have to create a new row View from scratch (e.g., via inflation), just as we did before.
However, if convertView is not null, then it is actually one of your previously created Views. This will be the case primarily when the user scrolls the ListView — as new rows appear, Android will attempt to recycle the views of the rows that scrolled off the other end of the list, to save you having to rebuild them from scratch.
Assuming that each of your rows has the same basic structure, you can use findViewById() to get at the individual widgets that make up your row and change their contents, then return convertView from getView() rather than create a whole new row.
For example, here is the getView() implementation from last time, now optimized via convertView (from the FancyLists/Recycling project at http://apress.com/):
public class RecyclingDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter(this));
selection = (TextView)findViewById(R.id.selection);
}
public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}
class IconicAdapter extends ArrayAdapter {
Activity context;
IconicAdapter(Activity context) {
super(context, R.layout.row, items);
this.context = context;
}
public View getView(int position, View convertView,
ViewGroup parent) {
View row = convertView;
if (row==null) {
LayoutInflater inflater = context.getLayoutInflater();
row = inflater.inflate(R.layout.row, null);
}
TextView label = (TextView)row.findViewById(R.id.label);
label.setText(items[position]);
ImageView icon = (ImageView)row.findViewById(R.id.icon);
if (items[position].length() > 4) {
icon.setImageResource(R.drawable.delete);
} else {
icon.setImageResource(R.drawable.ok);
}
return(row);
}
}
}
Here we check to see if the convertView is null and, if so we then inflate our row — but if it is not null, we just reuse it. The work to fill in the contents (icon image, text) is the same in either case. The advantage is that if the convertView is not null, we avoid the potentially expensive inflation step.
This approach will not work in every case, though. For example, it may be that you have a ListView for which some rows will have one line of text and others will have two. In this case, recycling existing rows becomes tricky, as the layouts may differ significantly. For example, if the row we need to create a View for requires two lines of text, we cannot just use a View with one line of text as is. We either need to tinker with the innards of that View, or ignore it and inflate a new View.
Of course, there are ways to deal with this, such as making the second line of text visible or invisible depending on whether it is needed. And on a phone every millisecond of CPU time is precious, possibly for the user experience, but always for battery life — more CPU utilization means a more quickly drained battery.
That being said, particularly if you are a rookie to Android, focus on getting the functionality right first, then looking to optimize performance on a second pass through your code rather than getting lost in a sea of Views, trying to tackle it all in one shot.
Using the Holder Pattern
Another somewhat expensive operation we do a lot with fancy views is call findViewById(). This dives into our inflated row and pulls out widgets by their assigned identifiers so we can customize the widget contents (e.g., change the text of a TextView, change the icon in an ImageView). Since findViewById() can find widgets anywhere in the tree of children of the row’s root View, this could take a fair number of instructions to execute, particularly if we keep having to re-find widgets we had found once before.
In some GUI toolkits, this problem is avoided by having the composite Views, like our rows, be declared totally in program code (in this case, Java). Then accessing individual widgets is merely a matter of calling a getter or accessing a field. And you can certainly do that with Android, but the code gets rather verbose. We need a way that lets us use the layout XML yet cache our row’s key child widgets so we have to find them only once. That’s where the holder pattern comes into play, in a class we’ll call ViewWrapper.