版權(quán)聲明:本文已授權(quán)微信公眾號:Android必修課撵术,轉(zhuǎn)載請申明出處
自Android5.0以來钠乏,RecyclerView漸漸取代ListView成為Android開發(fā)中使用最多的列表控件代箭,對于RecyclerView的使用相信大家都不陌生墩划,但對于RecyclerView的高效刷新,卻是很多人不知道的嗡综。
簡單粗暴的刷新方式
Adapter.notifyDataSetChanged();
這種方式想必是大家曾經(jīng)用的最多的一種刷新Adapter的方式乙帮,它的缺點很明顯:
- 無腦刷新整個RecyclerView可視區(qū)域,每個item重繪极景,如果你的onBindViewHolder邏輯處理稍微復(fù)雜一些察净,則容易造成卡頓
- 無法觸發(fā)RecyclerView的item動畫,用戶體驗極差盼樟。
局部刷新方式
為了解決上述問題氢卡,RecyclerView推出了局部刷新的方式
Adapter.notifyItemChanged(int)
Adapter.notifyItemInserted(int)
Adapter.notifyItemRangeChanged(int, int)
Adapter.notifyItemRangeInserted(int, int)
Adapter.notifyItemRangeRemoved(int, int)
局部刷新只會刷新指定position的item,這樣完美解決了上述簡單粗暴刷新方式的缺點晨缴,但是:
- 局部刷新需要指定item的position译秦,如果你只更新了一條數(shù)據(jù),那么你可以很容易知道position位置击碗,但是如果你更新的是整個列表筑悴,你需要計算出所有你需要刷新的position,那么這將是一場災(zāi)難
DiffUtil
Google似乎也注意到了這一點稍途,因此在support-recyclerview-v7:24.2.0中阁吝,推出了一個用于計算哪些位置需要刷新的工具類:DiffUtil。
使用DiffUtil晰房,有3個步驟
1.自實現(xiàn)DiffUtil.callback
private DiffUtil.Callback diffCallback = new DiffUtil.Callback() {
@Override
public int getOldListSize() {
// 返回舊數(shù)據(jù)的長度
return oldList == null ? 0 : oldList.size();
}
@Override
public int getNewListSize() {
// 返回新數(shù)據(jù)的長度
return newList == null ? 0 : newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
// 返回兩個item是否相同
// 例如:此處兩個item的數(shù)據(jù)實體是User類求摇,所以以id作為兩個item是否相同的依據(jù)
// 即此處返回兩個user的id是否相同
return TextUtils.equals(oldList.get(oldItemPosition).getId(), newList.get(oldItemPosition).getId());
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
// 當(dāng)areItemsTheSame返回true時,我們還需要判斷兩個item的內(nèi)容是否相同
// 此處以User的age作為兩個item內(nèi)容是否相同的依據(jù)
// 即返回兩個user的age是否相同
return oldList.get(oldItemPosition).getAge() == newList.get(newItemPosition).getAge();
}
};
2.計算得到DiffResult
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
3.將DiffResult設(shè)置給Adapter
// 注意此處一定要將新數(shù)據(jù)設(shè)置給Adapter
// 否則會造成ui刷新了但數(shù)據(jù)未更新的bug
mAdapter.setData(newList);
diffResult.dispatchUpdatesTo(mAdapter);
這樣我們就實現(xiàn)了局部刷新位置的計算和局部刷新的實現(xiàn)殊者,相比notifyDataSetChanged()与境,性能大大提高。
本文到此結(jié)束猖吴?
不不不摔刁,還早著呢,咱們理智分析一下:
- 首先DiffUtil.calculateDiff()這個方法是執(zhí)行在主線程的海蔽,如果新舊數(shù)據(jù)List比較大共屈,那么這個方法鐵定是會阻塞主線程的
- 計算出DiffResult后,咱們必須要將新數(shù)據(jù)設(shè)置給Adapter党窜,然后才能調(diào)用DiffResult.dispatchUpdatesTo(Adapter)刷新ui拗引,然而很多人都會忘記這一步。
AsyncListDiff
DiffUtil已經(jīng)很好用了幌衣,但是有上述兩個問題矾削,想必Google的工程師也是看不下去的壤玫,雖然上述兩個問題不難解決,但是很容易遺漏哼凯。
因此Google又推出了一個新的類AsyncListDiff
先來看一波AsyncListDiff的使用方式:
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHodler> {
private AsyncListDiffer<User> mDiffer;
private DiffUtil.ItemCallback<User> diffCallback = new DiffUtil.ItemCallback<User>() {
@Override
public boolean areItemsTheSame(User oldItem, User newItem) {
return TextUtils.equals(oldItem.getId(), newItem.getId());
}
@Override
public boolean areContentsTheSame(User oldItem, User newItem) {
return oldItem.getAge() == newItem.getAge();
}
};
public UserAdapter() {
mDiffer = new AsyncListDiffer<>(this, diffCallback);
}
@Override
public int getItemCount() {
return mDiffer.getCurrentList().size();
}
public void submitList(List<User> data) {
mDiffer.submitList(data);
}
public User getItem(int position) {
return mDiffer.getCurrentList().get(position);
}
@NonNull
@Override
public UserAdapter.UserViewHodler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user_list, parent, false);
return new UserViewHodler(itemView);
}
@Override
public void onBindViewHolder(@NonNull UserAdapter.UserViewHodler holder, int position) {
holder.setData(getItem(position));
}
class UserViewHodler extends RecyclerView.ViewHolder {
private TextView tvName;
private TextView tvAge;
public UserViewHodler(View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tv_name);
tvAge = itemView.findViewById(R.id.tv_age);
}
public void setData(User data) {
tvName.setText(data.getName());
tvAge.setText(String.valueOf(data.getAge()));
}
}
}
這里使用了一個簡單的Adapter例子欲间,不做封裝,是為了更好地說明AsyncListDiffer断部。
不難看出猎贴,AsyncListDiffer的使用步驟:
- 自實現(xiàn)DiffUtil.ItemCallback,給出item差異性計算條件
- 將所有對數(shù)據(jù)的操作代理給AsyncListDiffer蝴光,可以看到這個Adapter是沒有List數(shù)據(jù)的
- 使用submitList()更新數(shù)據(jù)她渴,并刷新ui
ok,咱們看一下效果:
首先我們給Adapter設(shè)置數(shù)據(jù)
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(String.valueOf(i), "用戶" + i, i + 20));
}
mAdapter.submitList(users);
然后修改數(shù)據(jù)
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(String.valueOf(i), "用戶" + i, i % 3 == 0 ? i + 10: i + 20));
}
mAdapter.submitList(users);
跑起來看一哈
ok蔑祟,我們看到只有被3整除的position被刷新了惹骂,完美的局部刷新。
那么問題來了做瞪,AsyncListDiffer是如何解決我們上述的兩個問題的呢?
解惑
我們走進(jìn)AsyncListDiffer的源碼看一下:
public class AsyncListDiffer<T> {
private final ListUpdateCallback mUpdateCallback;
private final AsyncDifferConfig<T> mConfig;
public AsyncListDiffer(@NonNull RecyclerView.Adapter adapter,
@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mUpdateCallback = new AdapterListUpdateCallback(adapter);
mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build();
}
private List<T> mList;
private List<T> mReadOnlyList = Collections.emptyList();
private int mMaxScheduledGeneration;
public List<T> getCurrentList() {
return mReadOnlyList;
}
public void submitList(final List<T> newList) {
if (newList == mList) {
// 如果新舊數(shù)據(jù)相同,則啥事不做
return;
}
// 用于控制計算線程右冻,防止在上一次submitList未完成時装蓬,
// 又多次調(diào)用submitList,這里只返回最后一個計算的DiffResult
final int runGeneration = ++mMaxScheduledGeneration;
if (newList == null) {
// 如果新數(shù)據(jù)集為空纱扭,此種情況不需要計算diff
// 直接清空數(shù)據(jù)即可
// 通知item remove
mUpdateCallback.onRemoved(0, mList.size());
mList = null;
mReadOnlyList = Collections.emptyList();
return;
}
if (mList == null) {
// 如果舊數(shù)據(jù)集為空牍帚,此種情況不需要計算diff
// 直接將新數(shù)據(jù)添加到舊數(shù)據(jù)集即可
// 通知item insert
mUpdateCallback.onInserted(0, newList.size());
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
return;
}
final List<T> oldList = mList;
// 在子線程中計算DiffResult
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mConfig.getDiffCallback().areItemsTheSame(
oldList.get(oldItemPosition), newList.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mConfig.getDiffCallback().areContentsTheSame(
oldList.get(oldItemPosition), newList.get(newItemPosition));
}
});
// 在主線程中更新數(shù)據(jù)
mConfig.getMainThreadExecutor().execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
latchList(newList, result);
}
}
});
}
});
}
private void latchList(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
diffResult.dispatchUpdatesTo(mUpdateCallback);
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
}
}
線程部分源碼:
private static class MainThreadExecutor implements Executor {
final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
mHandler.post(command);
}
}
@NonNull
public AsyncDifferConfig<T> build() {
if (mMainThreadExecutor == null) {
mMainThreadExecutor = sMainThreadExecutor;
}
if (mBackgroundThreadExecutor == null) {
synchronized (sExecutorLock) {
if (sDiffExecutor == null) {
sDiffExecutor = Executors.newFixedThreadPool(2);
}
}
mBackgroundThreadExecutor = sDiffExecutor;
}
return new AsyncDifferConfig<>(
mMainThreadExecutor,
mBackgroundThreadExecutor,
mDiffCallback);
}
ui刷新部分源碼:
public final class AdapterListUpdateCallback implements ListUpdateCallback {
@NonNull
private final RecyclerView.Adapter mAdapter;
public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) {
mAdapter = adapter;
}
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
mAdapter.notifyItemRangeChanged(position, count, payload);
}
}
源碼實現(xiàn)很簡單,總結(jié)一下:
- 首先排除新舊數(shù)據(jù)為空的情況乳蛾,這種情況不需要計算diff
- 在子線程中計算DiffResult暗赶,在主線程將DiffResult設(shè)置給Adapter,解決主線程阻塞問題
- 將Adapter的數(shù)據(jù)代理給AsyncListDiffer肃叶,解決Adapter與DiffUtil的數(shù)據(jù)一致性問題
完結(jié)蹂随,撒花
喜歡這篇文章記得給我一個小心心哦