大家元旦快樂~
好記性不如爛筆頭,所以我準備弄個源碼解析系列,不準備詳細解析源碼,但把基本原理和設(shè)計思想梳理清楚,也給自己留個筆記存檔好在后面需要的時候翻起。
今天就從ListView開始。
ListView的核心在于layoutChildren函數(shù),分兩種情況,一種是全新加載,第二種是非全新加載。主要區(qū)別在于ListView內(nèi)部的View緩存池的使用,下面依次來講下。
在layoutChildren里面,會根據(jù)LayoutMode選擇調(diào)用fillSpecific/fillUp/fillFromTop之類的函數(shù)來進行填充,這個只是策略問題不是很關(guān)鍵,最終這些函數(shù)都調(diào)用了makeAndAddView,然后再調(diào)用obtainView,再調(diào)用adapter.getView,拿到view之后再使用setupChild加入到ListView里面去并放好位置(包含child自己的measure),流程如下:




setupChild函數(shù)里面片段:

全新加載的很好理解,每個Item都是按照上面的流程走,并且每個Item的view都是通過getView里面inflate出來的(這種情況getView的convertView傳過來是空,意味著ListView還沒有緩存view可以使用)
非全新加載,比如頁面滑動,或者adapter數(shù)據(jù)發(fā)生變化,這種情況下面整體流程和全新加載沒有區(qū)別,但在部分函數(shù)調(diào)用里面有細微差別,比如:
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,看上面代碼如果不是數(shù)據(jù)發(fā)生變化的非全新加載(比如頁面滾動),則把所有子view都放入ActiveViews,然后重新計算位置重新擺放新的view的時候,就會首先從ActiveViews里面拿出緩存view,看makeAndAddView函數(shù)的第一段就是:

更巧妙的是,在拿ActiveView的緩存view的時候,會根據(jù)位置來拿,這樣的view認為是不需要重新經(jīng)過adapter的getView函數(shù)的,這樣極大的提高了效率。(頁面滾動的時候頁面里面的item只是位置變化,不需要重新調(diào)用adapter.getView函數(shù))
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高度發(fā)生了變化,需要更多的view來填充,這個時候就會從另外一種緩存里面拿,叫做ScrapViews。這種緩存view是屬于ListView被填滿了,結(jié)果還剩余有view就會被放入到這個緩存池里面來。在layoutChildren函數(shù)的尾部可以看到有這樣一段代碼就是這個意思:
// 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 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的第二個參數(shù)convertView就是從ScrapViews里面拿過來的緩存view,可能為空也可能不為空,所以我們的getView函數(shù)都要有null判斷。
這樣子我們就能串起來了,在makeAndAddView里面先看看ActiveViews里面有沒有緩存view,有的話則直接成功返回也不需要調(diào)用adapter.getView。沒有的話則繼續(xù)調(diào)用obtainView,然后再去ScrapViews里面看看有沒有緩存view,ScrapViews里面拿出來的view都需要重新經(jīng)過adapter.getView進行重新刷數(shù)據(jù)。ScrapViews可能拿到緩存view也可能拿不到,所以getView實現(xiàn)需要null判斷。
我們可以把ActiveViews和ScrapViews理解為一二級緩存,有效率上面的差別(很明顯后者要經(jīng)過getView重新綁定數(shù)據(jù))
ListView的layoutChildren在開始的時候通過fillActiveViews將子view全部放到ActiveViews里面,然后等會重新布局的時候又首先從ActiveViews里面拿出來,不夠用的時候再繼續(xù)從ScrapViews里面拿,非常高效。所以ActiveViews可以理解為layout期間的一個臨時產(chǎn)物。
整體流程就結(jié)束了,下面介紹下有些細節(jié)的地方可以從中學(xué)習(xí)到的。
View有onAttachedToWindow/onDetachFromWindow,還有一個不怎么常用的onStartTemporaryDetach/onFinishTemporaryDetach,表示開始和結(jié)束臨時Detach,這種狀態(tài)View其實沒有真正觸發(fā)onDetachFromWindow,只是臨時的Detached了,在addScrapView函數(shù)里看到有調(diào)用start,view被放入ScrapViews緩存池了,臨時被detach:

然后在obtainView里面,從ScrapViews里面重新拿出來要使用了,再調(diào)用finish:

這個臨時detached挺有意思,結(jié)合ViewGroup的detachAllViewsFromParent(在layoutChildren里面一開始就會調(diào)用這個方法),讓子view的parent都設(shè)成null,可以達到一些很巧妙的實現(xiàn)。
ScrapViews里面的緩存view有的是真正onDetachFromWindow,有的則是onStartTemporaryDetach,造成這個的原因主要是scrapActiveViews和addScrapView兩個實現(xiàn)上的差異,不過這個不影響實際使用,obtainView里面會根據(jù)實際情況來決定拿到的緩存view是要重新attachToWindow還是finshTemporaryDetach,這個outMetData[0](和mIsScrap[0]是同一個)就是標記緩存view是不是之前已經(jīng)detachFromWiindow(之前是isTemporarilyDetached的,說明還未真正detachFromWindow)


然后setupChild里面會根據(jù)是不是attachedToWindow做不同的操作:重新指定parent(attachViewToParent)還是重新attachToWindow(addViewInLayout)

源碼解析就怕別人看不懂,但愿對同學(xué)們有些幫助~
更多文章關(guān)注微信公眾號:安卓之美