一個(gè)問(wèn)題:
每次從服務(wù)器取到數(shù)據(jù)后筑累,都是調(diào)用adapter.notifyDataSetChanged();
進(jìn)行刷新。那局部刷新(adapter.notifyItemChanged();
)的這些東西不是白瞎了嗎籽前?對(duì)性能也不好江滨,還沒(méi)有動(dòng)畫(huà)津肛。
怎么辦:
用DiffUtil吧盒让!號(hào)稱(chēng)可以進(jìn)行局部刷新神器,讓你的item 該刷新的地方就刷新缭乘,數(shù)據(jù)沒(méi)有改變的地方不刷新(DiffUtil 內(nèi)部調(diào)用了的局部刷新沐序,還支持item動(dòng)畫(huà)喲!)堕绩。
怎么用:
- 重寫(xiě)一個(gè)類(lèi):DiffUtil.Callback 策幼,自己寫(xiě),(注意打Log奴紧, 如果覺(jué)得自己菜的話)
public class MyDiffCallback extends DiffUtil.Callback {
//Thing 是adapter 的數(shù)據(jù)類(lèi)特姐,要換成自己的adapter 數(shù)據(jù)類(lèi)
private List<Thing> current;
private List<Thing> next;
public MyDiffCallback(List<Thing> current, List<Thing> next) {
this.current = current;
this.next = next;
Log.d("數(shù)據(jù)c", current.toString());
Log.d("數(shù)據(jù)n", next.toString());
}
/**
* 舊數(shù)據(jù)的size
*/
@Override
public int getOldListSize() {
return current.size();
}
/**
* 新數(shù)據(jù)的size
*/
@Override
public int getNewListSize() {
return next.size();
}
/**
* 這個(gè)方法自由定制 ,
* 在對(duì)比數(shù)據(jù)的時(shí)候會(huì)被調(diào)用
* 返回 true 被判斷為同一個(gè)item
*/
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Thing currentItem = current.get(oldItemPosition);
Thing nextItem = next.get(newItemPosition);
return currentItem.getId() == nextItem.getId();
}
/**
*在上面的方法返回true 時(shí)黍氮,
* 這個(gè)方法才會(huì)被diff 調(diào)用
* 返回true 就證明內(nèi)容相同
*/
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Thing currentItem = current.get(oldItemPosition);
Thing nextItem = next.get(newItemPosition);
return currentItem.equals(nextItem);
}
}
- 并創(chuàng)建它唐含,
MyDiffCallback callback = new MyDiffCallback(adapter.things, things);
對(duì)比數(shù)據(jù)
DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback);
- 然后刷新,完事兒
result.dispatchUpdatesTo(adapter);
源碼分析,使用請(qǐng)回:
使用起來(lái)沫浆,尤其面臨大量數(shù)據(jù)刷新時(shí)捷枯,你會(huì)感到從所未有的高效和簡(jiǎn)潔但是呢?
why are you so niu bi ?
read the fucking source code
想了解原理只有閱讀源碼专执。閱讀源碼時(shí)淮捆,有一點(diǎn)講究,不是拿著一個(gè)類(lèi)直接閱讀他炊,而是揪其一點(diǎn)不斷深入。
比如他是怎么對(duì)比的:
一般我看不懂英文已艰,我會(huì)先點(diǎn)開(kāi)這個(gè)方法痊末,看見(jiàn)這個(gè)方法內(nèi)部調(diào)用了一個(gè)方法:
點(diǎn)入
new AdapterListUpdateCallback(adapter)
意外收獲
發(fā)現(xiàn)有一個(gè)adapter 傳入這個(gè)對(duì)象,看源碼它把a(bǔ)dapter 包了一層哩掺,實(shí)現(xiàn)了接口凿叠,調(diào)用了adapter局部刷新的方法。
難道dispatchUpdatesTo(new AdapterListUpdateCallback(adapter));
里面通過(guò)這個(gè)包裹實(shí)現(xiàn)了局部刷新的?
public final class AdapterListUpdateCallback implements ListUpdateCallback {
@NonNull
private final RecyclerView.Adapter mAdapter;
/**
* Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter.
*
* @param adapter The Adapter to send updates to.
*/
public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) {
mAdapter = adapter;
}
/** {@inheritDoc} */
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
/** {@inheritDoc} */
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
/** {@inheritDoc} */
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
/** {@inheritDoc} */
@Override
public void onChanged(int position, int count, Object payload) {
mAdapter.notifyItemRangeChanged(position, count, payload);
}
}
進(jìn)入方法 dispatchUpdatesTo(new AdapterListUpdateCallback(adapter));
public void dispatchUpdatesTo(ListUpdateCallback updateCallback) {
final BatchingListUpdateCallback batchingCallback;
if (updateCallback instanceof BatchingListUpdateCallback) {
//賦值
batchingCallback = (BatchingListUpdateCallback) updateCallback;
} else {
// 轉(zhuǎn)換賦值
batchingCallback = new BatchingListUpdateCallback(updateCallback);
//noinspection UnusedAssignment
updateCallback = batchingCallback;
}
...
if (endX < posOld) {
//傳入這個(gè)方法盒件,發(fā)現(xiàn)其實(shí)在里面有調(diào)用局部刷新等方法
dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX);
}
...
if (endY < posNew) {
//傳入這個(gè)方法蹬碧,發(fā)現(xiàn)其實(shí)在里面有調(diào)用局部刷新等方法
dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY,
endY);
}
...
for (int i = snakeSize - 1; i >= 0; i--) {
if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) {
//這里也是
batchingCallback.onChanged(snake.x + i, 1,
mCallback.getChangePayload(snake.x + i, snake.y + i));
}
}
后面省略.....
batchingCallback.dispatchLastEvent();
}
我們發(fā)現(xiàn)BatchingListUpdateCallback
類(lèi)中這個(gè)dispatchLastEvent()
方法中調(diào)用局部刷新,然后在 上面這個(gè)方法dispatchUpdatesTo(ListUpdateCallback updateCallback)
最后一行調(diào)用了dispatchLastEvent()
方法,代碼中dispatchAdditions()
和dispatchRemovals()
方法中均有調(diào)用batchingCallback 的刷新方法炒刁,意外收獲局部刷新的秘密恩沽!
回到正題,他到底是怎樣高效對(duì)比數(shù)據(jù)的呢翔始?
查看完整代碼
public void dispatchUpdatesTo(ListUpdateCallback updateCallback) {
//跳過(guò)
final BatchingListUpdateCallback batchingCallback;
if (updateCallback instanceof BatchingListUpdateCallback) {
batchingCallback = (BatchingListUpdateCallback) updateCallback;
} else {
batchingCallback = new BatchingListUpdateCallback(updateCallback);
// replace updateCallback with a batching callback and override references to
// updateCallback so that we don't call it directly by mistake
//noinspection UnusedAssignment
updateCallback = batchingCallback;
}
//跳過(guò)
final List<PostponedUpdate> postponedUpdates = new ArrayList<>();
int posOld = mOldListSize;
int posNew = mNewListSize;
for (int snakeIndex = mSnakes.size() - 1; snakeIndex >= 0; snakeIndex--) {
//這是啥罗心?好像是通過(guò)他的 x y 進(jìn)行刷新的,具體不曉得了
final Snake snake = mSnakes.get(snakeIndex);
final int snakeSize = snake.size;
final int endX = snake.x + snakeSize;
final int endY = snake.y + snakeSize;
if (endX < posOld) {
//進(jìn)入方法城瞎,一通操作渤闷,除了刷新,沒(méi)有數(shù)據(jù)對(duì)比
dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX);
}
if (endY < posNew) {
//進(jìn)入方法脖镀,一通操作飒箭,除了刷新,沒(méi)有數(shù)據(jù)對(duì)比
dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY,
endY);
}
for (int i = snakeSize - 1; i >= 0; i--) {
if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) {
// 沒(méi)有數(shù)據(jù)對(duì)比
batchingCallback.onChanged(snake.x + i, 1,
//進(jìn)入方法蜒灰,一通操作弦蹂,除了刷新,沒(méi)有數(shù)據(jù)對(duì)比
mCallback.getChangePayload(snake.x + i, snake.y + i));
}
}
posOld = snake.x;
posNew = snake.y;
}
batchingCallback.dispatchLastEvent();
}
看一遍你會(huì)得到上面一樣結(jié)果卷员,這時(shí)候會(huì)注意到自己找的地方錯(cuò)了盈匾,好像是數(shù)據(jù)對(duì)比在這之前,而且會(huì)注意到final Snake snake = mSnakes.get(snakeIndex);
這個(gè)對(duì)象毕骡,后面的代碼是依照Snake 的x.y 等屬性進(jìn)行刷新削饵,可以推斷出mSnackes集合必然保存著對(duì)比數(shù)據(jù)后產(chǎn)生的結(jié)果,但是看它的注釋一臉懵逼未巫。
// The Myers' snakes. At this point, we only care about their diagonal sections.
// 邁爾斯的蛇窿撬。在這一點(diǎn)上,我們只關(guān)心它們的對(duì)角線部分叙凡。(對(duì)于這個(gè)集合的注釋一臉懵逼)
private final List<Snake> mSnakes;
通過(guò)這個(gè)集合劈伴,發(fā)現(xiàn)構(gòu)造函數(shù)中對(duì)它進(jìn)行的賦值:
DiffResult(Callback callback, List<Snake> snakes, int[] oldItemStatuses,
int[] newItemStatuses, boolean detectMoves) {
//集合被賦值
mSnakes = snakes;
···
// 里面有add(snacke) 操作,但是看方法注釋是針對(duì)于0 的情況握爷,直接忽略跛璧。
addRootSnake();
// 這方法里面 調(diào)用了重寫(xiě)的areContentsTheSame(oldItemPos, newItemPos)(對(duì)比item內(nèi)容是否有改變)
//,并把狀態(tài)存入數(shù)組新啼,用的時(shí)候直接取出追城。
findMatchingItems();
}
追蹤構(gòu)造函數(shù)(DiffResult())被調(diào)用之處是calculateDiff(Callback cb, boolean detectMoves)
發(fā)現(xiàn)calculateDiff(Callback cb, boolean detectMoves)
方法
被 DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback);
調(diào)用(我們創(chuàng)建的時(shí)候,手動(dòng)調(diào)的燥撞,請(qǐng)看前面使用部分文章)
數(shù)據(jù)如何對(duì)比依然是謎
推測(cè)知道snakes 集合是數(shù)據(jù)對(duì)比后的結(jié)果座柱,它用來(lái)判斷item是否直接刷新
追蹤snakes集合產(chǎn)生過(guò)程必然就知道了對(duì)比過(guò)程
接著看caulateDiff(callback cb ,boolean deteMoves)
方法
public static DiffResult calculateDiff(Callback cb, boolean detectMoves) {
// 這里調(diào)用了我們使用時(shí)重寫(xiě)的方法迷帜,現(xiàn)在知道,重寫(xiě)的方法的用處了
final int oldSize = cb.getOldListSize();
final int newSize = cb.getNewListSize();
//結(jié)果集
final List<Snake> snakes = new ArrayList<>();
···
// 一通操作
···
// 得到snake 色洞,重要
final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd,
range.newListStart, range.newListEnd, forward, backward, max);
if (snake != null) {
if (snake.size > 0) {
//添加到集合
snakes.add(snake);
}
//轉(zhuǎn)成 x ,y 信息得到
snake.x += range.oldListStart;
snake.y += range.newListStart;
// 一通轉(zhuǎn)化 和保存戏锹,返回?cái)?shù)據(jù)結(jié)果集
····
return new DiffResult(cb, snakes, forward, backward, detectMoves);
}
發(fā)現(xiàn)diffPartial(cb, range.oldListStart, range.oldListEnd,range.newListStart, range.newListEnd, forward, backward, max);
方法,計(jì)算得snake 對(duì)象然后添加到集合中火诸, 但是打開(kāi)這個(gè)方法里面你會(huì)發(fā)現(xiàn)里面全是計(jì)算:
private static Snake diffPartial(Callback cb, int startOld, int endOld,
int startNew, int endNew, int[] forward, int[] backward, int kOffset) {
···
for (int d = 0; d <= dLimit; d++) {
for (int k = -d; k <= d; k += 2) {
···
while (x < oldSize && y < newSize
//這里調(diào)用了重寫(xiě)的方法耶锦针,判斷是否是同一個(gè)item
&& cb.areItemsTheSame(startOld + x, startNew + y)) {
x++;
y++;
}}}
for (int k = -d; k <= d; k += 2) {
for (int k = -d; k <= d; k += 2) {
···
while (x > 0 && y > 0
//這里調(diào)用了重寫(xiě)的方法耶,判斷是否是同一個(gè)item
&& cb.areItemsTheSame(startOld + x - 1, startNew + y - 1)) {
x--;
y--;
}
}
}
···
}
不必?fù)?dān)心惭蹂,這里涉及一個(gè)算法就是Myers diff 算法(我打算單獨(dú)分析)伞插,snakes集合目前的話可以理解為保存的對(duì)比結(jié)果就行了。
到這里diffUtil 的源碼才算大概了解了盾碗。