理解用setTag 和 Viewholder 来优化listView

时间:2022-10-06 22:44:11

在说 setTaggetTag之前,我们先说下 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;
    }

没用setTaggetTag 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,然后加载数据,每一个组件(这里的是ImageViewTextView都需要通过 findViewById这个方法去寻找视图控件。加载第一页的时候做一次这样的工作尚可忍受,然而,当加载第二页的时候, findViewById这个方法依然被调用,依次以后每次加载都如此。须知道,从xml中去寻找控件引用并加载时一件比较耗内存的事情,你每次都做这件事情,后果更甚。

 

因此,我定义了一个静态的 Viewholder类,放在静态存储区,并把组件引用通过setTag方法附加在View上面,当加载第二页的时候,你就不用再次去findViewById了,直接用getTag方法来取出数据引用即可。

 

这种方法叫做视图缓存。这也是2009年谷歌IO大会给出的优化建议。

如果你对这种方法存疑,有网友已经就这两种做法,还有另外一种,分别进行了数据测试,实测证明了,这个视图缓存方法的确是达到了优化的效果的。详情看此文章。

[Android]ListView性能优化之视图缓存

视图缓存的缺点——

应该注意到,这是一种以空间换时间的做法,因为增加了一个静态类,来达到减少 findViewById的次数。此外,setTag这样附加数据到view中会让view增加额外的负担,这样就造成了设置tagview和不设置tagview大小是不一样,只不过,我们一直都习惯了别人告诉我们这么做,没去比较而已。

 

网友农民伯伯通过反编译新浪微博而得到另外一种做法,就是用自定义的类(继承自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强制类型转换成自定义的布局容器,其中就能把每个组件的引用给转换出来? 求解释。   

农民伯伯最后的验证结果是跟使用视图缓存效果差不多,但是空间上优化了,并且由于没有使用Tagview也不会变小了。