RecyclerView局部刷新机制——payload

简介: 这篇文章其实之前就完成了,一直遗忘在角落里了,今天无意翻之前的笔记发现的,大部分内容应该还是有效的。之前在使用RecyclerView的遇到过一个问题,使用notifyItemChanged刷新数据的时候会出现重影或者闪烁的现象。这个问题很容易出现,当我们的列表中有进度显示(比如下载),这时候需要不停的更新进度,就需要使用notifyItemChanged使用notifyItemChanged可以只刷新那一个item,这样就避免了像ListView那样全部刷新但是如果使用notifyItemChanged(position),在滑动的时候刷新就会出现重影或者闪烁的问题。

源码分析


我们从源码入手来看看


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不仅仅解决闪烁和重影问题,也会使更新更高效,减少资源开销。




目录
相关文章
|
存储 缓存 索引
RecyclerView 动画原理 | pre-layout,post-layout 与 scrap 缓存的关系
RecyclerView 动画原理 | pre-layout,post-layout 与 scrap 缓存的关系
89 0
|
Android开发
ViewPager源码分析(2):滑动及冲突处理
我的简书同步发布:ViewPager源码分析(2):滑动及冲突处理 转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001】 上一篇介绍了ViewPager的onMeasure和onLayout两个方法,这是自定义View最基本的两个函数。但是我们的ViewPager有个需求就是滑动,接下来我们一起去学习ViewPager在滑动方面做了哪些工作,以及ViewPager如何处理与子View之间的滑动冲突。由于ViewPager的子View有Decor View还有普通的子View,而本篇文章讲的主要是普通子View,因此,不再去刻意区
ViewPager源码分析(2):滑动及冲突处理
|
缓存
ViewPager懒加载的实现,理解setUserVisibleHint,而不只是会用就好
Viewpager默认会缓存临近操作的两个页面,也就是至少会缓存一个页面。
213 0
ViewPager懒加载的实现,理解setUserVisibleHint,而不只是会用就好
|
缓存
ViewPager中Fragment状态保存的哪些事
在使用 ViewPager 时 , 如果我们的适配器使用的是 FragmentStatePagerAdapter ,那么当我们重新滑到之前已销毁的页面时,一般情况下页面的状态依然将保持不变(比如 RecyclerView 的 滚动位置等,EditText 的 输入内容 等), 或者说 View 历史状态被还原了。 本文的主旨就是解释其 保存与还原内部的原理以及过程。
250 0
|
缓存
ViewPager的缓存机制和懒加载实现
ViewPager的缓存机制和懒加载实现
|
API
Flutter 用 Dio的 Patch请求完成数据编辑功能
本篇介绍了详情数据的获取,实体对象的部分修改来展示 Dio的 patch 请求。可以看到,Dio 提供的一系列 Restful 请求的方式基本相同,可以做到更好的封装。
303 0
Flutter 用 Dio的 Patch请求完成数据编辑功能
|
前端开发 开发者
一道面试题:ViewPager中的Framgent如何实现懒加载?
setUserVisiblity已被废弃,推荐使用 setMaxLifecycle 处理 Fragment 在 ViewPager 中的懒加载
425 0
一道面试题:ViewPager中的Framgent如何实现懒加载?
|
存储 缓存 Android开发
聊聊ViewPager2中的缓存和复用机制
聊聊ViewPager2中的缓存和复用机制
聊聊ViewPager2中的缓存和复用机制
非UI线程下页面处理:view的postInvalidate和post对消息处理的差异化
我们知道view有一系列post方法,用于在非UI线程中发出一些页面处理。view还有另外一个postInvalidate方法,同样在非UI线程中发起重绘。 同样是在非UI线程向UI线程发出消息,但是这里面有很大的区别。
218 0
|
XML 数据格式
图片加载错乱,Glide无法设置Tag解决方式
图片加载错乱,Glide无法设置Tag解决方式
357 0