Android深⼊理解RecyclerView的缓存机制
⽂章⽬录
RecyclerView在项⽬中的使⽤已经很普遍了,可以说是项⽬中最⾼频使⽤的⼀个控件了。除了布局灵活性、丰富的动画,RecyclerView还有优秀的缓存机制,本⽂尝试通过源码深⼊了解⼀下RecyclerView中的缓存机制。
写在前⾯
RecyclerView 是通过内部类 Recycler 管理的缓存,那么 Recycler 中缓存的是什么?我们知道 RecyclerView 在存在⼤量数据时依然可以滑动的如丝滑般顺畅,⽽ RecyclerView 本⾝是⼀个 ViewGroup ,那么滑动时避免不了添加或移除⼦View(⼦View通
过RecyclerView#Adapter中的onCreateViewHolder创建),如果每次使⽤⼦View都要去重新创建,肯定会影响滑动的流 畅性,所以RecyclerView 通过 Recycler 来缓存的是 ViewHolder (内部包含⼦View),这样在滑动时可以复⽤⼦View,某些条件下还可以复⽤⼦View绑定的数据。所以本质上缓存是为了减少重复绘制View和绑定数据的时间,从⽽提⾼了滑动时的性能。
四级缓存
Recycler缓存ViewHolder对象有4个等级,优先级从⾼到底依次为:
mAttachedScrap:ArrayList< ViewHolder>类型
mCachedViews:ArrayList< ViewHolder>类型
mViewCacheExtension:ViewCacheExtension类型
mRecyclerPool:RecycledViewPool类型
注:官⽅上把mAttachedScrap、mCachedViews当成⼀级了,为了⽅便区分,本⽂还是把他们当成两级缓存。
说说心里话作文
缓存涉及对象作⽤
重新创建视图
View(onCreateViewHolder)
重新绑定数据
(onBindViewHolder)
⼀
级
风味茄子缓
存
mAttachedScrap缓存屏幕中可见范围的ViewHolder fal fal ⼆
级缓存mCachedViews
缓存滑动时即将与RecyclerView分离的ViewHolder,按
⼦View的position或id缓存,默认最多存放2个
fal fal
三
级
缓
存
mViewCacheExtension开发者⾃⾏实现的缓存--四
名师指导级缓存mRecyclerPool
ViewHolder缓存池,本质上是⼀个SparArray,其中
key是ViewType(int类型),value存放的是 ArrayList<
ViewHolder>,默认每个ArrayList中最多存放5个
ViewHolder
fal true
RecyclerView 滑动时会触发 onTouchEvent#onMove ,回收及复⽤ ViewHolder 在这⾥就会开始。我们知道设置 RecyclerView 时需要设置LayoutManager, LayoutManager 负责 RecyclerView 的布局,包含对 ItemView 的获取与复⽤。以LinearLayoutManager为例,当RecyclerView 重新布局时会
依次执⾏下⾯⼏个⽅法:
onLayoutChildren():对RecyclerView进⾏布局的⼊⼝⽅法
fill(): 负责对剩余空间不断地填充,调⽤的⽅法是layoutChunk()
layoutChunk():负责填充View,该View最终是通过在缓存类Recycler中找到合适的View
上述的整个调⽤链:onLayoutChildren()->fill()->layoutChunk()->next()->getViewForPosition(),getViewForPosition()即是是从RecyclerView的回收机制实现类Recycler中获取合适的View,下⾯主要就来从看这个Recycler#getViewForPosition()的实现。
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, fal);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
他们都会执⾏tryGetViewHolderForPositionByDeadline函数,继续跟进去:
//根据传⼊的position获取ViewHolder
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
---------省略----------
boolean fromScrapOrHiddenOrCache = fal;
ViewHolder holder = null;
//预布局属于特殊情况从mChangedScrap中获取ViewHolder
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
//1、尝试从mAttachedScrap中获取ViewHolder,此时获取的是屏幕中可见范围中的ViewHolder
//2、mAttachedScrap缓存中没有的话,继续从mCachedViews尝试获取ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
----------省略----------
}
if (holder == null) {
final int offtPosition = mAdapterHelper.findPositionOfft(position);
-
--------省略----------
final int type = ItemViewType(offtPosition);
//如果Adapter中声明了Id,尝试从id中获取,这⾥不属于缓存
if (mAdapter.hasStableIds()) {
holder = ItemId(offtPosition),
type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
3、从⾃定义缓存mViewCacheExtension中尝试获取ViewHolder,该缓存需要开发者实现
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
//4、从缓存池mRecyclerPool中尝试获取ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//如果获取成功,会重置ViewHolder状态,所以需要重新执⾏Adapter#onBindViewHolder绑定数据 Internal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
---------省略----------
//5、若以上缓存中都没有找到对应的ViewHolder,最终会调⽤Adapter中的onCreateViewHolder创建⼀个 holder = ateViewHolder(RecyclerView.this, type);
}
}
boolean bound = fal;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} el if (!holder.isBound() || dsUpdate() || holder.isInvalid()) {
final int offtPosition = mAdapterHelper.findPositionOfft(position);
//6、如果需要绑定数据,会调⽤Adapter#onBindViewHolder来绑定数据
bound = tryBindViewHolderByDeadline(holder, offtPosition, position, deadlineNs);
}
----------省略----------
return holder;
}
上述逻辑⽤流程图表⽰:
总结⼀下上述流程:通过mAttachedScrap、mCachedViews及mViewCacheExtension获取的ViewHol
der不需要重新创建布局及绑定数据;通过缓存池mRecyclerPool获取的ViewHolder不需要重新创建布局,但是需要重新绑定数据;如果上述缓存中都没有获取到⽬标ViewHolder,那么就会回调Adapter#onCreateViewHolder创建布局,以及回调Adapter#onBindViewHolder来绑定数据。
ViewCacheExtension
我们已经知道ViewCacheExtension属于第三级缓存,需要开发者⾃⾏实现,那么ViewCacheExtension在什么场景下使⽤?⼜是如何实现的呢?
⾸先我们要明确⼀点,那就是Recycler本⾝已经设置了好⼏级缓存了,为什么还要留个接⼝让开发者去⾃⾏实现缓存呢?关于这⼀点,谈⼀谈我的理解:来看看Recycler中的其他缓存,其中mAttachedScrap⽤来处理可见屏幕的缓存;mCachedViews⾥存储的数据虽然是根
据position来缓存,但是⾥⾯的数据随时可能会被替换的;再来看mRecyclerPool,mRecyclerPool⾥按viewType去存储ArrayList< ViewHolder>,所以mRecyclerPool并不能按position去存储ViewHolder,⽽且从mRecyclerPool取出的View每次都要去
⾛Adapter#onBindViewHolder去重新绑定数据。假如我现在需要在⼀个特定的位置(⽐如position=0位置)⼀直展⽰某个View,且⾥⾯的内容是不变的,那么最好的情况就是在特定位置时,既不需要每次重
新创建View,也不需要每次都去重新绑定数据,上⾯的⼏种缓存显然都是不适⽤的,这种情况该怎么办呢?可以通过⾃定义缓存ViewCacheExtension实现上述需求。
ViewCacheExtension适⽤场景:ViewHolder位置固定、内容固定、数量有限时使⽤
ViewCacheExtension使⽤举例:
冰柜的温度怎么调
⽐如在position = 0时展⽰的是⼀个⼴告,位置不变,内容不变,来看看如何实现:
//DemoRvActivity.java:
public class DemoRvActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private DemoAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
tContentView(R.layout.activity_demo_rv);
recyclerView = findViewById(R.id.rv_view);
recyclerView.tLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
adapter = new DemoAdapter();
recyclerView.tAdapter(adapter);
//viewType类型为TYPE_SPECIAL时,设置四级缓存池RecyclerPool不存储对应类型的数据因为需要开发者⾃⾏缓存 RecycledViewPool().tMaxRecycledViews(DemoAdapter.TYPE_SPECIAL, 0);
//设置ViewCacheExtension缓存
recyclerView.tViewCacheExtension(new MyViewCacheExtension());
}
//实现⾃定义缓存ViewCacheExtension
class MyViewCacheExtension extends RecyclerView.ViewCacheExtension {
@Nullable
@Override
public View getViewForPositionAndType(@NonNull RecyclerView.Recycler recycler, int position, int viewType) { //如果viewType为TYPE_SPECIAL,使⽤⾃⼰缓存的View去构建ViewHolder
// 否则返回null,会使⽤系统RecyclerPool缓存或者从新通过onCreateViewHolder构建View及ViewHolder
return viewType == DemoAdapter.TYPE_SPECIAL ? (position) : null;
}
}
}
可爱的英语怎么写在看下Adapter的代码:
public class DemoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//viewType类型 TYPE_COMMON代表普通类型 TYPE_SPECIAL代表特殊类型(此处的View和数据⼀直不变)
public static final int TYPE_COMMON = 1;
windows10易升public static final int TYPE_SPECIAL = 101;
public SparArray<View> caches = new SparArray<>();//开发者⾃⾏维护的缓存
private List<String> mDatas = new ArrayList<>();
DemoAdapter() {
initData();
}
名满天下private void initData() {
for (int i = 0; i < 50; i++) {
if (i == 0) {
mDatas.add("我是⼀条特殊的数据,我的位置固定、内容不会变");
} el {
mDatas.add("这是第" + (i + 1) + "条数据");
}
}
}
public List<String> getData() {
return mDatas;
}
痕迹的迹怎么写
@NonNull
@Override