Android中列表是每个应用都会有的UI效果,而与用户的交互无非就是用户上下滑动、左右滑动、点击item 等等,本文就从小编遇到一次加载大量数据而影响体验优化之旅。
项目的列表采用RecycleView + BaseMultiItemQuickAdapter 分组效果,数据量10000~20000以上
数据拉取、缓存
首先是数据的获取方式,分页?还是全部获取? 这得考虑到后端的查询效率,数据库可以采用建立索引,提高效率,前端可以采用分页获取,并且为了提高体验,当然是存储在本地数据库了(NoSql),这里就有一个问题分页获取分页 再存储,数据库存储会导致阻塞线程,
果然出现了:Message Blocked in Main Thread XXXX ms,这是anr日志,说明,执行了耗时任务,UI 无响应。。。。。。。。。。
所以就想到了
这里采用了线程控制存储(建议采用线程池的方式来实现,不然物极必反,过多创建线程,浪费了资源。。。)
1
2
3
4public CustomerReposity(){
mThreadPool = new ThreadPoolExecutor(3, 5,
10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(128));
}
1 | mThreadPool.execute(new Runnable() { |
从后端拉取到解析-再到数据库的时间
这边其实考虑到序列化,序列化有两种 Serializable\Parcelable,Serializable 采用反射机制,会导致大量对象产生,所以,考虑到性能的话,采用Parcelable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@Override
public void writeToParcel(Parcel out, int i) {
out.writeString(cid);
pingYin = in.readString();
}
public static final Parcelable.Creator<Customer> CREATOR = new Parcelable.Creator<Customer>() {
@Override
public Customer createFromParcel(Parcel source) {
return new Customer(source);
}
@Override
public Customer[] newArray(int size) {
return new Customer[size];
}
};
总不能全部加载完毕才显示给用户看吧?
刚开始采用了数据监听(LiveData的方式,项目架构本身采用MVVM的方式)
1
2
3
4
5
6
7
mReposity.getCustomerDefaultTeam().observe(this, new Observer<List<Customer>>() {
@Override
public void onChanged(@Nullable List<Customer> customers) {
mAdapter.setNewData(customers);
}
});
又会有问题:数据边加载边刷新??? 列表一直notifyDataChanged………这边采用preFetch思想,先给用户展示部分数据
RecycleView 优化
布局优化
也就是item 布局,减少层级,这样就避免多次绘制UI,如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源,如果不要求动画,可以通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭来提神效率,通过 getExtraLayoutSpace 来增加 RecyclerView 预留的额外空间(显示范围之外,应该额外缓存的空间),如下所示
1
2
3
4
5
6
7
8
9
10
11mViewDataBinding.rvCustomers.setLayoutManager(new LinearLayoutManager(this){
@Override
protected int getExtraLayoutSpace(RecyclerView.State state) {
return 100;
}
});
mViewDataBinding.rvCustomers.setAdapter(mAdapter);
mViewDataBinding.rvCustomers.setHasFixedSize(true);
mViewDataBinding.rvCustomers.setItemViewCacheSize(20);
((SimpleItemAnimator) mViewDataBinding.rvCustomers.getItemAnimator()).setSupportsChangeAnima mAdapter.expandAll();
mCustomerReposity = CustomerReposity.getmInstances();
可参考资料来源:https://blankj.com/2018/09/29/optimize-recycler-view/
List Remove效率
在页面点击展开分组、收缩分组的时候发现,展开的时候很顺利,收缩的时候即卡顿个几秒,,看下日志ANR。。。。。,查看 BaseQuickAdapter 源码
1 | public int collapse(@IntRange(from = 0) int position, boolean animate, boolean notify) { |
因此分析 原因估计是出在recursiveCollapse(position)这个方法 ,继续往下看1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@SuppressWarnings("unchecked")
private int recursiveCollapse(@IntRange(from = 0) int position) {
T item = getItem(position);
if (!isExpandable(item)) {
return 0;
}
IExpandable expandable = (IExpandable) item;
int subItemCount = 0;
if (expandable.isExpanded()) {
List<T> subItems = expandable.getSubItems();
if (null == subItems) return 0;
for (int i = subItems.size() - 1; i >= 0; i--) {
T subItem = subItems.get(i);
int pos = getItemPosition(subItem);
if (pos < 0) {
continue;
}
if (subItem instanceof IExpandable) {
subItemCount += recursiveCollapse(pos);
}
mData.remove(pos);
subItemCount++;
}
}
return subItemCount;
}
看了下代码好像没做什么,就是删除分组下的item,就想到了,我一个分组有几十W+的item,它这样remove,是不是时间花太久了??????》〉》〉
果然 remove 方法执行耗时了。。。。。。开始改造
```java
/**
* 改造源码****###
* @param position
* @return
*/
private int recursiveCollapse(@IntRange(from = 0) int position) {
MultiItemEntity item = getItem(position);
if (!isExpandable(item)) {
return 0;
}
IExpandable expandable = (IExpandable) item;
if (expandable.isExpanded()) {
List<MultiItemEntity> subItems = expandable.getSubItems();
mData = removeAllChildList(mData,subItems);
}
return expandable.getSubItems().size();
}
public int collapsew(@IntRange(from = 0) int position, boolean animate) {
return collapses(position, animate, true);
}
/**
* 替代List.removeAll()的方法提高效率
* @param source
* @param destination
* @return
*/
public List<MultiItemEntity> removeAllChildList(List<MultiItemEntity> source, List<MultiItemEntity> destination) {
List<MultiItemEntity> afterRemove = new LinkedList<MultiItemEntity>();
Set<MultiItemEntity> destinaToRemove = new HashSet<MultiItemEntity>(destination);
for (MultiItemEntity t : source) {
if (!destinaToRemove.contains(t)) {
afterRemove.add(t);
}
}
return afterRemove;
}
```java