源碼解析之ListView
大家元旦快樂~
好記性不如爛筆頭,所以我準備弄個源碼解析系列,不準備詳細解析源碼,但把基本原理和設計思想梳理清楚,也給自己留個筆記存檔好在後面需要的時候翻起。
今天就從ListView開始。
ListView的核心在於layoutChildren函數,分兩種情況,一種是全新載入,第二種是非全新載入。主要區別在於ListView內部的View緩存池的使用,下面依次來講下。
在layoutChildren裡面,會根據LayoutMode選擇調用fillSpecific/fillUp/fillFromTop之類的函數來進行填充,這個只是策略問題不是很關鍵,最終這些函數都調用了makeAndAddView,然後再調用obtainView,再調用adapter.getView,拿到view之後再使用setupChild加入到ListView裡面去並放好位置(包含child自己的measure),流程如下:
setupChild函數裡面片段:
全新載入的很好理解,每個Item都是按照上面的流程走,並且每個Item的view都是通過getView裡面inflate出來的(這種情況getView的convertView傳過來是空,意味著ListView還沒有緩存view可以使用)
非全新載入,比如頁面滑動,或者adapter數據發生變化,這種情況下面整體流程和全新載入沒有區別,但在部分函數調用裡面有細微差別,比如:
layoutChildren裡面首先將ListView的child都放入緩存池:
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;final RecycleBin recycleBin = mRecycler;if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); }} else { recycleBin.fillActiveViews(childCount, firstPosition);}
緩存池管理就是這個RecycleBin對象,他裡面有兩種緩存,一種叫ActiveViews,看上面代碼如果不是數據發生變化的非全新載入(比如頁面滾動),則把所有子view都放入ActiveViews,然後重新計算位置重新擺放新的view的時候,就會首先從ActiveViews裡面拿出緩存view,看makeAndAddView函數的第一段就是:
更巧妙的是,在拿ActiveView的緩存view的時候,會根據位置來拿,這樣的view認為是不需要重新經過adapter的getView函數的,這樣極大的提高了效率。(頁面滾動的時候頁面裡面的item只是位置變化,不需要重新調用adapter.getView函數)
View getActiveView(int position) {
int index = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >=0 && index < activeViews.length) { final View match = activeViews[index]; activeViews[index] = null; return match; }return null;
}
假如ActiveViews裡面拿不到緩存view了,比如ListView高度發生了變化,需要更多的view來填充,這個時候就會從另外一種緩存裡面拿,叫做ScrapViews。這種緩存view是屬於ListView被填滿了,結果還剩餘有view就會被放入到這個緩存池裡面來。在layoutChildren函數的尾部可以看到有這樣一段代碼就是這個意思:
// Flush any cached views that did not get reused above
recycleBin.scrapActiveViews();/** * Move all views remaining in mActiveViews to mScrapViews. */void scrapActiveViews() { final View[] activeViews = mActiveViews; final boolean hasListener = mRecyclerListener != null;final boolean multipleScraps = mViewTypeCount > 1;
ArrayList<View> scrapViews = mCurrentScrap; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) {
.......
從ScrapViews拿緩存view的代碼在obtainView裡面:
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);if (scrapView != null) { if (child != scrapView) { // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position);} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true; // Finish the temporary detach started in addScrapView(). child.dispatchFinishTemporaryDetach(); }}
可以看到adapter.getView的第二個參數convertView就是從ScrapViews裡面拿過來的緩存view,可能為空也可能不為空,所以我們的getView函數都要有null判斷。
這樣子我們就能串起來了,在makeAndAddView裡面先看看ActiveViews裡面有沒有緩存view,有的話則直接成功返回也不需要調用adapter.getView。沒有的話則繼續調用obtainView,然後再去ScrapViews裡面看看有沒有緩存view,ScrapViews裡面拿出來的view都需要重新經過adapter.getView進行重新刷數據。ScrapViews可能拿到緩存view也可能拿不到,所以getView實現需要null判斷。
我們可以把ActiveViews和ScrapViews理解為一二級緩存,有效率上面的差別(很明顯後者要經過getView重新綁定數據)
ListView的layoutChildren在開始的時候通過fillActiveViews將子view全部放到ActiveViews裡面,然後等會重新布局的時候又首先從ActiveViews裡面拿出來,不夠用的時候再繼續從ScrapViews裡面拿,非常高效。所以ActiveViews可以理解為layout期間的一個臨時產物。
整體流程就結束了,下面介紹下有些細節的地方可以從中學習到的。
View有onAttachedToWindow/onDetachFromWindow,還有一個不怎麼常用的onStartTemporaryDetach/onFinishTemporaryDetach,表示開始和結束臨時Detach,這種狀態View其實沒有真正觸發onDetachFromWindow,只是臨時的Detached了,在addScrapView函數里看到有調用start,view被放入ScrapViews緩存池了,臨時被detach:
然後在obtainView裡面,從ScrapViews裡面重新拿出來要使用了,再調用finish:
這個臨時detached挺有意思,結合ViewGroup的detachAllViewsFromParent(在layoutChildren裡面一開始就會調用這個方法),讓子view的parent都設成null,可以達到一些很巧妙的實現。
ScrapViews裡面的緩存view有的是真正onDetachFromWindow,有的則是onStartTemporaryDetach,造成這個的原因主要是scrapActiveViews和addScrapView兩個實現上的差異,不過這個不影響實際使用,obtainView裡面會根據實際情況來決定拿到的緩存view是要重新attachToWindow還是finshTemporaryDetach,這個outMetData[0](和mIsScrap[0]是同一個)就是標記緩存view是不是之前已經detachFromWiindow(之前是isTemporarilyDetached的,說明還未真正detachFromWindow)
然後setupChild裡面會根據是不是attachedToWindow做不同的操作:重新指定parent(attachViewToParent)還是重新attachToWindow(addViewInLayout)
源碼解析就怕別人看不懂,但願對同學們有些幫助~
更多文章關注微信公眾號:安卓之美
推薦閱讀: