写在前面:

在上篇文章中说过对于像 RecyclerView 或者 ListView 等等此类在有限屏幕中展示大量内容的控件,复用的逻辑就是其核心的逻辑,而关于复用导致最常见的 bug 就是复用错乱。在大上周我就遇到了一个很奇怪的问题,这也是我下决心研究 RecyclerView 的原因。
RecyclerView 源码分析
而这篇文章的目的首先是讨论在 RecyclerView 复用错乱时,一些通用的解决思路,其次就是探究我遇到的那个奇怪的问题,帮助未来同样遇到的朋友们。
复用错乱的解决办法
本文的前半部分很简单的,以为关于复用错乱,RecyclerView 已经有他的前辈 ListView 替它踩了很多坑了。虽然他们的复用逻辑是有差异的,例如 ListView 只有两层缓存,但是 RecyclerView 可以理解为有四层;ListView 缓存的单位是 view,而 RecyclerView 缓存的单位是 ViewHolder。但是不管他们复用逻辑的差异如何,终归都是把那个缓存起来的 view 拿过来接着用,所以解决复用错乱的方法是一样的。
RecyclerView 复用导致错乱的原因其实就是拿出来之前的 View 来添加到新 item 上,之前 View 的状态一直保留着,所以也就错乱了。不过解决起来很简单:
首先我们以 adapter 数据的来源分为两大类:
1.当数据来源是同步的
这种情况是最简单的,你就保证当 onBindViewHolder 方法调用的时候,你的 itemview 中每个 view 的状态都有一个默认值。这是什么意思呢?
if ("<unknown>".equals(artists)) {
holder.cbMusicState.setChecked(true);
} else {
holder.cbMusicState.setChecked(false);
}
假设我们的 holder 里面有个 Checkbox 控件,当歌手名为 unknown 时,Checkbox 勾选。注意个时候你一定要加上这个 else 条件,才能保证复用这个 ViewHolder 的时候,Checkbox 的状态不出错。任何控件都一样,总结起来就是你要给每个控件的状态赋一个新的值,替换掉之前的,这样自然不会出现什么复用错乱的问题。
2.当数据的来源是异步的
这种情况也很常见,我们举个栗子,比如你的 ItemView 里面有个 ImageView,每次 onBindViewHolder 的时候,你传入一个 url,等待服务器返回的结果,然后展示在 ImageView 上。这种情况会怎样导致错乱呢?
是这样的,假设我进入了页面,开始为第一个 ImageView 请求图片,但是此刻我下划屏幕,划到了第四个 item,此时第一个 item 已经不可见了,第四个 item 复用了第一个 item 的 imageview,恰好此刻第一个 imageview 的图片结果返回了,就正好展示在了第四个 itemview 上。 这样就发生了图片的错乱。
出现这个问题的原因就是这个 ImageView 和请求的 url 没一一绑定,所以按照这个思路来解决吧:
holder.ivCameraImages.setBackground(R.drawable.place_holder);
holder.ivCameraImages.setTag(imageURL);
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == MSG_IMAGE) {
Bitmap bm = (Bitmap) msg.obj;
if (bm != null) {
if (TextUtils.equals((String) imageView.getTag(), imageURL)) {
imageView.setBackground(new BitmapDrawable(bm));
}
}
}
}
首先在没加载图片之前,给 ImageView 设置一个默认图片,然后通过 setTag 方法,将 ImageView 和 图片的 url 一一对应起来,设置的时候再判断一下,这个 imageview 的 tag 和当时请求的 url,是不是一致的,如果是一致的,再保存。
以上就是复用错乱时两种比较通用的解法,基本上可以覆盖大部分情况。
一个奇怪的问题
这个问题的现象是这样子的:
当 RecyclerView 的条目很少的时候,比如只有六个,将 RecyclerView 从上滑动到下,这个时候是正常的,onBindViewHolder 会调用,不过此时从底部上划的时候,上方的 item 从不可见到可见的这个过程中,onBindViewHolder 并没有调用,这个时候我也就没办法进行一些刷新 item 的操作了。
这个问题的原因是 onBindViewHolder 方法不调用导致的,我在 StackOverflow 上搜索了很多答案,终于找到了一个可以解决我的问题的:
recyclerview-not-recycling-views-if-the-view-count-is-small
(中文资料压根就没有,所以掌握英文搜索是多么的重要)
你可以调用
recyclerView.setItemViewCacheSize(int);
这个 api,去调整 RecyclerView 的复用逻辑和方式来解决 onBindViewHolder 没有调用的这个问题。
但是原理是怎样的呢?作为一名好奇心颇重的程序员,一步步 debug RecyclerView 的源代码,发现了导致这个问题的原因,一起来看看吧。
在上一篇文章中,我们分析了 RecyclerView 的源码,其中复用逻辑的模块,有一个非常重要的核心方法 tryBindViewHolderByDeadline,这个方法目的就是在 RecyclerView 的层层缓存结构中,取出 ViewHolder。
这里就不再次研究它了,想了解的去看之前的文章,我来描述一下对于这个场景,简化之后的逻辑:
当 RecyclerView 从底部向上滑动的时候,会先后从 mCachedViews 和 mRecyclerPool 中寻找缓存的 ViewHolder。
mCachedViews 和 mRecyclerPool 之间又有什么关系呢?
public void setViewCacheSize(int viewCount) {
mRequestedCacheMax = viewCount;
updateViewCacheSize();
}
void updateViewCacheSize() {
int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
mViewCacheMax = mRequestedCacheMax + extraCache;
// first, try the views that can be recycled
for (int i = mCachedViews.size() - 1;
i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
recycleCachedViewAt(i);
}
}
当调用 setViewCacheSize 这个方法时,相当于是给 mViewCacheMax 这个变量赋值了, for 循环调用 recycleCachedViewAt 的作用是将 mCachedViews 中缓存的 ViewHolder 放进 RecyclerPool 中。可以看到 for 循环的周期是从 mCachedViews 的最后一个对象直到 mCachedViews.size == mViewCacheMax 这个值时。
也就是可以这么理解, setViewCacheSize 这个方法其实就是为 mCachedViews 集合设置所能持有 ViewHolder 的最大数量。
当 setViewCacheSize(0)时,RecyclerView 想去复用 ViewHolder 时,只能去 RecyclerPool 中去取了,这里就有问题来了,从 RecyclerPool 中取和从 mCachedViews 中取 ViewHolder 中又有什么区别呢?
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
当从 RecyclerPool 取出 ViewHolder 时,调用了 resetInternal 这个函数的作用是清空一些记录的参数,包括之前记录 ViewHolder 状态的 mFlags。
else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
代码再往下走的时候,刚刚清空的 flag 参数这个时候就用到了,holder.isBound() 返回 flase,进入 if 判断,调用 tryBindViewHolderByDeadline 进而调用了 onBindViewHolder。
到这里这个逻辑就描述清楚了,所以设置 setViewCacheSize 来调整 mCachedViews 保存 ViewHolder 的大小,就能解决问题。
当然有些特殊的情况,某些位置就不能调用 onBindViewHolder,没关系,可以监听 RecyclerView 的滑动,当滑动停止的时候,再调用 notify 刷新下列表也是可以的。
好了本文到这里就结束了~希望对大家的学习有所帮助,也希望大家多多支持。
# recyclerview复用错乱
# recyclerview错乱问题
# recycview复用的问题
# Android中使用RecyclerView实现下拉刷新和上拉加载
# Android中RecyclerView布局代替GridView实现类似支付宝的界面
# Android中RecyclerView实现多级折叠列表效果(二)
# Android中RecyclerView实现横向滑动代码
# Android RecyclerView网格布局(支持多种分割线)详解(2)
# Android Recyclerview实现多选
# 单选
# 全选
# 反选
# 批量删除的功能
# Android RecyclerView实现下拉刷新和上拉加载
# Android RecyclerView的卡顿问题的解决方法
# Android项目实战之仿网易新闻的页面(RecyclerView )
# Android如何利用RecyclerView实现列表倒计时效果实例代码
# 复用
# 这个问题
# 第一个
# 这种情况
# 这个时候
# 有个
# 很简单
# 在上
# 清空
# 中取
# 都是
# 这是
# 我也
# 他们的
# 我就
# 都有
# 来了
# 我在
# 好了
# 你可以
相关文章:
微信网站制作公司有哪些,民生银行办理公司开户怎么在微信网页上查询进度?
合肥做个网站多少钱,合肥本地有没有比较靠谱的交友平台?
手机网站制作平台,手机靓号代理商怎么制作属于自己的手机靓号网站?
北京建设网站制作公司,北京古代建筑博物馆预约官网?
建站主机选购指南:核心配置与性价比推荐解析
如何通过智能用户系统一键生成高效建站方案?
深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?
电商平台网站制作流程,电商网站如何制作?
建站主机选哪家性价比最高?
定制建站流程步骤详解:一站式方案设计与开发指南
较简单的网站制作软件有哪些,手机版网页制作用什么软件?
制作网站怎么制作,*游戏网站怎么搭建?
php8.4新语法match怎么用_php8.4match表达式替代switch【方法】
香港服务器部署网站为何提示未备案?
广州商城建站系统开发成本与周期如何控制?
建站之星代理商如何保障技术支持与售后服务?
高防服务器租用指南:配置选择与快速部署攻略
如何高效利用200m空间完成建站?
如何在IIS中新建站点并配置端口与物理路径?
公司门户网站制作流程,华为官网怎么做?
,购物网站怎么盈利呢?
建站之星安装模板失败:服务器环境不兼容?
手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?
如何在Windows 2008云服务器安全搭建网站?
长春网站建设制作公司,长春的网络公司怎么样主要是能做网站的?
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
如何在自有机房高效搭建专业网站?
,网页ppt怎么弄成自己的ppt?
如何在万网自助建站中设置域名及备案?
网站制作公司排行榜,四大门户网站排名?
如何选择适合PHP云建站的开源框架?
打鱼网站制作软件,波克捕鱼官方号怎么注册?
网站app免费制作软件,能免费看各大网站视频的手机app?
小程序网站制作需要准备什么资料,如何制作小程序?
制作国外网站的软件,国外有哪些比较优质的网站推荐?
制作证书网站有哪些,全国城建培训中心证书查询官网?
XML的“混合内容”是什么 怎么用DTD或XSD定义
如何通过虚拟主机空间快速建站?
网站制作外包价格怎么算,招聘网站上写的“外包”是什么意思?
小自动建站系统:AI智能生成+拖拽模板,多端适配一键搭建
,石家庄四十八中学官网?
如何在Mac上搭建Golang开发环境_使用Homebrew安装和管理Go版本
如何快速上传建站程序避免常见错误?
图册素材网站设计制作软件,图册的导出方式有几种?
武清网站制作公司,天津武清个人营业执照注销查询系统网站?
linux top下的 minerd 木马清除方法
如何用腾讯建站主机快速创建免费网站?
北京制作网站的公司,北京铁路集团官方网站?
北京网站制作公司哪家好一点,北京租房网站有哪些?
常州自助建站工具推荐:低成本搭建与模板选择技巧
*请认真填写需求信息,我们会在24小时内与您取得联系。