原文地址:https://android.jlelse.eu/a-nice-combination-of-rxjava-and-diffutil-fe3807186012
如果你使用RecyclerView族檬,并且保持了與API相同的更新涧团,你也許會(huì)注意到DIffUtil類添加了好幾個(gè)版本行疏,這個(gè)優(yōu)秀的工具類通過(guò)將已經(jīng)存在的數(shù)據(jù)和新生成的數(shù)據(jù)進(jìn)行對(duì)比,然后調(diào)用notifyItemInserted
和notifyItemRemoved
來(lái)輕松實(shí)現(xiàn)數(shù)據(jù)的改變懈万。你需要做的就是實(shí)現(xiàn)一個(gè)回調(diào),在回調(diào)中將現(xiàn)有數(shù)據(jù)和新獲取的數(shù)據(jù)進(jìn)行比較。
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);
上述代碼顯示了如何實(shí)現(xiàn)回調(diào)帝璧,如何執(zhí)行計(jì)算以及如何將notify-call分配到RecyclerView的適配器。
然而這里還存在一個(gè)挑戰(zhàn)那就是如果數(shù)據(jù)量特別大的話湿刽,或者對(duì)比比較復(fù)雜的情況下的烁,你不能在主線程中調(diào)用計(jì)算功能,需要把這些移到一個(gè)后臺(tái)進(jìn)程诈闺,然后把結(jié)果發(fā)送給主線程用來(lái)設(shè)置新的數(shù)據(jù)渴庆,
現(xiàn)在事情變得很棘手,因?yàn)镈iggUtil計(jì)算需要用到已經(jīng)存在的數(shù)據(jù)和新的數(shù)據(jù)雅镊,如果你的適配器有一個(gè)setData()方法襟雷,那么他將需要一個(gè)getData()方法,這意味著將從多個(gè)線程訪問(wèn)數(shù)據(jù)仁烹,因此你需要一些用于同步或者線程安全的數(shù)據(jù)結(jié)構(gòu)耸弄,如何避免這種情況呢。
使用Rxjava將會(huì)解決一切問(wèn)題卓缰,假設(shè)你有一個(gè)Flowable<List<Thing>> listOfThings,他將發(fā)出RecycleView將顯示的獲取的新版本的數(shù)據(jù)计呈,我們可以在IO或者在計(jì)算線程調(diào)度以免阻塞主線程砰诵,然后再主線程進(jìn)行觀察事件將數(shù)據(jù)傳遞給adapter。
ThingRepository
.latestThings(2,TimeUnit.SECONDS)
.subscribeOn(computation())
.observeOn(mainThread())
.subscribe(things -> {
adapter.setThings(things);
adapter.notifyDataSetChanged();
})
以上的代碼確保了在計(jì)算線程中獲取新的數(shù)據(jù)列表捌显,并且發(fā)送給主線程然后調(diào)用adapter的notifyDataSetChanged茁彭,這樣做有效,但是看起來(lái)不是很好扶歪,因?yàn)槊總€(gè)新列表都需要重新繪制理肺。
為了使用DiffUtil,我們需要調(diào)用calculateDiff()方法善镰,并將DiffResult與最新的List<Thing>一起傳遞給我們的訂閱者哲嘲,一個(gè)簡(jiǎn)單的方法是使用Pair類,這是支持庫(kù)中一個(gè)簡(jiǎn)單并且強(qiáng)大的實(shí)現(xiàn)類媳禁,我們將更改訂閱以接受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()的回調(diào)需要當(dāng)前列表和新列表才能生效,我們?nèi)绾未_保我們從數(shù)據(jù)源所獲取的每一個(gè)新列表竣稽,我們也想獲取上一個(gè)事件囱怕,這也是RxJava更神秘的一個(gè)運(yùn)算符scan(),scan的初始值將是一個(gè)由空列表和空值作為DiffResult值組成的對(duì)。
現(xiàn)在我們將會(huì)調(diào)用calculateDiff()并將其列在我們的Pair中毫别,我們使用結(jié)果構(gòu)建一個(gè)新的DiffResult娃弓。
我們還有一件事要考慮,如果我們這樣離開(kāi)岛宦,第一件事將包含一個(gè)DiffResult為null的Pair台丛,所以我們必須在我們的訂閱中對(duì)null進(jìn)行檢查,但是砾肺,由于也有可能我們開(kāi)始的時(shí)候是一個(gè)空列表挽霉,所以我們可以使用skip運(yùn)算符來(lái)簡(jiǎn)單的跳過(guò)他,這將會(huì)忽略第一個(gè)事件变汪。
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和一些職能操作符在后臺(tái)線程上使用DiffUtil侠坎,還沒(méi)有必要考慮適配器保存的當(dāng)前數(shù)據(jù)的任何同步或并發(fā),我們的實(shí)力應(yīng)用程序的結(jié)果可以在下面看到裙盾。
Demo地址: