Android之瞭解 ListView緩存機制

1.什麼是ListView

在android開發中ListView是非常常用的組件,它以列表的形式來表示,即使是數據夠大,也能根據數據的長度進行自適應來顯示。在一定條件下,其加載大量的數據也不會發生OOM。

在android開發文檔可以看到ListView是直接繼承於AbsListView,AbsListView又繼承AdapterView,AdapterView繼承ViewGroup:


用過listview的開發者都知道adapter,至於爲什麼要出現adapter呢,試想一下:如果沒有adapter,讓listview和數據源直接接觸,那麼listview就要做大量的適配工作,因爲數據源的類型有太多,有可能是一個數組,也有可能是一個集合等等。如果listview去和每一種數據源進行適配的話會導致代碼極其臃腫並且違背了它所顯示的原則。這時候很有必要需要一個東西去做適配工作,android就出現了adapter這個機制,原理如下圖:


adapter中除了要適配數據源的工作以外還要寫一些邏輯是在getView()。

2.ListView工作原理

ListView的工作原理是:例如當屏幕需要顯示9個item時,那麼ListView只會創建10個視圖,當第一個item離開屏幕時,那麼第一個item就會拿來複用,用來顯示第10個item,這種設計是非常棒的。最重要的一個原因是RecycleBin機制,RecycleBin是作爲內部類在AbsListView裏面,因此ListView會用到其機制。那麼現在仔細分析RecycleBin:

(1)成員變量:

 

(2)setViewTypeCount(int viewTypeCount)是初始化


(3)fillActiveViews(int childCount,int firstActivePosition)來存放可見的view,根據參數意思:childCount存放View的數量,firstActivePosition是第一個可見的位置


(4)addScrapView(View scrap,int position)來存放不在於屏幕內的View



(5)getScrapView(int position)從緩存的View獲取


(6)getActiveView(int position)這個方法是得到屏幕上某個位置的顯示的view


(7)getTransientStateView(int position) 獲取具有TransientState狀態的View,獲取後會移除


(8)retrieveFromScrap(ArrayList<View> scrapViews,int position)根據參數可知 是從緩存數組獲取對應的view


(9)clearTransientStateViews() 清除具有TransientStateViews的View


(10)pruneScrapViews()當緩存的數組大於屏幕上活躍的數組後就會走這個方法,在scrapActiveViews()這個方法內調用,至於爲什麼要清理呢?主要是因爲TransientStateView數組中的View的transient屬性消失後,會緩存兩次,一遍是具有transientState的view,另一遍是View不具有transientState又會緩存一遍,這樣緩存中就會導致活躍中的數組,因此需要處理。


(11)markChildrenDirty()可以看到裏面調用緩存的view的forceLayout,這個方法只會執行自己的onMeasure()和onLayout()方法,當ListView的size改變時進行調用。


3.ListView的繪製

因爲ListView 和 GridView都是繼承View的,那緩存的邏輯應該寫在AbsListView裏面,那裏面肯定有onMeasure()方法和onLayout()方法,去看看:



可以看見onLayout()方法其實就是當ListVIew發生變化後,要求子佈局進行重繪。

在看ListView中的layoutChildren();這個方法源碼就不貼了,這個方法主要是確定佈局模式,並且確定佈局順序,從頂到底去填充ListView,裏面有fillFromTop(int nextTop)這個方法裏面調用fillDown(int pos,int nextTop)這個方法主要是遍歷當前顯示在屏幕上的view,並且緩存起來通過執行makeAndAddView()方法,裏面在調用setupChild()方法,最後實際上調用obtainView()方法去獲取一個view,裏面實際上調用了我們最熟悉的getView(),參數有三個分別是位置,convertView,還有this,也就是說當填滿整個屏幕後,就不在繪製了。另外第二次Layout郭霖博客已經描述很清晰了,這裏不再敘述。具體是:先調用detachAllViewsFromParent()方法清掉所有的view,然後再用緩存的view去添加進行加載,最後不會執行obtainView(),因爲如果又要infalte,使效率大大降低,最後重新attachViewToParent()方法。

簡單的流程圖如下: