首页 > 代码库 > 理解用setTag 和 Viewholder 来优化listView
理解用setTag 和 Viewholder 来优化listView
在说 setTag和getTag之前,我们先说下 Viewholder,它不是Android开发固定的API,而是谷歌Demo中推荐的设计方法。Viewholder对象它一般包括listview子项里所有的组件,convertView是空的,在Viewholder里存储对列表子项每个组件的id应用,通过setTag方法,把这个带有view引用的对象附加在View上,如此,当listView更新的时候,就不用再次去重复寻找引用,并且强制转换等工作,findViewById(R.id.img);通过getTag直接从view携带的Viewholder中取出每个组件的引用。
那么 setTag和 getTag的作用就很明显了,tag是标签的意思,但是在这里它不单单是作为view的标签,从本质上说,它是附加在view上面的任意数据,setTag(Object obj) 形参是obj,任意对象。而getTag则更好理解了,既然setTag把数据附加上去,自然得有方法把它取下来再次使用。
请看代码——
用setTag和 getTag ,ViewHolder的代码
ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, null); holder = new ViewHolder(); holder.icon1 = (ImageView) convertView.findViewById(R.id.icon1); holder.text1 = (TextView) convertView.findViewById(R.id.text1); holder.icon2 = (ImageView) convertView.findViewById(R.id.icon2); holder.text2 = (TextView) convertView.findViewById(R.id.text2); convertView.setTag(holder); } else{ holder = (ViewHolder)convertView.getTag(); } holder.icon1.setImageResource(R.drawable.icon); holder.text1.setText(mData[position]); holder.icon2 .setImageResource(R.drawable.icon); holder.text2.setText(mData[position]); } static class ViewHolder { TextView text1; ImageView icon1; TextView text2; ImageView icon2; }
没用setTag,getTag和 ViewHolder的时候
if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, null); } ((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon); ((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]); ((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon); ((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]);
}
当Android第一次加载第一页listView的时候,convertView是空的,因此通过
mInflater.inflate(R.layout.list_item_icon_text,
null);
这行代码实例化了一个 view给 convertView,然后加载数据,每一个组件(这里的是ImageView和TextView)都需要通过 findViewById这个方法去寻找视图控件。加载第一页的时候做一次这样的工作尚可忍受,然而,当加载第二页的时候, findViewById这个方法依然被调用,依次以后每次加载都如此。须知道,从xml中去寻找控件引用并加载时一件比较耗内存的事情,你每次都做这件事情,后果更甚。
因此,我定义了一个静态的 Viewholder类,放在静态存储区,并把组件引用通过setTag方法附加在View上面,当加载第二页的时候,你就不用再次去findViewById了,直接用getTag方法来取出数据引用即可。
这种方法叫做视图缓存。这也是2009年谷歌IO大会给出的优化建议。
如果你对这种方法存疑,有网友已经就这两种做法,还有另外一种,分别进行了数据测试,实测证明了,这个视图缓存方法的确是达到了优化的效果的。详情看此文章。
[Android]ListView性能优化之视图缓存
视图缓存的缺点——
应该注意到,这是一种以空间换时间的做法,因为增加了一个静态类,来达到减少 findViewById的次数。此外,setTag这样附加数据到view中会让view增加额外的负担,这样就造成了设置tag的view和不设置tag的view大小是不一样,只不过,我们一直都习惯了别人告诉我们这么做,没去比较而已。
网友农民伯伯通过反编译新浪微博而得到另外一种做法,就是用自定义的类(继承自RelativeLayout或其他容器),然后在这个里面直接把子元素findViewById后放到成员变量再暴露出来。
做法代码如下——出自农民伯伯博客
public View getView(int position, View convertView, ViewGroup parent) { // 开始计时 long startTime = System.nanoTime(); TestItemLayout item; if (convertView == null) { item = new TestItemLayout(BaseAdapterActivity.this); } else item = (TestItemLayout) convertView; item.icon1.setImageResource(R.drawable.icon); item.text1.setText(mData[position]); item.icon2.setImageResource(R.drawable.icon); item.text2.setText(mData[position]); // 停止计时 long endTime = System.nanoTime(); // 计算耗时 long val = (endTime - startTime) / 1000L; Log.e("Test", "Position:" + position + ":" + val); if (count < 100) { if (val < 2000L) { sum += val; count++; } } else mTV.setText(String.valueOf(sum / 100L) + ":" + nullcount);// 显示统计结果 return item; } TestItemLayout public class TestItemLayout extends LinearLayout { public TextView text1; public ImageView icon1; public TextView text2; public ImageView icon2; public TestItemLayout(Context context) { super(context); ((LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate( R.layout.list_item_icon_text, this); icon1 = (ImageView) findViewById(R.id.icon1); text1 = (TextView) findViewById(R.id.text1); icon2 = (ImageView) findViewById(R.id.icon2); text2 = (TextView) findViewById(R.id.text2); } }
个人理解:这种做法是不是用了反射的思想呢?当加载第二页的时候, item = (TestItemLayout) convertView; 把convertView强制类型转换成自定义的布局容器,其中就能把每个组件的引用给转换出来? 求解释。
农民伯伯最后的验证结果是跟使用视图缓存效果差不多,但是空间上优化了,并且由于没有使用Tag,view也不会变小了。