RecyclerView了解—缓存与优化

欢迎大家入坑.
大家好,我是冰雪情缘,已经在 Android TV开发爬坑多年,也是一名TV开发开源爱好者.

Android TV 开源社区 https://gitee.com/kumei/Android_tv_libs
Android TV 文章专题 https://www.jianshu.com/c/3f0ab61a1322
Android TV QQ群1:522186932 QQ群2:468357191

写在前面

不想看原理的小伙伴,可以直接去看优化相关的内容

想必作为列表的 RecyclerView,大家应该都见识过了.

下面给出几个 手机,TV电视 上 常见 的 RecyclerView 使用的例子.

谷歌Android-TV的界面
在这里插入图片描述
小米电视的界面
在这里插入图片描述 在这里插入图片描述
手机上某XX应用的界面
在这里插入图片描述 在这里插入图片描述


优化

为什么要去了解RecyclerView的优化?
说白了,就是为了得到更好的用户体验;
大家肯定都不想用户在使用 应用的时候,又或者拿给老板体验的时候;
导致使用的人直接气的砸掉机器… …并且抛出一句“搞的这么久?就这?什么辣鸡东西!!!”

优化点都在哪里?
第1点: 超级无敌快的加载速度,页面一瞬间显示出来!快过他!!让闪电侠都无地自容,再也不敢说他是世界最快的男人。
在这里插入图片描述
第2点:使用过程中无明显的卡顿,如丝滑般流畅… …

优化的侧重点是什么??
在这里插入图片描述
侧重点,主要是 减少 createViewHolder(创建), bindViewHolder(绑定) 的 耗时调用次数
从哪里得到需要减少 耗时 与 调用次数,我们可以简单的了解下一些知识.

优化的一些基础知识了解
在这里插入图片描述
绘制过程主要是由
CPU 来进行 Measure、Layout、Record、Execute的数据计算工作,
GPU 负责 栅格化、渲染。
所谓的栅格化就是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作;

Why 60 fps?
每秒 60 帧 和 16ms

Android 系统每隔 16ms 发出一次 VSYNC 信号,触发 UI 的渲染任务.
为了能到达到丝滑般流畅,这就意味,我们 RecyclerView 的操作应该保持在 60 fps/s,也就是 16ms

在这里插入图片描述
CPU和GPU的处理时间因为各种原因都大于一个VSync的间隔(16.6ms),导致了卡顿。

优化的分析工具了解
工欲善其事必先利其器
我们优化RecyclerView之前,肯定需要先找到相关原因,这里就需要借助一些工具.
Android Studio 查看布局层级(Layout Inspector)
Show GPU Overdraw(检测过渡绘制(Overdraw)的工具)
Profile GPU Rendering 检查界面的渲染性能。该工具以滚动柱状图呈现渲染界面每帧所花费的时间(16ms的速度作为对比基准)
谷歌官方资料

Systrace 性能分析工具
Tracer for OpenGL ES
Graphics API Debugger

gfxinfo 命令(dumpsys gfxinfo packageName),获取 渲染相关的内存和 View Hierarchy 信息
dumpsys gfxinfo packageName framestats ,gfxinfo命令新增 framestats参数(拿到最近120帧每个绘制阶段的耗时信息)
SurfaceFlinger(dumpsys SurfaceFlinger),查看 Graphic Buffer 占用的内存
Choreographer(帧率)


理解缓存

了解缓存对于优化的好处是什么?为什么要去了解?

为何要了解ReycclerView的缓存机制,
第一,能更合理的使用缓存,保证应用的流畅性,低耗能;
第二,优化的能更到位;
第三,基础更扎实,后续 提升技术能力的基石.

当你满心欢喜 写了一个类似上图的 几个 界面,但是手势或遥控器 操作的时候,不是卡顿,就是内存,CPU爆炸,是不是一脸蒙蔽,然后又无从下手,有那么几个函数,又不知道如何使用,性能优化也更无从下手,那么我们进入正题吧,先去了解缓存,再来根据缓存的知识点,进行优化的知识了解

简单了解RecyclerView的流程

我们知道 RecyclerView 的优化 最重要是 减少 onCreateViewHolder(缩减createVH), onBindViewHolder(缩减bindVH)耗时(时间)调用次数,下面我们将围绕着这两个东西来讲解一些的 优化 事宜与相关的 缓存 知识.

先看看 RecyclerView 整个流程的简单介绍的示意图:
在这里插入图片描述
RecyclerView 包含了很多东西,比如 ItemDecoration,ItemAnimator,LayoutManger,Recycler,ViewHolder,Adapter… …
这里我们主要讲解的是 Recycler 这个东西,它是什么?
Recycler 主要是负责 ViewHolder 的创建复用(缓存的管理)

可以看看一张ReyclerVie的类图,大概了解下他们之间的关系:
在这里插入图片描述
先简单的看看几个相关的函数:

RecyclerView相关函数 Recycler 相关函数 含义
setItemViewCacheSize setViewCacheSize 设置 cacheView缓存大小,默认2个
setRecycledViewPool 设置缓存池(RecycledViewPool),每种类型默认5个
setViewCacheExtension 设置自定义缓存
detachAndScrapAttachedViews
setRecyclerListener 设置缓存回调
getViewForPosition 获取ViewHolder,从缓存中获取,没有则创建
bindViewToPosition 绑定 ViewHolder,会调用 Adapter.bindViewHolder -> onBindViewHolder

有没有思考过,setItemViewCacheSize 什么时候设置,设置多少个,过程是如何的?
包括 setRecycledViewPool 也是,对于 createView, bindView 他们又有什么影响?共享 RecycledViewPool 改如何去弄?
如何加快我们的界面加载速度以及复用?
setViewCacheExtension 什么时候使用?
notify…刷新界面为何还是要重新创建或者绑定,到底这么回事?


获取ViewHolder的流程

先简单的介绍下获取ViewHolder的流程
onLayoutChildre 会调用 detachAndScrapAttachedViews 分离 ViewHolder 存储到相应的缓存(Recycler)中去

这里拿出了 LinearLayoutManger 布局的流程(简单的过程):
fill ->

// layoutChunk 代码
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
    
    
	View view = layoutState.next(recycler);
}

// LayoutState next 代码
View next(RecyclerView.Recycler recycler) {
    
    
    // getViewForPosition 这里就是获取 ViewHolder 的过程
    // Recycler 主要是负责 ViewHolder 的 创建 与 复用(缓存的管理)
	final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

那我们先来看看getViewForPosition获取ViewHolder的流程吧:
在这里插入图片描述
getViewForPosition 这个函数就是从 Recycler 取出 ViewHolder 交给 Adapter.
getViewForPosition 做了哪些事情?按照上面流程我们大概的了解到,就是将 Recycler 缓存中对应的 ViewHolder取出来,没有则重新调用 createVH 创建ViewHolder.

Recylcer 缓存包含了(ChangedScrapAttachedScrapCachedViewsViewCacheExtensionRecyclerPool),后面会详解去了解缓存相关的知识,我们先来看看代码的过程

// 按照流程图一步步看下去,我们一共分成了 7 步
ViewHolder holder = null;
// 第 1 步: 根据 position 获取 changedScrap(被更新的ViewHolder)
if (mState.isPreLayout()) {
    
    
	holder = getChangedScrapViewForPosition(position);
	fromScrapOrHiddenOrCache = holder != null;
}

// 第 2 步:根据 position 获取 AttachedScrap(还在屏幕中的ViewHolder),CachedViews(缓存离开屏幕的viewHolder)
if (holder == null) {
    
    
	holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}

// 第 3 步:根据 ItemId 获取 AttachedScrap,CachedViews
if (holder == null) {
    
    
	final int type = mAdapter.getItemViewType(offsetPosition);
	// 判断是否开启了 StableIds,如果要开启,需要调用 Adapter.setHasStableIds(true)
	if (mAdapter.hasStableIds()) {
    
    
		holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
	}
}

// 第 4 步:根据 position, type 从 自定义缓存 获取 ViewHolder
if (holder == null && mViewCacheExtension != null) {
    
    
	final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
	if (view != null) {
    
    
    	holder = getChildViewHolder(view);
    }
}

// 第 5 步:根据 type 从 RecycledViewPool 获取对应的 ViewHolder
if (holder == null) {
    
    
	holder = getRecycledViewPool().getRecycledView(type);
}

// 第 6 步:创建 ViewHolder
if (holder == null) {
    
    
	holder = mAdapter.createViewHolder(RecyclerView.this, type);
}

// 第 7 步:判断是否需要 bindViewHolder
if (mState.isPreLayout() && holder.isBound()) {
    
    
	// do not update unless we absolutely have to.
	holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
    
    
	// 判断 是否bind过 或 是否已更新 或 是否失效
	// tryBindViewHolderByDeadline
	mAdapter.bindViewHolder(holder, offsetPosition);
}

上面已经大概的讲解了 getViewForPosition 从 Recycler 获取 ViewHolder 的过程,

这里我们大概了解下 Recycler 的几个缓存变量:

public final class Recycler {
    
    
	private ArrayList<ViewHolder> mChangedScrap = null; 
	final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();      
	final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); 
	private ViewCacheExtension mViewCacheExtension;
	private RecycledViewPool mRecyclerPool;
} 

这几个缓存变量有什么区别?
这是优先级最高的缓存,RecyclerView在获取ViewHolder时,优先会到这 mAttachedScrap 与 mChangedScrap 两个缓存来找。其次才是 mCachedViews,最后才是RecyclerViewPool。

列出一个表格说明对应的意思(需要注意create,bind,优化相关):

缓存级别 createVH bindVH 变量 含义
一级缓存(Scrap View) mAttachedScrap mAttachedScrap存储的是当前还在屏幕中的ViewHolder。匹配机制按照position和id进行匹配
一级缓存(Scrap View) mChangedScrap mChangedScrap存储的是数据被更新的ViewHolder,比如说调用了Adapter的 notifyXXX 方法
二级缓存(Cache View) mCachedViews 默认大小为2,缓存离开屏幕的viewHolder. 解决两点: 1. 频繁进入/离开屏幕的ViewHolder导致的内存抖动的问题;2.还有用于保存Prefetch的ViewHoder.
三级缓存(可选可配置) ViewCacheExtension 自定义缓存,通常用不到,getViewForPositionAndType 来实现自己的缓存 使用场景:位置固定 内容不变 数量有限
四级缓存(缓存池) RecyclerViewPool 根据ViewType来缓存ViewHolder,每个ViewType的数组大小默认为5,可以动态的改变 缓存的ViewHolder需要重新绑定(bindView). 也可以 RecyclerView之间共享ViewHolder的缓存池Pool.

其实再 bindView之前,有一个判断 !holder.isBound() || holder.needsUpdate() || holder.isInvalid(),这几个是什么意思呢?

判断是否要重新绑定 ViewHolder,holder.isBound() || holder.needsUpdate() || holder.isInvalid(),下列看看全部相关意义。

ViewHolder的 isInvalidisRemovedisBoundisTmpDetachedisScrapisUpdated 这几个方法:

方法名 对应的Flag 含义或者状态设置的时机
isInvalid FLAG_INVALID 表示当前ViewHolder是否已经失效。通常来说,在3种情况下会出现这种情况:1.调用了Adapter的notifyDataSetChanged方法; 2. 手动调用RecyclerView的invalidateItemDecorations方法; 3. 调用RecyclerView的setAdapter方法或者swapAdapter方法。
isRemoved FLAG_REMOVED 表示当前的ViewHolder是否被移除。通常来说,数据源被移除了部分数据,然后调用Adapter的notifyItemRemoved方法。
isBound FLAG_BOUND 表示当前ViewHolder是否已经调用了onBindViewHolder。
isTmpDetached FLAG_TMP_DETACHED 表示当前的ItemView是否从RecyclerView(即父View)detach掉。通常来说有两种情况下会出现这种情况:1.手动了RecyclerView的detachView相关方法;2. 在从mHideViews里面获取ViewHolder,会先detach掉这个ViewHolder关联的ItemView
isScrap 无Flag来表示该状态,用mScrapContainer是否为null来判断 表示是否在mAttachedScrap或者mChangedScrap数组里面,进而表示当前ViewHolder是否被废弃。
isUpdated FLAG_UPDATE 表示当前ViewHolder是否已经更新。通常来说,在3种情况下会出现情况:1.isInvalid方法存在的三种情况;2.调用了Adapter的onBindViewHolder方法;3. 调用了Adapter的notifyItemChanged方法

回收相关API

刚才在 onLayoutChildre 的 detachAndScrapAttachedViews 这个函数很关键,分离了 屏幕上 ViewHolder 存储到对应的缓存上。这里再普及下其它相关的函数:

方法名 含义
detachAndScrapAttachedViews(recycler) detach轻量回收所有View
detachAndScrapView(view, recycler) detach轻量回收指定View
detachView(view); 超级轻量回收一个View,马上就要添加回来
attachView(view); 将上个方法detach的View attach回来
recycler.recycleView(viewCache.valueAt(i)); detachView 后 没有attachView的话 就要真的回收掉他们

recycle真的回收一个View ,该View再次回来需要执行onBindViewHolder方法
removeAndRecycleView(View child, Recycler recycler)
removeAndRecycleAllViews(Recycler recycler);

detach 和recycle的时机
一个View只是暂时被清除掉,稍后立刻就要用到,使用detach。它会被缓存进scrapCache的区域。
一个View 不再显示在屏幕上,需要被清除掉,并且下次再显示它的时机目前未知 ,使用remove。它会被以viewType分组,缓存进RecyclerViewPool里。

了解detach/attach View
addView和removeView方法,操作容器内的子视图数组,触发视图重绘制,触发子视图attach和detached窗体回调。
addViewInLayout和removeViewInLayou方法,与上面一样,只是不会重绘视图
attachViewToParent和detachViewFromParent方法,只会操作容器内的子视图数组

注意: 一个View只被detach,没有被recycle的话,不会放进RecyclerViewPool里,会一直存在recycler的scrap 中。网上有人的Demo就是如此,因此View也没有被复用,有多少ItemCount,就会new出多少个ViewHolder。

题外话:想了解的 自定义 Layoutmanger,可以去了解下 onLayoutChildren
进行自定义的布局之前:

  1. 调用detachAndScrapAttachedViews方法把屏幕中的Items都分离出来,内部调整好位置和数据后,
    detachAndScrapAttachedViews(recycler)这个方法就是将屏幕上可见的view缓存在scrap里 。
    先把所有的View先从RecyclerView中detach掉,然后标记为"Scrap"状态,表示这些View处于可被重用状态(非显示中)。
    实际就是把View放到了Recycler中的一个集合中。
    detachAndScrapAttachedViews()会根据情况,将原来的Item View放入Scrap HeapRecycle Pool,从而在复用时提升效率。
  2. 调用 Recycler的getViewForPosition(int position) 方法来获取,通过addView方法来添加.
  3. 获取到Item并重新添加了之后,需要对它进行测量,这时候可以调用measureChild或measureChildWithMargins方法,
  4. 根据需求来决定使用 layoutDecorated 还是 layoutDecoratedWithMargins 方法;

在自定义ViewGroup中,layout完就可以运行看效果了,但在LayoutManager还有一件非常重要的事情,就是回收了,我们在layout之后,还要把一些不再需要的Items回收,以保证滑动的流畅度;
自定义LayoutManger,可以了解这篇文章


刷新界面-缓存的处理

notifyItemChanged对缓存的影响

同样只更新pos=1的数据,使用 notifyItemChanged(position)notifyDataSetChanged() 有什么区别?

notifyItemChanged(position)
在这里插入图片描述
notifyDataSetChanged()
在这里插入图片描述

名称 区别
notifyItemChanged(position),notifyItemChanged(int position, Object payload) 等 被更新的的item放入了ChangeScrp,不需要createVH,但是需要bindVH
notifyDataSetChanged() 0~4放入了RecyclerPool,不需要createVH,但是全部需要 bindVH

滚动-缓存的处理

往上滑动的缓存过程
在这里插入图片描述


setAdapter发生了什么

public void setAdapter(@Nullable Adapter adapter) {
    
    
  setLayoutFrozen(false);
  setAdapterInternal(adapter, false, true);
  processDataSetCompletelyChanged(false);
  requestLayout();
}
setAdpater->processDataSetCompletelyChanged -> markKnownViewsInvalid

判断 if (mAdapter == null || !mAdapter.hasStableIds()) {
    
     
调用  recycleAndClearCachedViews();
清空了 CacheView(一级缓存),添加到 RecyclerPool缓存池

 void markKnownViewsInvalid() {
    
    
            if (mAdapter != null && mAdapter.hasStableIds()) {
    
    
                final int cachedCount = mCachedViews.size();
                for (int i = 0; i < cachedCount; i++) {
    
    
                    final ViewHolder holder = mCachedViews.get(i);
                    if (holder != null) {
    
    
                        holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
                        holder.addChangePayload(null);
                    }
                }
            } else {
    
    
                // we cannot re-use cached views in this case. Recycle them all
                recycleAndClearCachedViews();
            }
        }

预加载 Prefetch 干了什么?

GapWorker

MotionEvent.ACTION_MOVE, ViewFlinger.run
mGapWorker.postFromTraversal(this, dx, dy);

tryGetViewHolderForPositionByDeadline // 上面讲过获取缓存的地址

long latestFrameVsyncMs = 0;
... ...
// 获取RenderThread的时间
latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
... ...
if (latestFrameVsyncMs == 0) {
    
    
	// abort - either no views visible, or couldn't get last vsync for estimating next
	return;
}

// 计算预加载的最后时间,如果能在截止日期之前完成预加载,那么就能成功完成ViewHolder的预加载,如果不能那么就预加载失败
long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
prefetch(nextFrameNs);

资料:
RecyclerView 预加载机制源码分析 https://conorlee.top/2019/07/17/RecyclerView-pre-initiate/


ExtraLayoutSpace 做了什么?

AnchorInfo,LayoutState
布局ItemView


优化了解

概述

根据 Recycler 获取或者存储 缓存的流程,我们知道 RecyclerView 优化 最重要是 减少 createViewHolder, bindViewHolder耗时(时间)调用次数,下面我们将围绕着两个东西来讲解下一些简单的优化事宜.
在这里插入图片描述

onCreateViewHolder
在这里插入图片描述
onBindViewHolder
在这里插入图片描述
想详细了解绘制过度,耗时查找等优化事宜,参考这篇博客-性能优化学习笔记.
一些函数相关的Demo 可以看看这个

根据上面的两个内容再补充一些细节:

  • 合理使用缓存设置(setItemViewCacheSize,setViewCacheExtension,setRecycledViewPool)
  • 注意耗时操作(尤其是屏幕滚动的时候,尽量 停止加载的操作)
  • 减少布局结构、减少过渡绘制,可以提高item的 measure 与 draw 的效率。也尽量避免多次measure & layout 次数(比如TextView可以进行有效优化)
    TextView 可以使用 使用 StaticLayout 或者 DynamicLayout 的自定义 View 来代替它
  • 取消默认动画
    mRecyclerView.setItemAnimator(null); 也可以改善一点点.
  • 调整draw缓存
    mRecyclerView.setDrawingCacheEnabled(true); mRecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
  • 慎用Alpha(不管是图片还是View,都需要注意下)
    也可以尝试重写 View 的 hasOverlappingRendering return false,提升一点点性能
  • 尽量使用稳定的高版本RecyclerView,比如新版本(25.1.0 及以上)有 Prefetch 功能
  • Item 高度是固定的话,RecyclerView.setHasFixedSize(true)
  • onViewRecycled 可以回收一些资源.
  • 设置更多预留空间(屏幕显示范围之外),重写 getExtraLayoutSpace
    启动应用后,如果一整屏 item的时候,向下滑动,RecyclerView找不到缓存,它将创建一个新的item,导致有点延时的感觉.
  • RecyclerView没有ItemClick方法,根据前面缓存的了解,建议 onCreateViewHolder 添加一次,避免多次调用 onBindViewHolder。
  • diffutil 可以了解下,是一个不错的工具,可以 判断两个数据集的差距
  • swapadapter 也可以了解下. 以前 setAdapter 是要清空缓存的,可以很好的发挥缓存的性能. 应用场景是两个数据源有很大的相似部分的情况下。
  • linearlayoutmanager的onSaveInstanceState和onRestoreInstanceState可以了解下.
  • 根据不同的机型(CPU,内存,内存使用情况 等等) 特性 进行对应的 优化策略(空间与时间 不可能达到100%完美平衡)
    也可以了解下这个几个函数(onTrimMemory,onLowMemory)

onTrimMemory使用资料

onTrimMemory(int level) :
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: // 你的用户界面不再可见,此时应该释放仅仅使用在UI上的大资源
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: // 系统处于低内存状态,app正在运行且不会被杀死
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: // 系统正处于低内存状态,app进程位于LRU List开始附近,尽管app进程被杀死的概率低,系统可能已经开始杀在LRU List中的后台进程,此时应该释放一些资源,防止被杀的几率
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: // 系统处于更低内存状态,app正在运行且不会被杀死,可以释放不用的资源提升app性能
ComponentCallbacks2.TRIM_MEMORY_MODERATE: //  系统正处于低内存状态,app进程位于LRU List中部附近,如果系统进一步内存紧张,app进程可能会被杀掉
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: // 系统处于极低的内存状态,app仍然不会被杀死。但如果不是放资源,系统开始杀后台进程,此时app应该清理一些资源
ComponentCallbacks2.TRIM_MEMORY_COMPLETE: // 系统正处于低内存状态,app进程是首先被杀进程之一,如果系统现在没有恢复内存,应该立即释放对app不重要的所有内容

RecyclerViewPool 注意事项!!!

我在测试代码里面 共用了一个 RecyclerViewPool 。

// 这里需要,因为使用 RecyclerViewPool,是以空间换时间,
// 需要注意内存情况,在内存低的情况,可以尝试清理掉.
int max = 8; 
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_1, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_2, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_3, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_4, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_5, max);

共用 RecyclerViewPool 注意几个问题
在这里插入图片描述
类型(Type)为0 的ItemView 还是在调用 onCreateViewHolder 的,一共调用了12次.
因为并没有使用 Pool里面的缓存,为何,里面没有东西哈,取不出来holder = getRecycledViewPool().getRecycledView(type);,holder 为 null。所以第一次显示界面,还是在拼命的创建,这里让人很沮丧!!!
在这里插入图片描述
所以结论就是,虽然共用了RecyclerViewPool ,第一次加载页面 或者 页面快速滚动 又或者 想使用缓存Pool的时候,如果Pool里面没有,就需要重新创建。

反观使用 Leanback 虽然也是共用 RecyclerViewPool,但 在滚动的过程中,如果之前的 Pool没有缓存对应的类型,也是白塔,还是需要创建大量的ItemView,反正是挺耗时的.

当有多个页面的切换的时候,如果另一个页面的 RecyclerView被释放掉了. 它页面的之前的View会被清理掉. 下次再创建这个页面,进入,如果没有缓存使用,还是会进行创建.
在这里插入图片描述

再前面 我们已经分析过,当离开屏幕的Item会保存(putRecycledView)到对应的 类型下的缓存Pool(mScrap)里面。

如何加速第一次加载的优化
这里也是用空间换时间,避免 创建视图耗费的时间,进而可以快速的显示界面.
提前创建缓冲池的 ViewHolder,这样可以减少 onCreateViewHolder,但是还是需要 onBindViewHolder

// 看个简单的小栗子
recycledViewPool.putRecycledView(adapter.createViewHolder(recyclerView, TYPE_1));
recycledViewPool.putRecycledView(adapter.createViewHolder(recyclerView, TYPE_1));
recycledViewPool.putRecycledView(adapter.createViewHolder(recyclerView, TYPE_2));

第一次进入界面会连续调用 onCreateViewHolder,因为 onCreateViewHolder没有办法去设置 ItemType,所以需要调用 createViewHolder

注意getItemViewType
当很多不同类型的控件的时候,不设置 ItemType返回的话,或者只返回 position,界面肯定会错乱,也达不到优化的好处.

在 Adapter 的 getItemViewType 设置对应的类型
@Override
public int getItemViewType(int position) {
    
    
	return mDatas.get(position).getItemType();
}

因为缓存里面会根据 Type 去取:holder = getRecycledViewPool().getRecycledView(type);
所以添加缓存的大小以及类型的也需要注意,和你的不同类型的控件记得要对应上。

mRecycledViewPool.setMaxRecycledViews(ItemView.TYPE_1, 10);

注意getItemId的使用
调用 notifyDataSetChanged 的时候,recyclerView 不知道到底发生了什么,所以它只能认为所有的东西都发生了变化,将所有的 viewHolder 都放入到 pool 中。

位置固定,比如,广告位。
不会改变
数量合理,保存在内存中没啥关系。

// Adapter.setHasStableIds(true);
// mAdapter.hasStableIds()
// 当设置了以上的 开关后,detachAndScrapAttachedViews -> scrapOrRecycleView 的 recycler.scrapView(view); 
// 将缓存到 mAttachedScrap,最后直接使用,不需要创建,绑定数据,非常优化喔!!
public long getItemId(int position) {
    
    
    Data data = datas.get(position);
    return (data.getTitle()).hashCode();
}
// 当你的标题栏被修改了,调用 notifydatasetchanged 刷新.
// 如果没有被更新,就会使用缓存,相当的优化.

// 或者一般这样写
 @Override
public long getItemId(int position) {
    
    
	return position;
}

思考:如果 getScrapOrCachedViewForId 去 获取(getItemId )的 Id 被改变了,那么 tryGetViewHolderForPositionByDeadline 接下来如何执行?自己思考下?然后单独

onViewRecycled的使用
View 被回收的时候调用,所以在这个函数里面可以释放资源;
比如释放一些图片资源,Glid,ImageView等等.

思考:为何这里可以释放掉不要的东西?
答:因为 addViewHolderToRecycledViewPool -> dispatchViewRecycled -> mAdapter.onViewRecycled,所以 ViewHolder 加入了 RecycledViewPool,复用的时候,不需要创建View,但是需要重新绑定数据.

CacheView使用注意
CacheView 默认为2个,主要是用来保存临时滚动屏幕外的 ViewHolder,主要是为了避免内存抖动.
比如你上下滚动又回来啦,只是短暂移除屏幕,又马上使用了,这里就不需要重新 创建 与 绑定.
CacheView 如果需要使用,其实就是 用空间换时间. 因为是实实在在的存在数组里面的.

setInitialItemPrefetchCount

RecyclerView的单击事件如何加?

一般人都会将单击事件加在 onBindViewHolder里面,这里是错误的行为!因为我们分析过 CacheView默认只有两个,如果新出的View能用到这个缓存,当然不用重新的 创建,绑定;但是大部分时间都是在使用 RecyclerPool,所以需要绑定数据,这就导致,频繁的 单击事件添加,内存抖动,这里处理的方案是,在 ViewHolder里面添加。

public static class ColorViewHolder extends RecyclerView.ViewHolder {
    
    
        public ColorViewHolder(View itemView) {
    
    
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
    
    
                @Override
                public void onClick(View v) {
    
    
                    Toast.makeText(v.getContext(), getLayoutPosition(), Toast.LENGTH_LONG).show();
                }
            });
        }

LinearLayoutMaanager.setInitialPrefetchItemCount()

RecyclerView.setHasFixedSize(true)
局部刷新,避免 false 会触发,requestLayut()会重新走三大流程(onMeasure,onLayout,onDraw)
在这里插入图片描述

多个RecyclerView公用 RecyclerViewPool

根据设备的内存,CPU情况自动设定优化策略


参考资料

深入浅出 RecyclerView

自定义LayoutManger,可以了解这篇文章
http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/

Anatomy of RecyclerView: a Search for a ViewHolder (continued) [需要翻墙]
https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91

RecyclerView缓存原理,有图有真相
https://juejin.im/post/5b79a0b851882542b13d204b

RecyclerView缓存机制(咋复用?)
https://github.com/MicroKibaco/CrazyDailyQuestion/issues/7

真正带你搞懂 RecyclerView 的缓存机制
https://zhuanlan.zhihu.com/p/80475040

RecyclerView-LayoutManger相关:
RecyclerView解析之LinearLayoutManager

优化相关知识点:
Android 之如何优化 UI 渲染(上)

关于UI渲染,你需要了解什么?

渲染!!! Drawn out: how Android renders (Google I/O '18)

超过16ms就让页面卡顿了?

结束语

由于本人技术水平有限,有问题的地方还望一起探讨,学习,互相 进步,谢谢.
不要忘记三连,点赞,关注,收藏.
更欢迎加入TV社区, QQ群 与 关注TV文章专题.

Android TV 开源社区 https://gitee.com/kumei/Android_tv_libs
Android TV 文章专题 https://www.jianshu.com/c/3f0ab61a1322
Android TV QQ群1:522186932 QQ群2:468357191

猜你喜欢

转载自blog.csdn.net/qw85525006/article/details/91127988