Android源码解读——RecyclerView回收复用机制

Android源码解读——RecyclerView回收复用机制,第1张

概述问题归类:什么是回收?什么是复用?回收什么?复用什么?回收到哪里去?从哪里获得复用?什么时候回收?什么时候复用?带着以上几个问题来分析源码,当以上问题都能解释清楚的时候,对RecyclerView回收复用机制的了解也算是完成了。1、什么是回收?什么是复用?回收:即缓存,RecyclerView的缓 问题归类:什么是回收?什么是复用?回收什么?复用什么?回收到哪里去?从哪里获得复用?什么时候回收?什么时候复用?

带着以上几个问题来分析源码,当以上问题都能解释清楚的时候,对RecyclerVIEw回收复用机制的了解也算是完成了。

1、什么是回收?什么是复用?

回收:即缓存,RecyclerVIEw的缓存是将内容存到集合里面。

复用:即取缓存,从集合中去获取。

2、回收什么?复用什么?

回收和复用的对象都是VIEwHolder。

什么是VIEwHolder?VIEwHolder其实就是用来包装vIEw的,我们可以将它看成列表的itemvIEw

3、回收到哪里去?从哪里获得复用?4、什么时候回收?什么时候复用?

问题3、4结合源码一起分析。

首先对RecyclerVIEw进行普通的使用

 

public class TestRvAdapter extends RecyclerVIEw.Adapter<TestRvAdapter.VIEwHolder> {    private Context context;    private List<Star> starList;    private static final String TAG = "TestRvAdapter";    public TestRvAdapter(Context context, List<Star> starList) {        this.context = context;        this.starList = starList;    }    @NonNull    @OverrIDe    public TestRvAdapter.VIEwHolder onCreateVIEwHolder(@NonNull VIEwGroup parent, int vIEwType) {        VIEw vIEw = LayoutInflater.from(context).inflate(R.layout.rv_top_item, null);        Log.e(TAG, "onCreateVIEwHolder: " + getItemCount());        return new VIEwHolder(vIEw);    }    @OverrIDe    public voID onBindVIEwHolder(@NonNull TestRvAdapter.VIEwHolder holder, int position) {        holder.tv.setText(starList.get(position).getname());        Log.e(TAG, "onBindVIEwHolder: " + position);    }    @OverrIDe    public int getItemCount() {        return starList == null ? 0 : starList.size();    }    public class VIEwHolder extends RecyclerVIEw.VIEwHolder {        private TextVIEw tv;        public VIEwHolder(@NonNull VIEw itemVIEw) {            super(itemVIEw);            tv = itemVIEw.findVIEwByID(R.ID.tv_star);        }    }}

 

public class RvTestActivity extends AppCompatActivity {    private RecyclerVIEw recyclerVIEw;    private List<Star> starList = new ArrayList<>();    @OverrIDe    protected voID onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_test_rv);        initData();        recyclerVIEw = findVIEwByID(R.ID.test_rv);        /*注意此处*/        recyclerVIEw.setLayoutManager(new GrIDLayoutManager(this, 1));        recyclerVIEw.addItemdecoration(new divIDerItemdecoration(this, linearLayout.VERTICAL));        recyclerVIEw.setAdapter(new TestRvAdapter(this, starList));    }    private voID initData() {        for (int i = 1; i <= 1000; i++) {            starList.add(new Star(i + "", "快乐家族" + i));        }    }}

以上代码相信都看得懂吧?我们看下代码细节。

在adapter的onCreateVIEwHolder和onBindVIEwHolder两个方法里面进行打印,然后在activity里面我们设置LayoutManager时,用的是GrIDLayoutManager,数量设置为1,其实跟linearlayoutmanager是一样的效果,但是这里为了方便测试,所以用的是GrIDLayoutManager,进入到GrIDLayoutManager源码也可以看到,他是继承自linearlayoutmanager的。 OK现在运行看看打印情况。

可以发现,刚进入界面的时候,onCreateVIEwHolder和onBindVIEwHolder都进行了打印,而往下滑动之后只会打印onBindVIEwHolder,不会再进入onCreateVIEwHolder

我们把代码 new GrIDLayoutManager(this, 1) 的1改成8试试会是怎么样的打印情况:

 

可以看到,无论滚动到哪个item,onCreateVIEwHolder和onBindVIEwHolder都一直在打印,那么这是为什么呢?

接下来分析源码。

首先就 RecyclerVIEw 的源码就有一万多行,这还没包括layoutmanager的,分析到底从何入手?那么我们知道,在触发onCreateVIEwHolder和onBindVIEwHolder的时候,我们都对屏幕进行了滑动,所以我们直接先进入 RecyclerVIEw 的ontouchEvent看看RecyclerVIEw 是如何进行滑动处理的。由于是滑动,因此我们直接进入action_move里面进行查看。(由于源码太多,就不贴上来了,源码流程只需要关系入口以及我们需要分析的部分就行了。)

入口:滑动 Move 事件 --> scrollByInternal --> scrollStep --> mLayout.scrollVerticallyBy(mLayout就是LayoutManager,所以我们要看他的实现类linearlayoutmanager.scrollVerticallyBy)  --> scrollBy --> fill --> layoutChunk --> layoutState.next获取vIEw --> addVIEw(vIEw);

vIEw就是从这里加载进RecyclerVIEw 的,那么在addVIEw之前有一个获取vIEw的动作layoutState.next,我们具体分析一下到底是如何获取的。

layoutState.next --> getVIEwForposition --> tryGetVIEwHolderForpositionByDeadline

tryGetVIEwHolderForpositionByDeadline就是我们最终需要找到的一个方法,在这个方法里面,RecyclerVIEw 通过缓存取出vIEwHolder,我们可以看到里面有各种 if (holder == null) 的判断,那么到底是如何去取的呢?

分以下几种情况去获取VIEwHolder

getChangedScrapVIEwForposition -- mChangeScrap 与动画相关getScrapOrHIDdenorCachedHolderForposition -- mAttachedScrap 、mCachedVIEwsgetScrapOrCachedVIEwForID -- mAttachedScrap 、mCachedVIEws (VIEwType,itemID)mVIEwCacheExtension.getVIEwForpositionAndType -- 自定义缓存getRecycledVIEwPool().getRecycledVIEw -- 从缓冲池里面获取

 

归纳一下我们可以得出,RecyclerVIEw 大致分为四级缓存

mChangeScrap与 mAttachedScrap,用来缓存还在屏幕内的 VIEwHoldermCachedVIEws,用来缓存移除屏幕之外的 VIEwHoldermVIEwCacheExtension,开发给用户的自定义扩展缓存,需要用户自己管理 VIEw 的创建和缓存(通常用不到,至少目前为止我没用到过)RecycledVIEwPool,VIEwHolder 缓存池

多级缓存的最终目的就是为了提升性能

看到源码最后一个if语句,也就是当所有的缓存都没有vIEwHolder的时候,这个时候我们就需要创建,

holder = mAdapter.createVIEwHolder(RecyclerVIEw.this, type);

createVIEwHolder方法里面自然的就调用到了adapter的onCreateVIEwHolder方法

也就是:当没有缓存的时候: mAdapter.createVIEwHolder --> onCreateVIEwHolder

    创建VIEwHolder 后 绑定: tryBindVIEwHolderByDeadline--> mAdapter.bindVIEwHolder--> onBindVIEwHolder

以上为复用(取缓存)的过程,那么回收(存缓存)是个什么样的机制呢?

首先也是一样要找到入口

当我们刷新布局的时候,RecyclerVIEw 会调用到 linearlayoutmanager 的 onLayoutChildren 方法。

linearlayoutmanager.onLayoutChildren --> detachAndScrapAttachedVIEws --> scrapOrRecycleVIEw

    private voID scrapOrRecycleVIEw(Recycler recycler, int index, VIEw vIEw) {            final VIEwHolder vIEwHolder = getChildVIEwHolderInt(vIEw);            if (vIEwHolder.shouldIgnore()) {                if (DEBUG) {                    Log.d(TAG, "ignoring vIEw " + vIEwHolder);                }                return;            }            if (vIEwHolder.isInvalID() && !vIEwHolder.isRemoved()                    && !mRecyclerVIEw.mAdapter.hasStableIDs()) {                removeVIEwAt(index);                recycler.recycleVIEwHolderInternal(vIEwHolder);            } else {                detachVIEwAt(index);                recycler.scrapVIEw(vIEw);                mRecyclerVIEw.mVIEwInfoStore.onVIEwDetached(vIEwHolder);            }        }

 

那么在 scrapOrRecycleVIEw 方法里面分为了两种情况:

recycler.recycleVIEwHolderInternal(vIEwHolder);recycler.scrapVIEw(vIEw);分析recycler.recycleVIEwHolderInternal(vIEwHolder);

进入到recycleVIEwHolderInternal方法,可以看到有如下的一个判断:

if (mVIEwCacheMax > 0                        && !holder.hasAnyOfTheFlags(VIEwHolder.FLAG_INVALID                        | VIEwHolder.FLAG_REMOVED                        | VIEwHolder.FLAG_UPDATE                        | VIEwHolder.FLAG_ADAPTER_position_UNKNowN)) {                    // Retire oldest cached vIEw                    int cachedVIEwSize = mCachedVIEws.size();                    if (cachedVIEwSize >= mVIEwCacheMax && cachedVIEwSize > 0) {                        recycleCachedVIEwAt(0);                        cachedVIEwSize--;                    }                    int targetCacheIndex = cachedVIEwSize;                    if (ALLOW_THREAD_GAP_WORK                            && cachedVIEwSize > 0                            && !mPrefetchRegistry.lastPrefetchIncludedposition(holder.mposition)) {                        // when adding the vIEw, skip past most recently prefetched vIEws                        int cacheIndex = cachedVIEwSize - 1;                        while (cacheIndex >= 0) {                            int cachedPos = mCachedVIEws.get(cacheIndex).mposition;                            if (!mPrefetchRegistry.lastPrefetchIncludedposition(cachedPos)) {                                break;                            }                            cacheIndex--;                        }                        targetCacheIndex = cacheIndex + 1;                    }                    mCachedVIEws.add(targetCacheIndex, holder);                    cached = true;                }

 

这段代码表示:如果VIEwHolder不改变(有时候一个列表可能用到了多个VIEwHolder),那么先判断mCachedVIEws的大小

         mCachedVIEws.size 大于默认可缓存的大小(默认为2),执行recycleCachedVIEwAt,实际上就是将cacheVIEw里面的数据拿到RecycledVIEwPool缓存池里面,然后再把新的缓存存入到cacheVIEw里面,采取的是先进先出的原则。

       缓存池里面保存的只是 VIEwHolder 类型,没有数据,而cacheVIEw是包含数据的(经过binder了的VIEwHolder)。这也是为什么分级的原因,最终还是为了执行效率。

而缓冲池是与Map类似。他的缓存形式就如:ArrayList<ArrayList<vIEwholder>>, RecycledVIEwPool 会根据VIEwType来进行分类,ArrayList<vIEwholder>对应的就是一个VIEwType

我们进入recycleCachedVIEwAt--> addVIEwHolderToRecycledVIEwPool(缓存到缓存池里面)

如果上面的条件不满足,那么会执行到下面的if语句块,直接缓存到缓存池:

if (!cached) {    addVIEwHolderToRecycledVIEwPool(holder, true);    recycled = true;}

 

直接执行addVIEwHolderToRecycledVIEwPool,可以看到与上面调用的方法一样,在这个方法里面会执行getRecycledVIEwPool().putRecycledVIEw(holder)

 

 

    public voID putRecycledVIEw(VIEwHolder scrap) {            final int vIEwType = scrap.getItemVIEwType();            final ArrayList<VIEwHolder> scrapHeap = getScrapDataForType(vIEwType).mScrapHeap;
        //多余的直接丢弃 mMaxScrap = 5 if (mScrap.get(vIEwType).mMaxScrap <= scrapHeap.size()) { return; } if (DEBUG && scrapHeap.contains(scrap)) { throw new IllegalArgumentException("this scrap item already exists"); } scrap.resetInternal(); scrapHeap.add(scrap); }

 

 

 

通过上面源码可以得出,每个ArrayList<vIEwholder>最多存储5个VIEwHolder,多余的会直接丢弃不保存。

为什么数据满了之后,会直接丢弃呢?接着分析:

假如没有满的话,也就是没有多余的话,那么就会执行 scrap.resetInternal(); 

        voID resetInternal() {            mFlags = 0;            mposition = NO_position;            moldposition = NO_position;            mItemID = NO_ID;            mPreLayoutposition = NO_position;            mIsRecyclableCount = 0;            mShadowedHolder = null;            mShadowingHolder = null;            clearPayload();            mWasimportantForAccessibilityBeforeHIDden = VIEwCompat.important_FOR_ACCESSIBIliTY_auto;            mPendingAccessibilityState = PENDING_ACCESSIBIliTY_STATE_NOT_SET;            clearnestedRecyclerVIEwIfNotnested(this);        }        

 

这里面是将VIEwHolder进行清空,然后再通过 scrapHeap.add(scrap); 进行保存,这也是为什么缓冲池里面只是 VIEwHolder类型,而没有数据的原因,没有数据的话,我们缓存太多没有意义。

这也是为什么前面我们运行的时候,当值=8的时候,会不停的刷新onCreateVIEwHolder和onBindVIEwHolder,而为1的时候只刷新onBindVIEwHolder,因为其默认上限值为2+5=7,超过这个数,多余的就没进入缓存了。

以上分析完毕之后我们知道了,下面两种情况是如何缓存的了

而第三种是系统不管的,也就是我们自定义的缓存

分析代码:recycler.scrapVIEw(vIEw),mAttachedScrap 和 mChangedScrap 这种情况
    voID scrapVIEw(VIEw vIEw) {            final VIEwHolder holder = getChildVIEwHolderInt(vIEw);            if (holder.hasAnyOfTheFlags(VIEwHolder.FLAG_REMOVED | VIEwHolder.FLAG_INVALID)                    || !holder.isUpdated() || canReuseUpdatedVIEwHolder(holder)) {                if (holder.isInvalID() && !holder.isRemoved() && !mAdapter.hasStableIDs()) {                    throw new IllegalArgumentException("Called scrap vIEw with an invalID vIEw."                            + " InvalID vIEws cannot be reused from scrap, they should rebound from"                            + " recycler pool." + exceptionLabel());                }                holder.setScrapContainer(this, false);                mAttachedScrap.add(holder);            } else {                if (mChangedScrap == null) {                    mChangedScrap = new ArrayList<VIEwHolder>();                }                holder.setScrapContainer(this, true);                mChangedScrap.add(holder);            }        }

 

我们发现,该方法一进入的时候就直接在处理了 

mAttachedScrap.add(holder);
mChangedScrap.add(holder);

只是需要清楚上面的判断是如何判断的,其意思就是 if 的情况 ,当我们的标记没移除、或者失效、更新、动画不复用或者没动画的时候,就会利用 mAttachedScrap 进行保存,否则就用 mChangedScrap 进行保存。

完。 总结

以上是内存溢出为你收集整理的Android源码解读——RecyclerView回收复用机制全部内容,希望文章能够帮你解决Android源码解读——RecyclerView回收复用机制所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/web/1053967.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-25
下一篇2022-05-25

发表评论

登录后才能评论

评论列表(0条)

    保存