源码分析
我们从源码入手来看看
public final void notifyItemChanged(int position, @Nullable Object payload) { this.mObservable.notifyItemRangeChanged(position, 1, payload); } 复制代码
可以看到payload是一个object,并非int。它调用了mObservable的notifyItemRangeChanged
public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { for(int i = this.mObservers.size() - 1; i >= 0; --i) { ((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeChanged(positionStart, itemCount, payload); } } 复制代码
调用了AdapterDataObserver的onItemRangeChanged,这是一个接口,它的实现是RecyclerViewDataObserver,实现的函数
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { RecyclerView.this.assertNotInLayoutOrScroll((String)null); if(RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { this.triggerUpdateProcessor(); } } 复制代码
又调用了mAdapterHelper的onItemRangeChanged
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { if(itemCount < 1) { return false; } else { this.mPendingUpdates.add(this.obtainUpdateOp(4, positionStart, itemCount, payload)); this.mExistingUpdateTypes |= 4; return this.mPendingUpdates.size() == 1; } } 复制代码
调用了obtainUpdateOp函数
public AdapterHelper.UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) { AdapterHelper.UpdateOp op = (AdapterHelper.UpdateOp)this.mUpdateOpPool.acquire(); if(op == null) { op = new AdapterHelper.UpdateOp(cmd, positionStart, itemCount, payload); } else { op.cmd = cmd; op.positionStart = positionStart; op.itemCount = itemCount; op.payload = payload; } return op; } 复制代码
可以看到作为参数赋给一个UpdateOp对象,那么哪里使用了这个对象的payload?
在AdapterHelper中查找发现有几处这样的代码
this.mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); 复制代码
这个callback也是一个接口,在RecyclerView中可以找到它的实现,其中对应的函数:
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { RecyclerView.this.viewRangeUpdate(positionStart, itemCount, payload); RecyclerView.this.mItemsChanged = true; } 复制代码
调用了viewRangeUpdate函数
void viewRangeUpdate(int positionStart, int itemCount, Object payload) { int childCount = this.mChildHelper.getUnfilteredChildCount(); int positionEnd = positionStart + itemCount; for(int i = 0; i < childCount; ++i) { View child = this.mChildHelper.getUnfilteredChildAt(i); RecyclerView.ViewHolder holder = getChildViewHolderInt(child); if(holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart && holder.mPosition < positionEnd) { holder.addFlags(2); holder.addChangePayload(payload); ((RecyclerView.LayoutParams)child.getLayoutParams()).mInsetsDirty = true; } } this.mRecycler.viewRangeUpdate(positionStart, itemCount); } 复制代码
可以看到调用了holder的addChangePayload
void addChangePayload(Object payload) { if(payload == null) { this.addFlags(1024); } else if((this.mFlags & 1024) == 0) { this.createPayloadsIfNeeded(); this.mPayloads.add(payload); } } private void createPayloadsIfNeeded() { if(this.mPayloads == null) { this.mPayloads = new ArrayList(); this.mUnmodifiedPayloads = Collections.unmodifiableList(this.mPayloads); } } List<Object> getUnmodifiedPayloads() { return (this.mFlags & 1024) == 0?(this.mPayloads != null && this.mPayloads.size() != 0?this.mUnmodifiedPayloads:FULLUPDATE_PAYLOADS):FULLUPDATE_PAYLOADS; } 复制代码
这里有两个list,mPayloads和mUnmodifiedPayloads,在getUnmodifiedPayloads中可以看到当mPayloads不为空才会返回mUnmodifiedPayloads,否则返回FULLUPDATE_PAYLOADS,即Collections.EMPTY_LIST。
在RecyclerView中搜索getUnmodifiedPayloads函数,发现其中一处应该跟我们的问题有关
boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) { return this.mItemAnimator == null || this.mItemAnimator.canReuseUpdatedViewHolder(viewHolder, viewHolder.getUnmodifiedPayloads()); } 复制代码
payloads应该对这个函数的返回值有影响,继续看mItemAnimator的对应函数。
这个mItemAnimator也是一个接口,实现类是DefaultItemAnimator,它的对应函数
public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, @NonNull List<Object> payloads) { return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); } 复制代码
可以看到如果payloads不为空,即最开始的payload不为null(因为是object,所以0还是其它都无所谓,只要不为空就行),canReuseUpdatedViewHolder则为true。
那么canReuseUpdatedViewHolder影响什么,同样在RecyclerView中搜索发现
void scrapView(View view) { RecyclerView.ViewHolder holder = RecyclerView.getChildViewHolderInt(view); if(!holder.hasAnyOfTheFlags(12) && holder.isUpdated() && !RecyclerView.this.canReuseUpdatedViewHolder(holder)) { if(this.mChangedScrap == null) { this.mChangedScrap = new ArrayList(); } holder.setScrapContainer(this, true); this.mChangedScrap.add(holder); } else { if(holder.isInvalid() && !holder.isRemoved() && !RecyclerView.this.mAdapter.hasStableIds()) { throw new IllegalArgumentException("Called scrap view with an invalid view. Invalid views cannot be reused from scrap, they should rebound from recycler pool." + RecyclerView.this.exceptionLabel()); } holder.setScrapContainer(this, false); this.mAttachedScrap.add(holder); } } 复制代码
可以看到如果有payload,holder会被放入mAttachedScrap,否则放入mChangedScrap。
mAttachedScrap和mChangedScrap
这两个就涉及到RecyclerView的缓存机制了,整个缓存机制包含多个集合,这两个集合就是其中的重要部分,这个机制就不在这篇文章里细说了。
先看看它们俩个有什么用
mChangedScrap | 与RecyclerView分离的ViewHolder列表 |
mAttachedScrap | 未与RecyclerView分离的ViewHolder列表 |
简单来说当holder有了变化就会放入mChangedScrap,这样刷新的时候会移除重新bind一下;
而holder没有改变则放入mAttachedScrap,这样刷新的时候就不需要重新bind,直接更新数据即可。
所以正是因为没有payload需要重新bind,所以会出现闪烁。而在滑动中不仅位置一直变,因为进度也在变,所以不停的进行移除bind,就会导致重影的现象。
而使用了payload后,不会移除重新bind,只更新进度条自己,就不会闪烁或重影了。
payload的大用处
最后再补充一个重要的部分!
payload的应用不仅仅是这么简单,在研究的过程中我还发现了另外一个函数
public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) { this.onBindViewHolder(holder, position); } 复制代码
很熟悉吧,这是RecyclerView的Adapter中的一个函数,我们一般使用
public abstract void onBindViewHolder(@NonNull VH var1, int var2); 复制代码
因为上面那个重载的函数不是abstract的,所以我们不容易注意到。那么这个函数有什么用?
可以看到默认处理就是调用了下面的函数,没什么特殊,但是我们可以重写它。
比如说我们刷新的时候,只想改变一个TextView的文案
如果是之前的处理,会重新执行一遍onBindViewHolder(@NonNull VH var1, int var2)
,这样不仅那个TextView,其它组件也会更新一遍数据,虽然数据没变,尤其有图片的时候需要重新load一次。
但是重写onBindViewHolder(@NonNull VH holder, int position, @NonNull List payloads)我们就可以只为TextView重新设置文案即可,如下:
@Override public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) { if(payloads.isEmpty()){ onBindViewHolder(holder, position); } else{ holder.tv.setText("change text"); } } 复制代码
而且通过对payload设置不同的值,我们可以通过判断payload分别处理不同的刷新,比如:
@Override public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) { if(payloads.isEmpty()){ onBindViewHolder(holder, position); } else{ for(Object obj : payloads){ if((int)obj == 1){ holder.tv.setText("change text"); } else if((int)obj == 2){ holder.img.setImageBitmap(newBitmap); } } } } 复制代码
所以payload再配合
onBindViewHolder(@NonNull VH holder, int position, @NonNull List payloads)使用就可以实现RecyclerView的item的局部刷新,不用再刷新整条item了。
总结
总结
payload机制作用很大,尤其是当RecyclerView中的每个Item布局和数据比较复杂,需要单独更新的时候。使用payload不仅仅解决闪烁和重影问题,也会使更新更高效,减少资源开销。