Android架構(gòu)師之深入理解RecyclerView復(fù)用和緩存機(jī)制詳解
本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」,作者Android開發(fā)編程。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號(hào)。
前言
學(xué)習(xí)源碼,研究源碼編程思想,是程序開發(fā)者進(jìn)階的必經(jīng)之路
大家都知道RecyclerView有回收復(fù)用機(jī)制,那么回收復(fù)用機(jī)制是如何作用的?
今天我們就用源碼來(lái)講解,一起學(xué)習(xí)
一、Recycler介紹
RecyclerView是通過(guò)內(nèi)部類Recycler管理的緩存,那么Recycler中緩存的是什么?我們知道RecyclerView在存在大量數(shù)據(jù)時(shí)依然可以滑動(dòng)的如絲滑般順暢,而RecyclerView本身是一個(gè)ViewGroup,那么滑動(dòng)時(shí)避免不了添加或移除子View(子View通過(guò)RecyclerView#Adapter中的onCreateViewHolder創(chuàng)建),如果每次使用子View都要去重新創(chuàng)建,肯定會(huì)影響滑動(dòng)的流 暢性,所以RecyclerView通過(guò)Recycler來(lái)緩存的是ViewHolder(內(nèi)部包含子View),這樣在滑動(dòng)時(shí)可以復(fù)用子View,某些條件下還可以復(fù)用子View綁定的數(shù)據(jù)。所以本質(zhì)上緩存是為了減少重復(fù)繪制View和綁定數(shù)據(jù)的時(shí)間,從而提高了滑動(dòng)時(shí)的性能
- public final class Recycler {
- final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
- ArrayList<ViewHolder> mChangedScrap = null;
- final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
- private final List<ViewHolder>
- mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
- private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
- int mViewCacheMax = DEFAULT_CACHE_SIZE;
- RecycledViewPool mRecyclerPool;
- private ViewCacheExtension mViewCacheExtension;
- static final int DEFAULT_CACHE_SIZE = 2;
Recycler緩存ViewHolder對(duì)象有4個(gè)等級(jí),優(yōu)先級(jí)從高到底依次為:
1、ArrayList mAttachedScrap --- 緩存屏幕中可見范圍的ViewHolder
2、ArrayList mCachedViews ---- 緩存滑動(dòng)時(shí)即將與RecyclerView分離的ViewHolder,按子View的position或id緩存,默認(rèn)最多存放2個(gè)
3、ViewCacheExtension mViewCacheExtension --- 開發(fā)者自行實(shí)現(xiàn)的緩存
4、RecycledViewPool mRecyclerPool --- ViewHolder緩存池,本質(zhì)上是一個(gè)SparseArray,其中key是ViewType(int類型),value存放的是 ArrayList< ViewHolder>,默認(rèn)每個(gè)ArrayList中最多存放5個(gè)ViewHolder。
二、緩存機(jī)制分析詳解
RecyclerView滑動(dòng)時(shí)會(huì)觸發(fā)onTouchEvent#onMove,回收及復(fù)用ViewHolder在這里就會(huì)開始。我們知道設(shè)置RecyclerView時(shí)需要設(shè)置LayoutManager,LayoutManager負(fù)責(zé)RecyclerView的布局,包含對(duì)ItemView的獲取與復(fù)用。以LinearLayoutManager為例,當(dāng)RecyclerView重新布局時(shí)會(huì)依次執(zhí)行下面幾個(gè)方法:
onLayoutChildren():對(duì)RecyclerView進(jìn)行布局的入口方法
fill(): 負(fù)責(zé)對(duì)剩余空間不斷地填充,調(diào)用的方法是layoutChunk()
layoutChunk():負(fù)責(zé)填充View,該View最終是通過(guò)在緩存類Recycler中找到合適的View的
上述的整個(gè)調(diào)用鏈:onLayoutChildren()->fill()->layoutChunk()->next()->getViewForPosition(),getViewForPosition()即是是從RecyclerView的回收機(jī)制實(shí)現(xiàn)類Recycler中獲取合適的View,
下面主要就來(lái)從看這個(gè)Recycler#getViewForPosition()的實(shí)現(xiàn)。
- @NonNull
- public View getViewForPosition(int position) {
- return getViewForPosition(position, false);
- }
- View getViewForPosition(int position, boolean dryRun) {
- return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
- }
他們都會(huì)執(zhí)行tryGetViewHolderForPositionByDeadline函數(shù),繼續(xù)跟進(jìn)去:
//根據(jù)傳入的position獲取ViewHolder
- ViewHolder tryGetViewHolderForPositionByDeadline(int position,
- boolean dryRun, long deadlineNs) {
- ...省略
- boolean fromScrapOrHiddenOrCache = false;
- ViewHolder holder = null;
- //預(yù)布局 屬于特殊情況 從mChangedScrap中獲取ViewHolder
- if (mState.isPreLayout()) {
- holder = getChangedScrapViewForPosition(position);
- fromScrapOrHiddenOrCache = holder != null;
- }
- if (holder == null) {
- //1、嘗試從mAttachedScrap中獲取ViewHolder,此時(shí)獲取的是屏幕中可見范圍中的ViewHolder
- //2、mAttachedScrap緩存中沒有的話,繼續(xù)從mCachedViews嘗試獲取ViewHolder
- holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
- ...省略
- }
- if (holder == null) {
- final int offsetPosition = mAdapterHelper.findPositionOffset(position);
- ...省略
- final int type = mAdapter.getItemViewType(offsetPosition);
- //如果Adapter中聲明了Id,嘗試從id中獲取,這里不屬于緩存
- if (mAdapter.hasStableIds()) {
- holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
- type, dryRun);
- }
- if (holder == null && mViewCacheExtension != null) {
- 3、從自定義緩存mViewCacheExtension中嘗試獲取ViewHolder,該緩存需要開發(fā)者實(shí)現(xiàn)
- 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) {
- //如果獲取成功,會(huì)重置ViewHolder狀態(tài),所以需要重新執(zhí)行Adapter#onBindViewHolder綁定數(shù)據(jù)
- holder.resetInternal();
- if (FORCE_INVALIDATE_DISPLAY_LIST) {
- invalidateDisplayListInt(holder);
- }
- }
- }
- if (holder == null) {
- ...省略
- //5、若以上緩存中都沒有找到對(duì)應(yīng)的ViewHolder,最終會(huì)調(diào)用Adapter中的onCreateViewHolder創(chuàng)建一個(gè)
- holder = mAdapter.createViewHolder(RecyclerView.this, type);
- }
- }
- boolean bound = false;
- if (mState.isPreLayout() && holder.isBound()) {
- holder.mPreLayoutPosition = position;
- } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
- final int offsetPosition = mAdapterHelper.findPositionOffset(position);
- //6、如果需要綁定數(shù)據(jù),會(huì)調(diào)用Adapter#onBindViewHolder來(lái)綁定數(shù)據(jù)
- bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
- }
- ...省略
- return holder;
- }
通過(guò)mAttachedScrap、mCachedViews及mViewCacheExtension獲取的ViewHolder不需要重新創(chuàng)建布局及綁定數(shù)據(jù);通過(guò)緩存池mRecyclerPool獲取的ViewHolder不需要重新創(chuàng)建布局,但是需要重新綁定數(shù)據(jù);如果上述緩存中都沒有獲取到目標(biāo)ViewHolder,那么就會(huì)回調(diào)Adapter#onCreateViewHolder創(chuàng)建布局,以及回調(diào)Adapter#onBindViewHolder來(lái)綁定數(shù)據(jù)
總結(jié)
RecyclerView 滑動(dòng)場(chǎng)景下的回收復(fù)用涉及到的結(jié)構(gòu)體兩個(gè):
- mCachedViews 和 RecyclerViewPool
- mCachedViews 優(yōu)先級(jí)高于 RecyclerViewPool,回收時(shí),最新的 ViewHolder 都是往 mCachedViews 里放,如果它滿了,那就移出一個(gè)扔到 ViewPool 里好空出位置來(lái)緩存最新的 ViewHolder。
- 復(fù)用時(shí),也是先到 mCachedViews 里找 ViewHolder,但需要各種匹配條件,概括一下就是只有原來(lái)位置的卡位可以復(fù)用存在 mCachedViews 里的 ViewHolder,如果 mCachedViews 里沒有,那么才去 ViewPool 里找。
- 在 ViewPool 里的 ViewHolder 都是跟全新的 ViewHolder 一樣,只要 type 一樣,有找到,就可以拿出來(lái)復(fù)用,重新綁定下數(shù)據(jù)即可。