源碼解析之ListView

你們元旦快樂~緩存

好記性不如爛筆頭,因此我準備弄個源碼解析系列,不許備詳細解析源碼,但把基本原理和設計思想梳理清楚,也給本身留個筆記存檔好在後面須要的時候翻起。微信

今天就從ListView開始。函數

ListView的核心在於layoutChildren函數,分兩種狀況,一種是全新加載,第二種是非全新加載。主要區別在於ListView內部的View緩存池的使用,下面依次來說下。佈局

在layoutChildren裏面,會根據LayoutMode選擇調用fillSpecific/fillUp/fillFromTop之類的函數來進行填充,這個只是策略問題不是很關鍵,最終這些函數都調用了makeAndAddView,而後再調用obtainView,再調用adapter.getView,拿到view以後再使用setupChild加入到ListView裏面去並放好位置(包含child本身的measure),流程以下:學習

640?wx_fmt=png&wxfrom=5&wx_lazy=1

640?wx_fmt=png&wxfrom=5&wx_lazy=1

640?wx_fmt=png&wxfrom=5&wx_lazy=1

640?wx_fmt=png&wxfrom=5&wx_lazy=1

setupChild函數裏面片斷:
640?wx_fmt=png&wxfrom=5&wx_lazy=1this

全新加載的很好理解,每一個Item都是按照上面的流程走,而且每一個Item的view都是經過getView裏面inflate出來的(這種狀況getView的convertView傳過來是空,意味着ListView尚未緩存view可使用)spa

非全新加載,好比頁面滑動,或者adapter數據發生變化,這種狀況下面總體流程和全新加載沒有區別,但在部分函數調用裏面有細微差異,好比:設計

layoutChildren裏面首先將ListView的child都放入緩存池:3d

// 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函數的第一段就是:
640?wx_fmt=png&wxfrom=5&wx_lazy=1
更巧妙的是,在拿ActiveView的緩存view的時候,會根據位置來拿,這樣的view認爲是不須要從新通過adapter的getView函數的,這樣極大的提升了效率。(頁面滾動的時候頁面裏面的item只是位置變化,不須要從新調用adapter.getView函數)code

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:
640?wx_fmt=png&wxfrom=5&wx_lazy=1
而後在obtainView裏面,從ScrapViews裏面從新拿出來要使用了,再調用finish:
640?wx_fmt=png&wxfrom=5&wx_lazy=1

這個臨時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)
640?wx_fmt=png&wxfrom=5&wx_lazy=1
640?wx_fmt=png&wxfrom=5&wx_lazy=1
而後setupChild裏面會根據是否是attachedToWindow作不一樣的操做:從新指定parent(attachViewToParent)仍是從新attachToWindow(addViewInLayout)
640?wx_fmt=png&wxfrom=5&wx_lazy=1

源碼解析就怕別人看不懂,希望對同窗們有些幫助~

更多文章請關注微信公衆號:安卓之美

相關文章
相關標籤/搜索