原文地址:https://android.jlelse.eu/a-nice-combination-of-rxjava-and-diffutil-fe3807186012
如果你使用RecyclerView,并且保持了與API相同的更新,你也許會注意到DIffUtil類添加了好幾個版本,這個優秀的工具類通過將已經存在的數據和新生成的數據進行對比,然后調用notifyItemInserted
和notifyItemRemoved
來輕松實現數據的改變。你需要做的就是實現一個回調,在回調中將現有數據和新獲取的數據進行比較。
private static class MyDiffCallback extends DiffUtil.Callback{
private List<Thing> current;
private List<Thing> next;
public MyDiffCallback(List<Thing> current,List<Thing> next){
this.current = current;
this.next = next;
}
@Override
public int getOldListSize(){
return current.size();
}
@Override
public int getNewListSize(){
return next.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition,int newItemPosition){
Thing currentItem = current.get(oldItemPosition);
Thing nextItem = next.get(newItemPosition);
return currentItem.getId() == nextItem.getId();
}
@Override
public boolean areContentsTheSame(int oldItemPosition,int newItemPosition){
Thing currentItem = current.get(oldItemPosition);
Thing nextItem = current.get(newItemPosition);
return currentItem.equals(nextItem);
}
}
DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(current,next),true);
diffResult.dispatchUpdatesTo(adapter);
上述代碼顯示了如何實現回調,如何執行計算以及如何將notify-call分配到RecyclerView的適配器。
然而這里還存在一個挑戰那就是如果數據量特別大的話,或者對比比較復雜的情況下,你不能在主線程中調用計算功能,需要把這些移到一個后臺進程,然后把結果發送給主線程用來設置新的數據,
現在事情變得很棘手,因為DiggUtil計算需要用到已經存在的數據和新的數據,如果你的適配器有一個setData()方法,那么他將需要一個getData()方法,這意味著將從多個線程訪問數據,因此你需要一些用于同步或者線程安全的數據結構,如何避免這種情況呢。
使用Rxjava將會解決一切問題,假設你有一個Flowable<List<Thing>> listOfThings,他將發出RecycleView將顯示的獲取的新版本的數據,我們可以在IO或者在計算線程調度以免阻塞主線程,然后再主線程進行觀察事件將數據傳遞給adapter。
ThingRepository
.latestThings(2,TimeUnit.SECONDS)
.subscribeOn(computation())
.observeOn(mainThread())
.subscribe(things -> {
adapter.setThings(things);
adapter.notifyDataSetChanged();
})
以上的代碼確保了在計算線程中獲取新的數據列表,并且發送給主線程然后調用adapter的notifyDataSetChanged,這樣做有效,但是看起來不是很好,因為每個新列表都需要重新繪制。
為了使用DiffUtil,我們需要調用calculateDiff()方法,并將DiffResult與最新的List<Thing>一起傳遞給我們的訂閱者,一個簡單的方法是使用Pair類,這是支持庫中一個簡單并且強大的實現類,我們將更改訂閱以接受Pair<List<Thing>,<DiffResult>>
事件。
.subscribe(listDiffResultPair -> {
List<Thing> nextThings = listDiffResultPair.first;
DiffUtil.DiffResult diffResult = listDiffResultPair.second;
adapter.setThings(nextThings);
diffResult.dispatchUpdatesTo(adapter);
});
.scan()
傳遞給DiffUtil.calculateDiff()的回調需要當前列表和新列表才能生效,我們如何確保我們從數據源所獲取的每一個新列表,我們也想獲取上一個事件,這也是RxJava更神秘的一個運算符scan(),scan的初始值將是一個由空列表和空值作為DiffResult值組成的對。
現在我們將會調用calculateDiff()并將其列在我們的Pair中,我們使用結果構建一個新的DiffResult。
我們還有一件事要考慮,如果我們這樣離開,第一件事將包含一個DiffResult為null的Pair,所以我們必須在我們的訂閱中對null進行檢查,但是,由于也有可能我們開始的時候是一個空列表,所以我們可以使用skip運算符來簡單的跳過他,這將會忽略第一個事件。
List<Thing> emptyList = new ArrayList<>();
adapter.setThings(emptyList);
Pair<List<Things>,DiffUtil.DiffResult> initialPair = Pair.create(emptyList,null);
ThingRepository
.latestThings(2,TimeUnit.SECONDS)
.scan(initialPair,(pair,next) -> {
MyDiffCallback callback = new MyDiffCallback(pair.first,next);
DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback);
return Pair.create(next,result);
})
.skip(1);
這可以使用RxJava和一些職能操作符在后臺線程上使用DiffUtil,還沒有必要考慮適配器保存的當前數據的任何同步或并發,我們的實力應用程序的結果可以在下面看到。
Demo地址: