最新项目遇到一个问题,就是RecycleView的itemview会频繁拉取图片,同一时间多次拉取同一张照片。
初探,是因为该场景notifyDataSetChanged()过于频繁,一秒钟会调用5次左右,
导致ViewHolder没有复用,也不是没有复用而是复用的并没有像理想中的样式。

notify

  1. notifyDataSetChanged
  2. mObservable.notifyChanged()
  3. mObservers.get(i).onChanged();
  4. 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;
  5. requestLayout();

回收逻辑

  1. void onLayout(boolean changed, int l, int t, int r, int b)
  2. dispatchLayout()
  3. void dispatchLayoutStep2()
  4. void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
  5. detachAndScrapAttachedViews(recycler); //先回收,在fill,也就是后去查找可以复用的holder。仅处理children holder
  6. 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

回收另外一个分支

  1. onLayout
  2. dispatchLayout
  3. dispatchLayoutStep3
  4. removeAndRecycleScrapInt //仅处理mAttachedScrap所缓存Holder
    vh.setIsRecyclable(true);
  5. recycler.quickRecycleScrapView(scrap);
  6. 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
  7. addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled)
  8. getRecycledViewPool().putRecycledView(holder); 最终放到了getRecycledViewPool中。

查找可复用Holder过程

  1. void onLayout(boolean changed, int l, int t, int r, int b)
  2. dispatchLayout()
  3. void dispatchLayoutStep2()
  4. void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
  5. fill(RecyclerView.Recycler recycler, LayoutState layoutState,
    RecyclerView.State state, boolean stopOnFocusable)
  6. layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
    LayoutState layoutState, LayoutChunkResult result)
  7. LinerLayout.next
  8. getViewForPosition
  9. 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过程

  1. tryGetViewHolderForPositionByDeadline(int position,
    boolean dryRun, long deadlineNs)
  2. tryBindViewHolderByDeadline
  3. 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多行。刚看的时候无从下手,向其他博主所说的见森林而不见树木。
我就是从notifyDataSetChangedonBindViewHolder入手,查找上下逻辑从而追踪定位问题。

小葵花妈妈课堂开课了《RecyclerView 复用解析》相关推荐

  1. ITextPDF填写模板,部分中文丢失,如“小葵花妈妈课堂”,剩余“小葵花堂”

    ITextPDF填写模板,部分中文中文,如"小葵花妈妈课堂",剩余"小葵花堂" 问题描述 问题描述 一开始以为是linux字体的问题,重新安装两次无效无效.后怀 ...

  2. 你真的了解AsyncTask吗?AsyncTask源码分析

    转载请注明出处:http://blog.csdn.net/yianemail/article/details/51611326 1,概述 Android UI是线程不安全的,如果想要在子线程很好的访问 ...

  3. 线程中task取消_Rust Async: async-task源码分析

    async-std是rust异步生态中的基础运行时库之一,核心理念是合理的性能 + 用户友好的api体验.经过几个月密集的开发,前些天已经发布1.0稳定版本.因此是时候来一次深入的底层源码分析.asy ...

  4. Android之AsyncTask源码分析(第五篇:execute方法只能执行一次的原因)

    (注意:本文基于API 28的源码分析,API 29上或其他平台的源码略有不同) 前言 当你调用AsyncTask对象的execute()方法时,突然发生崩溃--内心充满不解:java.lang.Il ...

  5. 【Android 异步操作】AsyncTask 异步任务 ( 参数简介 | 方法简介 | 使用方法 | AsyncTask 源码分析 )

    文章目录 一.AsyncTask 参数简介 二.AsyncTask 方法简介 三.AsyncTask 基本用法 四.AsyncTask 构造函数源码解析 五.AsyncTask 构造函数相关源码注释 ...

  6. Asynctask源码分析

    ​ 首先我们使用AsyncTask时,一般是: new AsyncTask(...).execute() 复制代码 我们看new AsyncTask(),它走的是: public AsyncTask( ...

  7. android asynctask源码分析,Android通过Handler与AsyncTask两种方式动态更新ListView(附源码)...

    本文实例讲述了Android通过Handler与AsyncTask两种方式动态更新ListView的方法.分享给大家供大家参考,具体如下: 有时候我们需要修改已经生成的列表,添加或者修改数据,noti ...

  8. JavaFX源码分析实战:如何设置窗体标题小图标和任务栏图标

    JavaFX实战系列 JavaFX源码分析和实战:javaFX线程结构分析 JavaFX源码分析和实战之launcher启动器:两种启动javaFX的方式及launch(args[])参数设置和获取 ...

  9. AsyncTask使用以及源码分析

    综述 在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行.在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看And ...

  10. Redis学习之intset整数集合源码分析

    1.整数集合:整数的集合,升序排序,无重复元素 2.整数集合intset是集合键的底层实现之一,当一个集合只包含整数值的元素,并且这个集合的元素数量不多时,redis会使用整数集合作为集合键的底层实现 ...

最新文章

  1. 【STM32 .NET MF开发板学习-29】摄像头蓝牙图像远程获取
  2. Compiled functions can't take variable number of arguments or use keyword-only arguments with defaul
  3. 如何用python画数据图-关于如何使用Python绘制基本数据图形模型
  4. python怎么播放本地录音_Python播放音频与录音
  5. zoj 1406 Jungle Roads
  6. 官方华为鸿蒙os2.0,华为“新平板”6月2日登场,搭载麒麟9000,首发预装鸿蒙OS!...
  7. 计算机系统-电路设计11-内存的内部电路实现(输入与输出同线)
  8. EL表达式用法---查询博客
  9. Ant for Vue - input、select组件placeholder无法显示
  10. KVM 介绍(5):libvirt 介绍 [ Libvrit for KVM/QEMU ]
  11. memcached 可视化客户端工具TreeNMS
  12. Ubuntu 命令行 安装 Operator Mono 字体
  13. mysql 自己的ip怎么查看_如何查看连接mysql的ip地址
  14. SQL 实验项目3_1-数据更新
  15. python爬虫爬取新闻标题及链接_网络爬虫百度新闻标题及链接爬取
  16. Merlin部署KMS
  17. web页面播放语音提示保存成功
  18. 简单制作个性GHOST光盘系统还原盘(图文教程)
  19. [LeetCode]-原地哈希
  20. 联想-IBM PC并购案

热门文章

  1. Bootstrap broker localhost9092 (id -1 rack null) disconnected
  2. 1.2.2-凑零钱问题(暴力递归+动态规划)
  3. Rollup 插件开发牛刀小试
  4. POJ 3580 SuperMemo
  5. excel公式不自动计算_梯形丝杠设计计算公式及三针法测量Excel表
  6. linux下操作3G模块
  7. (一)生成器详解——简单生成器
  8. shopee虾皮面试题汇总-C++后端
  9. 汉字转语音 android 软件,文字转语音助手
  10. 实时Javascript开发框架Clouda、Meteor、Firebase对比