深入瞭解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個數不會太多。

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