引子
前段时间做了一个类似通讯录侧边导航功能,实现起来蛮简单,网上也有大量现成的demo,但是最近突然啃爹的测试,说有些数据量大的用户反映严重卡顿,我的实现跟网上的一样,大都千遍一律,实际问题很多
问题描述
1,关于加载延迟,没有图片可能大家没有直观的感受,大家可以想象下,就是直接加载2000条以上数据,不加排序什么,iOS是打开页面秒显,Android即使放在内存中加载,也要延迟3秒左右,不同手机性能可能有差距。
经过分析,可能是以下两点问题
1,可能是大量数据生成字母索引和排序导致的卡顿
2,一次性加载的数据量太多,导致展示缓慢
寻找解决方案
1,索引和排序问题 ,优化方案是存入数据库前预先生成字母索引
2,排序,从数据库查询出来时搜索出来时预先排序
3,一次性加载太多导致延迟空白,可以做一次性分页加载
一次性分页加载过程中如果点击页面会出现延迟进入下个界面的问题。并且左边的字母索引不能一次性全显示,于是找了下官方通讯录的源码
官方实现方式
结果发现官方使用的是LoaderManager+cursor+cursorAdapter的方式,官方的源码看上去很复杂,但是它是一次性加载完所有数据然后返回一个Cursor,cursorAdapter利用负责展示数据,脑海中展示2个疑问,
1,cursor一次性加载所有数据不会延迟吗?
我有尝试将所有数据放在内存中,adapter加载的时候还是会延迟。
但是官方的通讯录在大数据量下加载非常顺滑
原来数据库返回Cursor的时候并没有真正去查询,打比方你普通加载99999条数据需要1000ms,返回cursor 只会耗费一秒。
CursorAdapter在显示的时候才会去加载数据
/** * @see android.widget.ListAdapter#getView(int, View, ViewGroup) */ public View getView(int position, View convertView, ViewGroup parent) { if (!mDataValid) { throw new IllegalStateException("this should only be called when the cursor is valid"); } if (!mCursor.moveToPosition(position)) { throw new IllegalStateException("couldn't move cursor to position " + position); } View v; if (convertView == null) { v = newView(mContext, mCursor, parent); } else { v = convertView; } bindView(v, mContext, mCursor); return v; }
2,在查询所有数据的同时,加载了所有字母索引,例如我的
select sortLetters,COUNT(*) as count from member GROUP BY sortLetters order by upper(sortLetters) asc
官方的有点复杂,这个索引还加了缓存
3,保存字母和字母所占数量
labels = new String[numLabels];counts = new int[numLabels]; for (int i = 0; i < numLabels; i++) { cursor.moveToNext(); labels[i] = cursor.getString(0); counts[i] = cursor.getInt(1); }
4,根据字母和数量得到字母position
mSections = sections; mPositions = new int[counts.length]; int size = counts.length; int position = 0; for (int i = 0; i < size; i++) { if (TextUtils.isEmpty(mSections[i])) { mSections[i] = "#"; } mPositions[i] = position; position += counts[i];}mCount = position;
4,根据sectionIndex 获取postion
public int getPositionForSection(int sectionIndex) { if (mSections == null || mPositions == null) { return -1; } if (sectionIndex < 0 || sectionIndex >= mSections.length) { return -1; } return mPositions[sectionIndex]; }
5,根据字母获取Position
public int getPositionForTitle(String title){ for (int i = 0; i < mSections.length; i++) { if(mSections[i].equals(title)){ int position = getPositionForSection(i); MLog.e("test","getPositionForTitle" + position); return position; } } return -1; }
根据postion获取字母所在Section
@Override public int getSectionForPosition(int position) { if (mSections == null || mPositions == null) { return -1; } if (position < 0 || position >= mCount) { return -1; } int index = Arrays.binarySearch(mPositions, position); return index; }
修改过后,进入页面就能立即看到数据,以前即使在内存中直接加载,也会有几秒的空白时间
作者:岁月留痕