一种优化 ListView 初始化加载速度的方案

我在使用 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方法都是这样实现的:

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

只要控制好初始化的数据源大小即可。

这是一种优化ListView初始化加载速度的方案。初始化数据源不宜过大,可以根据布局占屏幕的比例来定义。

全文完。