一種優化 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初始化載入速度的方案。初始化資料來源不宜過大,可以根據佈局佔螢幕的比例來定義。
全文完。