Android getView方法优化简记
学习Android差不多半年,现在看来以前学习的终究太浅,重量不中质。看书也是囫囵吞枣,总想着看完再说,却没有想想自己究竟真正的掌握了什么。最近一直在反思之前的学习历程,这个getView方法的优化让我印象颇深,网上的资料数不胜数,说是优化,不如说是一种应该如此的学法。虽然只是入门的知识,但是很多基础教程上都没有提及。把它写下来,就是给自己提个醒,也算值得。
这里以ListView的getView()为例,刚学习Android的时候,重写ListView的Adapter时getView方法是这样写的:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = View.inflate(ctx, R.layout.layout_item, null);
TextView tv1 = (TextView) view.findViewById(R.id.tv_1st);
TextView tv2 = (TextView) view.findViewById(R.id.tv_2ed);
tv1.setText(position + "");
tv2.setText(strs[new Random().nextInt(strs.length)]);
return view;
}
这种写法就是一般的Android入门书里的学法,当ListView显示的数据较多时,在手机配置较差或模拟器中快速滑动列表时,特别容易出现内存溢出的错误,导致应用程序崩溃。
查看log:
在Android源码或是开发中应该这样写
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
ViewHolder holder;
if (convertView != null) {
//复用控件
view = convertView;
holder = (ViewHolder) view.getTag();
} else {
//构造列表项控件,用ItemView进行缓存
view = View.inflate(ctx, R.layout.layout_item, null);
holder = new ViewHolder();
holder.tv1 = (TextView) view.findViewById(R.id.tv_1st);
holder.tv2 = (TextView) view.findViewById(R.id.tv_2ed);
view.setTag(holder);
}
//绑定数据
holder.tv1.setText("ID: " + position);
holder.tv2.setText(strs[new Random().nextInt(strs.length)]);
return view;
}
}
//缓存数据项控件对象
static class ViewHolder {
TextView tv1;
TextView tv2;
}
这种写法为每个类表象都inflate了一个View对象,当列表项很多时会导致子控件的数量急剧膨胀,耗费大量的内存资源,甚至导致应用崩溃。Android的适配器在设计时,充分考虑了处理大规模数据的场景,为开发者提供了解决策略。在Adapter.getView方法中,有一个输入参数convertView,用于缓存最近一个失去可视状态的列表控件对象。当用户滚动列表时,处于可视状态的列表项会变成不可视状态,而不可视状态的列表则可能会变成可视状态。convertView便是用于缓存失去可视状态的列表项控件对象,通过Adapter.getVie方法传回开发者手中,开发者可以复用这个控件对象重新绑定即将可视的列表项数据,从而避免了构造新列表控件的开销。
另外,在使用convertView的时候,对列表项控件的子控件进行缓存可以节省列表项数据项绑定的开销,View.setTag方法可以将缓存放在对应的列表项控件中,节省调用View.findViewById方法造成的浪费。
通过列表项复用,可以有效地提高列表项的性能,节约内存开销,但同时它也增加了编程的复杂度,尤其是存在多种表项样式时,这种复杂度的增加也尤为明显。开发者需要根据列表项的数量和特征,选择合适的构造方法。
再看一下Android源码中的示例:
android/packages/apps/settings/src/com/android/settings
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unnecessary calls
// to findViewById() on each row.
AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
convertView = holder.rootView;
MyApplicationInfo info = getItem(position);
holder.appName.setText(info.label);
if (info.info != null) {
holder.appIcon.setImageDrawable(info.info.loadIcon(getPackageManager()));
holder.appSize.setText(info.info.packageName);
} else {
holder.appIcon.setImageDrawable(null);
holder.appSize.setText("");
}
holder.disabled.setVisibility(View.GONE);
holder.checkBox.setVisibility(View.GONE);
return convertView;
}
}
public class AppViewHolder {
public ApplicationsState.AppEntry entry;
public View rootView;
public TextView appName;
public ImageView appIcon;
public TextView appSize;
public TextView disabled;
public CheckBox checkBox;
static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.manage_applications_item, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
AppViewHolder holder = new AppViewHolder();
holder.rootView = convertView;
holder.appName = (TextView) convertView.findViewById(R.id.app_name);
holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard);
convertView.setTag(holder);
return holder;
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
return (AppViewHolder)convertView.getTag();
}
}
之前也曾经看过相关内容,可是却由于思考上的惰性,导致之后的写法还是刚接触时的样子。学习不能只是被动接受,学而不思则罔,古人说的太对了。