小葵花妈妈课堂开课了《RecyclerView 复用解析》
最新项目遇到一个问题,就是RecycleView的itemview会频繁拉取图片,同一时间多次拉取同一张照片。
初探,是因为该场景notifyDataSetChanged()过于频繁,一秒钟会调用5次左右,
导致ViewHolder没有复用,也不是没有复用而是复用的并没有像理想中的样式。
notify
- notifyDataSetChanged
- mObservable.notifyChanged()
- mObservers.get(i).onChanged();
- setDataSetChangedAfterLayout();
4.1.1 markKnownViewsInvalid
//1 Mark all known views as invalid,仅标记可见的布局为ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
//2 mAdapter.hasStableIds() true -> mCachedViews 标记 ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
//3 mAdapter.hasStableIds() false -> addViewHolderToRecycledViewPool ->getRecycledViewPool().putRecycledView(holder); AND mCachedViews.clear();
// we cannot re-use cached views in this case. Recycle them all
4.1.2 markItemDecorInsetsDirty
//child.getLayoutParams()).mInsetsDirty = true;
//mCachedViews layoutParams.mInsetsDirty = true; - requestLayout();
回收逻辑
- void onLayout(boolean changed, int l, int t, int r, int b)
- dispatchLayout()
- void dispatchLayoutStep2()
- void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
- detachAndScrapAttachedViews(recycler); //先回收,在fill,也就是后去查找可以复用的holder。仅处理children holder
- scrapOrRecycleView(recycler, i, v);
6.1 viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds() -->
6.1.1 true recycler.recycleViewHolderInternal(viewHolder);
addViewHolderToRecycledViewPool(holder, true); //最终放入getRecycledViewPool()
6.1.2 false scrapView(View view)
holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder) -->
6.1.2.1 true mAttachedScrap.add(holder);
6.1.2.2 false mChangedScrap.add(holder);
总结:如果Holder无效最终放入getRecycledViewPool,否则mAttachedScrap
回收另外一个分支
- onLayout
- dispatchLayout
- dispatchLayoutStep3
- removeAndRecycleScrapInt //仅处理mAttachedScrap所缓存Holder
vh.setIsRecyclable(true); - recycler.quickRecycleScrapView(scrap);
- recycleViewHolderInternal(ViewHolder holder)
forceRecycle || holder.isRecyclable()
成立
(mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN))
不成立,包含ViewHolder.FLAG_INVALID addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled)
getRecycledViewPool().putRecycledView(holder);
最终放到了getRecycledViewPool中。
查找可复用Holder过程
- void onLayout(boolean changed, int l, int t, int r, int b)
- dispatchLayout()
- void dispatchLayoutStep2()
- void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
- fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) - layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) - LinerLayout.next
- getViewForPosition
- tryGetViewHolderForPositionByDeadline
9.1 isPreLayout() --> getChangedScrapViewForPosition(int position) //如果是pre-layout,会从mChangedScrap获取复用
9.2 getScrapOrHiddenOrCachedHolderForPosition(position, dryRun)
9.2.1 mAttachedScrap 首先从该处获取,
9.2.2 mChildHelper.findHiddenNonRemovedView(position) 拿到隐藏的ViewHolder
9.2.3 mCachedViews //Search in our first-level recycled view cache. 最后从一级缓存获取
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapOrCachedViewForId
// 通过原厂注释可以知道,has stable才可以使用mCachedViews缓存。在回收处也可以看出。
9.2.4 mAdapter.hasStableIds() --> getScrapOrCachedViewForId() //仍然需要hasStableIds
// 首先mAttachedScrap
// 之后mCachedViews
9.3 mViewCacheExtension //外部扩展
9.4 getRecycledViewPool().getRecycledView(type) //此处查到holder被resetInternal,即需要重新bind
9.5 mAdapter.createViewHolder(RecyclerView.this, type); //最后,create
bind过程
- tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) - tryBindViewHolderByDeadline
- mAdapter.bindViewHolder(holder, offsetPosition);
至此RecyclerView的复用机制已经差不多了。至此也看到了我的问题也出现了答案的线索。
因为是notifyDataSetChanged
出发的刷新,会将所有ViewHolder标记为FLAG_INVALID。
在回收ViewHolder时,会放入getRecycledViewPool中。
RecycledViewPool官方注释
RecycledViewPool lets you share Views between multiple RecyclerViews.
If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
RecyclerView automatically creates a pool for itself if you don’t provide one.
作用为多个RecyclerViews之间复用ViewHolder。
可以通过setRecycledViewPool进行设置。
SparseArray mScrap = new SparseArray<>(); 用来存储不同类型的ViewHolder
static class ScrapData {ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();int mMaxScrap = DEFAULT_MAX_SCRAP;long mCreateRunningAverageNs = 0;long mBindRunningAverageNs = 0;
}
默认mMaxScrap大小为5个
private static final int DEFAULT_MAX_SCRAP = 5;
也就是做多存储5个。我的项目正好有6个Item显示,就导致只能存储 1,2,3,4,5 而0没有进行存储
onBind的时候就会出现0 - 绑定 1,1 绑定2… 的问题。
解决办法为通过
new一个RecyclerView.RecycledViewPool, 并设置recycledViewPool.setMaxRecycledViews(0, 6);
第一个参数viewType,我这里仅有一种type,所以就写0
最终通过mRecycleList.setRecycledViewPool(recycledViewPool);
设置RecycledViewPool。
至此大工告成。
RecyclerView源码有1w多行。刚看的时候无从下手,向其他博主所说的见森林而不见树木。
我就是从notifyDataSetChanged
和 onBindViewHolder
入手,查找上下逻辑从而追踪定位问题。
小葵花妈妈课堂开课了《RecyclerView 复用解析》相关推荐
- ITextPDF填写模板,部分中文丢失,如“小葵花妈妈课堂”,剩余“小葵花堂”
ITextPDF填写模板,部分中文中文,如"小葵花妈妈课堂",剩余"小葵花堂" 问题描述 问题描述 一开始以为是linux字体的问题,重新安装两次无效无效.后怀 ...
- 你真的了解AsyncTask吗?AsyncTask源码分析
转载请注明出处:http://blog.csdn.net/yianemail/article/details/51611326 1,概述 Android UI是线程不安全的,如果想要在子线程很好的访问 ...
- 线程中task取消_Rust Async: async-task源码分析
async-std是rust异步生态中的基础运行时库之一,核心理念是合理的性能 + 用户友好的api体验.经过几个月密集的开发,前些天已经发布1.0稳定版本.因此是时候来一次深入的底层源码分析.asy ...
- Android之AsyncTask源码分析(第五篇:execute方法只能执行一次的原因)
(注意:本文基于API 28的源码分析,API 29上或其他平台的源码略有不同) 前言 当你调用AsyncTask对象的execute()方法时,突然发生崩溃--内心充满不解:java.lang.Il ...
- 【Android 异步操作】AsyncTask 异步任务 ( 参数简介 | 方法简介 | 使用方法 | AsyncTask 源码分析 )
文章目录 一.AsyncTask 参数简介 二.AsyncTask 方法简介 三.AsyncTask 基本用法 四.AsyncTask 构造函数源码解析 五.AsyncTask 构造函数相关源码注释 ...
- Asynctask源码分析
首先我们使用AsyncTask时,一般是: new AsyncTask(...).execute() 复制代码 我们看new AsyncTask(),它走的是: public AsyncTask( ...
- android asynctask源码分析,Android通过Handler与AsyncTask两种方式动态更新ListView(附源码)...
本文实例讲述了Android通过Handler与AsyncTask两种方式动态更新ListView的方法.分享给大家供大家参考,具体如下: 有时候我们需要修改已经生成的列表,添加或者修改数据,noti ...
- JavaFX源码分析实战:如何设置窗体标题小图标和任务栏图标
JavaFX实战系列 JavaFX源码分析和实战:javaFX线程结构分析 JavaFX源码分析和实战之launcher启动器:两种启动javaFX的方式及launch(args[])参数设置和获取 ...
- AsyncTask使用以及源码分析
综述 在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行.在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看And ...
- Redis学习之intset整数集合源码分析
1.整数集合:整数的集合,升序排序,无重复元素 2.整数集合intset是集合键的底层实现之一,当一个集合只包含整数值的元素,并且这个集合的元素数量不多时,redis会使用整数集合作为集合键的底层实现 ...
最新文章
- 【STM32 .NET MF开发板学习-29】摄像头蓝牙图像远程获取
- Compiled functions can't take variable number of arguments or use keyword-only arguments with defaul
- 如何用python画数据图-关于如何使用Python绘制基本数据图形模型
- python怎么播放本地录音_Python播放音频与录音
- zoj 1406 Jungle Roads
- 官方华为鸿蒙os2.0,华为“新平板”6月2日登场,搭载麒麟9000,首发预装鸿蒙OS!...
- 计算机系统-电路设计11-内存的内部电路实现(输入与输出同线)
- EL表达式用法---查询博客
- Ant for Vue - input、select组件placeholder无法显示
- KVM 介绍(5):libvirt 介绍 [ Libvrit for KVM/QEMU ]
- memcached 可视化客户端工具TreeNMS
- Ubuntu 命令行 安装 Operator Mono 字体
- mysql 自己的ip怎么查看_如何查看连接mysql的ip地址
- SQL 实验项目3_1-数据更新
- python爬虫爬取新闻标题及链接_网络爬虫百度新闻标题及链接爬取
- Merlin部署KMS
- web页面播放语音提示保存成功
- 简单制作个性GHOST光盘系统还原盘(图文教程)
- [LeetCode]-原地哈希
- 联想-IBM PC并购案
热门文章
- Bootstrap broker localhost9092 (id -1 rack null) disconnected
- 1.2.2-凑零钱问题(暴力递归+动态规划)
- Rollup 插件开发牛刀小试
- POJ 3580	SuperMemo
- excel公式不自动计算_梯形丝杠设计计算公式及三针法测量Excel表
- linux下操作3G模块
- (一)生成器详解——简单生成器
- shopee虾皮面试题汇总-C++后端
- 汉字转语音 android 软件,文字转语音助手
- 实时Javascript开发框架Clouda、Meteor、Firebase对比