当前位置: 首页 > 科技观察

Android架构师深入理解RecyclerView多路复用及缓存机制详解

时间:2023-03-12 18:43:48 科技观察

本文转载自微信公众号《Android开发编程》,作者Android开发编程。转载本文请联系Android开发编程公众号。前言学习源代码,研究源代码编程思想,是程序开发者前进的必由之路。大家都知道RecyclerView是有回收复用机制的,那么回收复用机制是怎么工作的呢?今天我们就用源码来讲解,一起学习1.Recycler介绍RecyclerView是一个由内部类Recycler管理的缓存,那么Recycler中缓存的是什么?我们知道RecyclerView在数据量大的情况下依然可以滑动如丝般顺滑,而RecyclerView本身就是一个ViewGroup,所以在滑动的时候不可避免的要添加或者移除子View(子View是通过RecyclerView中的onCreateViewHolder创建的)#适配器)。如果每次使用子View都要重新创建,肯定会影响滑动的流畅度,所以RecyclerView通过Recycler缓存了ViewHolder(里面包含子View),这样子View在滑动的时候可以复用,而子视图绑定的数据在一定条件下也可以重用。所以缓存本质上就是减少重复绘制View和绑定数据的时间,从而提高滑动的性能mCachedViews=newArrayList();privatefinalListmUnmodifiableAttachedScrap=Collections.unmodifiableList(mAttachedScrap);privateintmRequestedCacheMax=DEFAULT_CACHE_SIZE;intmViewCacheMax=DEFAULT_CACHE_SIZE;RecycledViewPoolmRecyclerPool;privateViewCacheExtensionmViewCacheExtension;staticfinalintDEFAULT_CACHE_SIZE=2;Recycler缓存ViewHolder对象有4个等级,优先级从高低顺序为:1、ArrayListmAttachedScrap---缓存屏幕可见范围内的ViewHolder2、ArrayListmCachedViews---缓存缓存滑动时会和RecyclerView分离的ViewHolder,根据位置缓存或者子View的id,默认最多存储2个3.ViewCacheExtensionmViewCacheExtension---开发者自己实现的缓存4.RecycledViewPoolmRecyclerPool---ViewHolder缓存池,本质上是一个SparseArray,其中key为ViewType(int类型),value存放在ArrayList中。默认情况下,每个ArrayList中最多可以存储5个ViewHolder。2、缓存机制详解当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()的实现。@NonNullpublicViewgetViewForPosition(intposition){returngetViewForPosition(position,false);}ViewgetViewForPosition(intposition,booleandryRun){returntryGetViewHolderForPositionByDeadline(position,dryRun,FOREVER_NS).itemView;}他们会根据传入的位置/位置执行tryGetViewHolderForPositionByDeadline/函数:GetViewHolderDetryViewBolderViewHolder(intposition,booleandryRun,longdeadlineNs){...OmitbooleanfromScrapOrHiddenOrCache=false;ViewHolderholder=null;//预布局是特例来自mChangedScrap=holder!=null;}if(holder==null){//1。尝试从mAttachedScrap获取ViewHolder。这时候你拿到的就是屏幕可见范围内的ViewHolder//2.如果没有mAttachedScrap缓存,继续从mCachedViews尝试获取ViewHolderholder=getScrapOrHiddenOrCachedHolderForPosition(position,dryRun);...省略}if(holder==null){finalintoffsetPosition=mAdapterHelper.findPositionOffset(position);...省略finalinttype=mAdapter.getItemViewType(offsetPosition);//如果AId是在adapter中声明的,尝试从id中获取,这个不属于缓存(holder==null&&mViewCacheExtension!=null){3.尝试从自定义缓存mViewCacheExtension中获取ViewHolder,需要开发者实现finalViewview=mViewCacheExtension.getViewForPositionAndType(this,position,type);if(view!=null){holder=getChildViewHolder(view);}}if(holder==null){//fallbacktopool//4.尝试从缓冲池中获取ViewHolderholdermRecyclerPool=getRecycledViewPool().getRecycledView(type);if(holder!=null){//如果获取成功,会回收设置ViewHolder状态,所以需要重新执行Adapter#onBindViewHolder绑定数据holder.resetInternal();if(FORCE_INVALIDATE_DISPLAY_LIST){invalidateDisplayListInt(holder);}}}if(holder==null){...Omit//5,如果以上缓存都没有找到相应的ViewHolder,最终会调用Adapter中的onCreateViewHolder创建一个holder=mAdapter.createViewHolder(RecyclerView.this,type);}}booleanbound=false;if(mState.isPreLayout()&&holder.isBound()){holder.mPreLayoutPosition=position;}elseif(!holder.isBound()||holder.needsUpdate()||holder.isInvalid()){finalintoffsetPosition=mAdapterHelper.findPositionOffset(position);//6.如果需要绑定数据,会调用Adapter#onBindViewHolder绑定数据bound=tryBindViewHolderByDeadline(holder,offsetPosition,position,deadlineNs);}...省略returnholder;}通过mAttachedScrap获取的ViewHolder,mCachedViews和mViewCacheExtension不需要重新创建布局和绑定数据;通过缓冲池mRecyclerPool获取的ViewHolder不需要重新创建布局,但需要重新绑定数据;如果以上缓存都没有获取到目标ViewHolder,那么会回调Adapter#onCreateViewHolder创建布局,回调Adapter#onBindViewHolder绑定数据。总结RecyclerView在滑动场景的回收复用涉及两个结构体:mCachedViews和RecyclerViewPoolmCachedViews的优先级高于RecyclerViewPool。回收时,最新的ViewHolder放在mCachedViews中。如果满了,那就去掉一个扔到ViewPool中,腾出空间来缓存最新的ViewHolder。复用时也是先在mCachedViews中。寻找ViewHolder,但是需要各种匹配条件。综上所述,只有原位置的卡位才能复用mCachedViews中存储的ViewHolder。如果没有mCachedViews,那就去ViewPool里找。ViewPool中的ViewHolder和新的ViewHolder是一样的,只要类型一样,如果找到了,可以取出来重用,重新绑定数据即可。