One question:
Every time the data is fetched from the server, it calls adapter.notifyDataSetChanged(); refreshes it. Aren't these things of adapter. notify ItemChanged ();) blind? It's not good for performance. There's no animation yet.
How to do it?
Use DiffUtil! It claims to be able to refresh artifacts locally, so that where your item should be refreshed, it will not refresh where the data has not changed. (DiffUtil calls local refresh internally, and also supports item animation yo!)
How to use it?
- Rewrite a class: DiffUtil.Callback. Write it yourself. (Pay attention to typing Log, if you feel like you're cooking.)
public class MyDiffCallback extends DiffUtil.Callback { //Thing is adapter's data class, and you need to replace it with your own adapter data class private List<Thing> current; private List<Thing> next; public MyDiffCallback(List<Thing> current, List<Thing> next) { this.current = current; this.next = next; Log.d("data c", current.toString()); Log.d("data n", next.toString()); } /** * size of old data */ @Override public int getOldListSize() { return current.size(); } /** * size of new data */ @Override public int getNewListSize() { return next.size(); } /** * This method is customized freely. * Called when comparing data * Returning true is judged to be the same item */ @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { Thing currentItem = current.get(oldItemPosition); Thing nextItem = next.get(newItemPosition); return currentItem.getId() == nextItem.getId(); } /** *When the above method returns true, * This method will only be called by diff * Returning true proves the same */ @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { Thing currentItem = current.get(oldItemPosition); Thing nextItem = next.get(newItemPosition); return currentItem.equals(nextItem); } }
- And create it,
MyDiffCallback callback = new MyDiffCallback(adapter.things, things);
Contrast data
DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback); - Then refresh and finish.
result.dispatchUpdatesTo(adapter);
Source code analysis, please return:
When you use it, especially in the face of massive data refresh, you will feel unprecedented efficiency and conciseness, but what?
why are you so niu bi ?
read the fucking source code
The only way to understand the principle is to read the source code. When reading the source code, there is a little stress, not with a class to read directly, but to pull its point continuously in-depth.
For example, how does he compare?
Generally, I can't understand English. I will click on this method first and see that a method is called inside this method.
image.png
Click on the new Adapter List Update Callback (adapter)
Accidental Harvest
An adapter was found to be passed into the object. Looking at the source code, it wrapped the adapter in a layer, implemented the interface and called the local refresh method of the adapter.
Is it dispatch Updates To (new Adapter List Update Callback (adapter)) where a partial refresh is achieved through this package?
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); }
}
Enter the method dispatchUpdatesTo (new AdapterListUpdateCallback (adapter);
public void dispatchUpdatesTo(ListUpdateCallback updateCallback) { final BatchingListUpdateCallback batchingCallback; if (updateCallback instanceof BatchingListUpdateCallback) { //assignment batchingCallback = (BatchingListUpdateCallback) updateCallback; } else { // Conversion assignment batchingCallback = new BatchingListUpdateCallback(updateCallback); //noinspection UnusedAssignment updateCallback = batchingCallback; } ... if (endX < posOld) { //Passing in this method, we found that there are some methods such as calling local refresh in it. dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX); } ... if (endY < posNew) { //Passing in this method, we found that there are some methods such as calling local refresh in it. dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY, endY); } ... for (int i = snakeSize - 1; i >= 0; i--) { if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) { //Here too batchingCallback.onChanged(snake.x + i, 1, mCallback.getChangePayload(snake.x + i, snake.y + i)); } } //Later omit... batchingCallback.dispatchLastEvent(); }
We found that in the BatchingListUpdateCallback class, this dispatchLastEvent() method calls partial refresh, and then in the last line of the above method dispatchUpdatesTo(ListUpdateCallback updateCallback), the dispatchLastEvent() method is invoked. Both dispatchAdditions() and dispatchRemovals() methods in the code call bat. ChingCallback refresh method, accidental harvest of local refresh secrets!
Back to the point, how does he compare data efficiently?
View the full code
public void dispatchUpdatesTo(ListUpdateCallback updateCallback) { //skip 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; } //skip final List<PostponedUpdate> postponedUpdates = new ArrayList<>(); int posOld = mOldListSize; int posNew = mNewListSize; for (int snakeIndex = mSnakes.size() - 1; snakeIndex >= 0; snakeIndex--) { //What is this? It seems to be refreshed through his x y. I don't know exactly. 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) { //Entry method, one-way operation, except refresh, no data comparison dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX); } if (endY < posNew) { //Entry method, one-way operation, except refresh, no data comparison dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY, endY); } for (int i = snakeSize - 1; i >= 0; i--) { if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) { // No data comparison batchingCallback.onChanged(snake.x + i, 1, //Entry method, one-way operation, except refresh, no data comparison mCallback.getChangePayload(snake.x + i, snake.y + i)); } } posOld = snake.x; posNew = snake.y; } batchingCallback.dispatchLastEvent(); }
If you look at the above results, you will notice that you are looking for the wrong place, as if before the data comparison, and you will notice that final Snake snake = mSnakes. get (snake index); this object, the latter code is refreshed according to Snake's x.y and other attributes, you can infer that the mSnackes collection is inevitable. Keep the results of the comparison data, but look at its annotations confused.
// The Myers' snakes. At this point, we only care about their diagonal sections. // Miles'snake. At this point, we only care about their diagonal parts. (Comments on this collection are muddled) private final List<Snake> mSnakes;
Through this set, it is found that the constructor assigns to it:
DiffResult(Callback callback, List<Snake> snakes, int[] oldItemStatuses, int[] newItemStatuses, boolean detectMoves) { //Sets are assigned mSnakes = snakes; ··· // There is an add(snacke) operation in it, but the method annotation is for the case of 0, which is ignored directly. addRootSnake(); // This method calls the rewritten areContentsTheSame (old ItemPos, new ItemPos) (to compare whether item content has changed) //The state is stored in an array and taken out directly when used. findMatchingItems(); }
Where the trace constructor (DiffResult ()) is called is calculateDiff(Callback cb, boolean detectMoves)
Discovery of the CalulateDiff (Callback cb, Boolean detectMoves) method
DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback); invoked (when we created it manually, see the previous article using part)
How data are compared remains a mystery
It is assumed that the snakes set is the result of data comparison, which is used to determine whether the item is refreshed directly.
Tracking the snakes set generation process necessarily knows the contrastive process.
Next, look at the caulateDiff (callback cb, Boolean deteMoves) method
public static DiffResult calculateDiff(Callback cb, boolean detectMoves) { // Here we call the method we used to override, and now we know the usefulness of the overridden method. final int oldSize = cb.getOldListSize(); final int newSize = cb.getNewListSize(); //Result Set final List<Snake> snakes = new ArrayList<>(); ··· // All-in-one operation ··· // Getting snake is important final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd, range.newListStart, range.newListEnd, forward, backward, max); if (snake != null) { if (snake.size > 0) { //Add to Collection snakes.add(snake); } //Convert to x, y information to get snake.x += range.oldListStart; snake.y += range.newListStart; // Return data result set through transformation and preservation ···· return new DiffResult(cb, snakes, forward, backward, detectMoves); }
Discover diffPartial(cb, range.oldListStart, range.oldListEnd,range.newListStart, range.newListEnd, forward, backward, max); method, calculate snake objects and add them to the collection, but open this method and you will find that all of them are computations:
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 //This calls the rewritten method to determine if it is the same 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 //This calls the rewritten method to determine if it is the same item. && cb.areItemsTheSame(startOld + x - 1, startNew + y - 1)) { x--; y--; } } } ··· }
The source code of diffUtil here is probably understood.