1. 程式人生 > >一種優化 ListView 初始化載入速度的方案

一種優化 ListView 初始化載入速度的方案

我在使用 ListView 的時候,有一個問題困擾我挺久:能不能控制 ListView 初始化時載入的Item數量?
Adapter的getCount方法?我一直以為getCount方法是用來告訴 Listview,Adapter有多少資料的,而ListView初始化時只加載一個螢幕的資料。那到底是這樣嗎?

為什麼要控制 ListView 初始化時載入的Item數量?
比如,如果我剛開啟一個頁面,ListView關聯Adapter就開始載入十幾條資料,如果載入的Item是TextView還好,影響不大,但如果是Webview呢?有些業務是需要Webview來作為ListView的Item,這時候就卡爆了。

我對ListView不怎麼熟悉,因為我剛開始學習Android不久,Recyclerview就開始火了,經常用的也是RecyclerView,在學校時做的小作品也比較簡單,在列表載入遇到的坑也比較少,現在工作了,不停的踩坑,發現要認真學一下ListView和RecyclerView才行。從實現原理入手。

接下來提出一種優化ListView的方案:
初始化ListView時,如果Item過於複雜,那麼初始化時資料來源應儘量小。

我以前從來就沒有關心過初始化資料來源大小的問題,現在從原始碼來分析。

ListView在初始化時,會呼叫setAdapter方法來關聯Adapter。在setAdapter方法,又會呼叫到getCount方法,原始碼如下所示:

public void setAdapter(ListAdapter adapter) {
    //程式碼省略
    // AbsListView#setAdapter will update choice mode states.
    super.setAdapter(adapter);

    if (mAdapter != null) {

        mOldItemCount = mItemCount;
        mItemCount = mAdapter.getCount();
        checkFocus();

        mAdapter.registerDataSetObserver(mDataSetObserver);

        mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());


    requestLayout();
}

這個mItemCount欄位很重要。
在之後ListView填充資料時,會呼叫fillDown方法:

private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }

    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos++;
    }

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

從這裡可以知道,會進入一個while迴圈來填充資料。
又會呼叫到makeAndAddView方法,原始碼如下所示:

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    View child;

    // Make a new view for this position, or convert an unused view if possible
    child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

在這裡呼叫到了obtainView方法,obtainView在ListView的父類AbsListView裡,原始碼如下所示:

iew obtainView(int position, boolean[] isScrap) {


    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()) {
                isScrap[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            } else {
                // we set isScrap to "true" only if the view is temporarily detached.
                // if the view is fully detached, it is as good as a view created by the
                // adapter
                isScrap[0] = false;
            }

        }
    }


    return child;
}

到這裡已經可以看到Adapter的getView方法了,由此我們可以得出結論,fillDown方法確實是在填充資料。

回到fillDown方法,進入一個死迴圈來填充資料,而從迴圈的條件:pos < mItemCount 可以知道,getCount方法返回的數值多大,那麼listView初始化時就要填充多少資料。

而通常我們在getCount方法都是這樣實現的:

@Override
public int getCount() {
    return mData.size();
}

只要控制好初始化的資料來源大小即可。

這是一種優化ListView初始化載入速度的方案。初始化資料來源不宜過大,可以根據佈局佔螢幕的比例來定義。

全文完。