深入了解ListView工作原理(一) -- 之viewHolder优化篇

看到题目,大家可能不禁要问,在网上一搜满大街的是讲ListView工作原理的,还再写一篇这个干吗?不急,没有一点新意,我是绝对不会写重复的东西,若非精品,分享也没有意义。所以,在这里保证,只要耐心看完,一定有干货,并且让你更加清晰的认识到listview内部是怎样工作的。

大家可以先看看网上http://www.bkjia.com/Androidjc/1037874.html这篇介绍ListView的工作原理,差不多就弄懂了ListView是如何和Adapter合作把listView的item一个个加载出来的。但是如果再深入下去,逻辑更复杂的时候就又会变得似是而非,这就说明还有细节没有弄清楚,比如:

1)优化的时候viewHolder能用static来优化吗?
2) 如果viewType不止一种的情况下,getView里的convertView传进来的是对应的viewType吗?

那么接下来,我打算分别用两篇文章来一一解析。

首先,我们温习一下adapter的getView优化的经典写法:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder viewHolder = null;

    if(convertView == null){
        viewHolder = new ViewHolder();
        convertView = mLayoutInflater.inflate(R.layout.lv_item, null);
        viewHolder.mImageView = (ImageView) convertView.findViewById(R.id.iv);
        viewHolder.mTextView = (TextView) convertView.findViewById(R.id.tv);
        convertView.setTag(viewHolder);
    }else{
        viewHolder = (ViewHolder) convertView.getTag();
    }

    viewHolder.mImageView.setBackgroundResource(R.mipmap.ic_launcher);
    viewHolder.mTextView.setText(mData.get(position));

    return convertView;
}

class ViewHolder{
     ImageView mImageView;
     TextView mTextView;
}

可以这么说,大家对这种方式都已经熟烂于心了。我们知道,listview里的每一个item都是通过adapter的getview来得到的,然后根据listview的高度和item的高度来循环加载显示在listevew上,因此首次加载就决定了能够显示出来的item的个数,并且首次走进的都是getview里convertview==null的时候,也就是说,此时加载了几个item(包括只显示一部分的),就创建了几个viewHolder对象,viewHolder里的mImageView和mTextView来指向对应的convertView里的imageView和TextView,方便后面直接操作。如下图所示:

viewHolder初始情况

图中listview显示了6个item,所以生成了6个对应的viewholder对象,并且在listview工作的过程中一直指向它们各自对应的item。注意此时,RecycleBin里还没有任何可以用来复用的convertView(这里为了循序渐进,只有一种ViewType,即最简单的情况,后面会介绍多种ViewType的情况)。若此时,listview向上滚动一部分,使得item 1一部分滚出listview的Top,并且item 7也进入一部分,那么因为此时RecycleBin里没有任何可以复用的ConvertView,所以,程序继续进入convertView == null 的情况,viewholder 7也被创建,指向item 7。如果此时继续滚动item 1至item 2 - 7刚好进入listview,将不会生成新的viewholder,但是!注意,此时滚出去的item 1对应的view将被放入RecycleBin里当做备用的convertView 1,如果继续向上滚动,item 8即将进入listview,此时listview的adapter在RecycleBin里找到了可以复用的convertview 1,而且因为之前用了setTag的方式,因此可以用getTag可以迅速获得viewholder 1,并且viewholder 1是始终指向它的,因此直接可以根据position来设置对应的图片,文本然后return 此更新后的view给listview来显示item 8。看到这里我们就知道listview是如何利用viewholder和convertview协同工作来显示item的。

好了,有了这个基础,那么我们开始可以回答文章最开始提出的第一个问题了,有网友说可以在此基础上把ViewHolder类设为static静态类,更加优化了listview,理由是这样的话整个内存中就只需要存一份viewholder对象就可以了。果真这样吗,做了一个实验,工作起来没问题,但是是否只有一份呢?

//把ViewHolder变为static之后,getView()里加上如下代码:
viewHolder = new ViewHolder();
AppLog.i("viewHolder" + i +" = " + viewHolder );
i++;

20.548 12491-12491/?  [getView()] - viewHolder1 = $ViewHolder@e6a668d
20.627 12491-12491/?  [getView()] - viewHolder2 = $ViewHolder@22e2da45
20.629 12491-12491/?  [getView()] - viewHolder3 = $ViewHolder@355bd5c1
20.632 12491-12491/?  [getView()] - viewHolder4 = $ViewHolder@210f0cfd
20.635 12491-12491/?  [getView()] - viewHolder5 = $ViewHolder@3c47bf9
20.637 12491-12491/?  [getView()] - viewHolder6 = $ViewHolder@35c7deb5
20.640 12491-12491/?  [getView()] - viewHolder7 = $ViewHolder@e64b131
20.642 12491-12491/?  [getView()] - viewHolder8 = $ViewHolder@34222f6d
20.644 12491-12491/?  [getView()] - viewHolder9 = $ViewHolder@34bf5569
20.647 12491-12491/?  [getView()] - viewHolder10 = $ViewHolder@20eedf25
20.650 12491-12491/?  [getView()] - viewHolder11 = $ViewHolder@27d348a1
20.653 12491-12491/?  [getView()] - viewHolder12 = $ViewHolder@2e3acddd
20.656 12491-12491/?  [getView()] - viewHolder13 = $ViewHolder@3290cc9e
40.937 12491-12491/: [MainActivity$1:48 onScrollStateChanged()] - scroll
44.879 12491-12491/: [MainActivity$1:52 onScrollStateChanged()] - fling
44.994 12491-12491/: [MainActivity$1:44 onScrollStateChanged()] - idle

从log来看静态内部类的实例内存地址都是不一样的,所以并没有优化viewholder的个数。这不是我想说的重点,其实只要想想,即便有某种方法用独一份对象(这里暂时叫其为PointerObject)来代替viewholder对象,这样是肯定不行的,因为PointerObject会不断的被赋值覆盖,也就是最终PointerObject只会指向最后一个赋值给它的item,在本例中,它最终指向的是item 7,而item 1 -7 都能用getTag获得此PointerObject,当item 8进入的时候,adapter可以用convertView 1获得此viewholder,但是由于此时viewholder指向的是item 7,所以设置图片,文本错误的把item 7变了,而返回的convertview 1仍然是原来的item 1的样子,即把item 1当做item 8用了,就会错乱下去,因此,这个路子是不行的。还有一点就是,其实根本没有必要,因为viewholder个数=convertview为null的个数,由于复用技术,因此convertview不会太多,一般情况下=listview显示的item个数+1*viewTypeCount。笔者例子中的listview一屏幕显示item为12个,而且viewTypeCount=1,即没有override getViewTypeCount()函数,默认=1。

所以,对于问题1,总结来说:
1)可以工作
2)理由不对,并没有优化,viewholder个数没变
3)此类路子不对
4)没有必要,viewholder个数不会太多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章