记一次优化列表(BaseMultiItemQuickAdapter 源码分析)

Android中列表是每个应用都会有的UI效果,而与用户的交互无非就是用户上下滑动、左右滑动、点击item 等等,本文就从小编遇到一次加载大量数据而影响体验优化之旅。

项目的列表采用RecycleView + BaseMultiItemQuickAdapter 分组效果,数据量10000~20000以上

数据拉取、缓存

首先是数据的获取方式,分页?还是全部获取? 这得考虑到后端的查询效率,数据库可以采用建立索引,提高效率,前端可以采用分页获取,并且为了提高体验,当然是存储在本地数据库了(NoSql),这里就有一个问题分页获取分页 再存储,数据库存储会导致阻塞线程,

果然出现了:Message Blocked in Main Thread XXXX ms,这是anr日志,说明,执行了耗时任务,UI 无响应。。。。。。。。。。
所以就想到了
这里采用了线程控制存储(建议采用线程池的方式来实现,不然物极必反,过多创建线程,浪费了资源。。。)

1
2
3
4
public CustomerReposity(){
mThreadPool = new ThreadPoolExecutor(3, 5,
10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(128));
}

1
2
3
4
5
6
7
mThreadPool.execute(new Runnable() {
@Override
public void run() {
Log.d("customer","thread save to db ::"+data.list.size());
saveCustomerToLocalDB(data.list, pageNumer == 1);
}
});

从后端拉取到解析-再到数据库的时间

这边其实考虑到序列化,序列化有两种 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
11
mViewDataBinding.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int collapse(@IntRange(from = 0) int position, boolean animate, boolean notify) {
position -= getHeaderLayoutCount();

IExpandable expandable = getExpandableItem(position);
if (expandable == null) {
return 0;
}
int subItemCount = recursiveCollapse(position);
expandable.setExpanded(false);
int parentPos = position + getHeaderLayoutCount();
if (notify) {
if (animate) {
notifyItemChanged(parentPos);
notifyItemRangeRemoved(parentPos + 1, subItemCount);
} else {
notifyDataSetChanged();
}
}
return subItemCount;
}

因此分析 原因估计是出在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