Android中列表是每個應用都會有的UI效果,而與使用者的互動無非就是使用者上下滑動、左右滑動、點選item 等等,本文就從小編遇到一次載入大量資料而影響體驗優化之旅。
專案的列表採用RecycleView + BaseMultiItemQuickAdapter 分組效果,資料量10000~20000以上
資料拉取、快取
首先是資料的獲取方式,分頁?還是全部獲取? 這得考慮到後端的查詢效率,資料庫可以採用建立索引,提高效率,前端可以採用分頁獲取,並且為了提高體驗,當然是儲存在本地資料庫了(NoSql),這裡就有一個問題分頁獲取分頁 再儲存,資料庫儲存會導致阻塞執行緒,
果然出現了:Message Blocked in Main Thread XXXX ms,這是anr日誌,說明,執行了耗時任務,UI 無響應。。。。。。。。。。 所以就想到了 這裡採用了執行緒控制儲存(建議採用執行緒池的方式來實現,不然物極必反,過多建立執行緒,浪費了資源。。。)
public CustomerReposity(){
mThreadPool = new ThreadPoolExecutor(3, 5,
10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(128));
}
複製程式碼
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
@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的方式)
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 預留的額外空間(顯示範圍之外,應該額外快取的空間),如下所示
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();
複製程式碼
可參考資料來源:blankj.com/2018/09/29/…
List Remove效率
在頁面點選展開分組、收縮分組的時候發現,展開的時候很順利,收縮的時候即卡頓個幾秒,,看下日誌ANR。。。。。,檢視 BaseQuickAdapter 原始碼
複製程式碼
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)這個方法 ,繼續往下看
@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 方法執行耗時了。。。。。。開始改造
/**
* 改造原始碼****###
* @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
# 部落格:http://guosen.github.io/
複製程式碼