概要
RecyclerView是Android开发中一个至关重要的UI控件,在日常项目的业务开发中无处不在,功能也极其强大。子View不同逻辑解耦,view回收复用高性能,易用性体现在局部刷新、item动画,拖拽测滑等,基本能替代ListView所有功能(但也并不能完全替代ListView,ListView并没有被标记为@Deprecated,关于替换的必要性可以参考【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析–缓存机制)。RecyclerView核心优势是缓存机制的设计,本文以RecyclerView缓存原理为主线,部分源码进行分析,从RecyclerView的缓存结构,缓存管理以及缓存使用等方面进行展开。
RecylerView缓存的简单梳理,RecylerView中一共有五种缓存,分别是:
- mScrapView
- mAttachedScrap
- mCachedViews
- mViewCacheExtension
- mRecyclerPool
其中前两种mScrapView、mAttachedScrap并不对外暴露,真正开发中能控制或自定义的是后三种mCachedViews、mViewCacheExtension和mRecyclerPool,所以在学习RecyclerView缓存原理的过程中,建议的方向是:理解前两种的作用以及相关源码,理解后三者的作用、源码并掌握实际用法。在阅读理解过程中结合实践对关键方法和变量进行跟踪debug,会更快的掌握整个知识体系。
注:本文引用的RecyclerView相关源码为最新api 29(Android Q),recyclerView-v7版本29.0.0(即最新sdk版本29.0.0)包下的源代码,查看最新源码需要最新测试版编译器Android Studio 3.5 Beta 4,具体在as配置文件中引用为:
dependencies {
...
implementation 'com.android.support:recyclerview-v7:29.0.0'
...
}
RecyclerView缓存结构
RecyclerView缓存本质上指的ViewHolder缓存,下面是源码中五种缓存变量的数据结构:
- mChangedScrap
ArrayList<ViewHolder> mChangedScrap = null;
- mAttachedScrap
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
- mCachedViews
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
- mViewCacheExtension
ViewCacheExtension是一个abstranct类,暴露给应用层实现,只有一个abstract的getViewForPositionAndType方法需要覆写。
private ViewCacheExtension mViewCacheExtension;
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
* <p>
* When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
* first level cache to find a matching View. If it cannot find a suitable View, Recycler will
* call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
* {@link RecycledViewPool}.
* <p>
* Note that, Recycler never sends Views to this method to be cached. It is developers
* responsibility to decide whether they want to keep their Views in this custom cache or let
* the default recycling policy handle it.
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
* <p>
* This method should <b>not</b> create a new View. Instead, it is expected to return
* an already created View that can be re-used for the given type and position.
* If the View is marked as ignored, it should first call
* {@link LayoutManager#stopIgnoringView(View)} before returning the View.
* <p>
* RecyclerView will re-bind the returned View to the position if necessary.
*
* @param recycler The Recycler that can be used to bind the View
* @param position The adapter position
* @param type The type of the View, defined by adapter
* @return A View that is bound to the given position or NULL if there is no View to re-use
* @see LayoutManager#ignoreView(View)
*/
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}
- mRecyclerPool
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
* <p>
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
* <p>
* RecyclerView automatically creates a pool for itself if you don't provide one.
*/
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* Tracks both pooled holders, as well as create/bind timing metadata for the given type.
*
* Note that this tracks running averages of create/bind time across all RecyclerViews
* (and, indirectly, Adapters) that use this pool.
*
* 1) This enables us to track average create and bind times across multiple adapters. Even
* though create (and especially bind) may behave differently for different Adapter
* subclasses, sharing the pool is a strong signal that they'll perform similarly, per type.
*
* 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return
* false for all other views of its type for the same deadline. This prevents items
* constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch.
*/
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
...
/**
* Sets the maximum number of ViewHolders to hold in the pool before discarding.
*
* @param viewType ViewHolder Type
* @param max Maximum number
*/
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
/**
* Returns the current number of Views held by the RecycledViewPool of the given view type.
*/
public int getRecycledViewCount(int viewType) {
return getScrapDataForType(viewType).mScrapHeap.size();
}
/**
* Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
* present.
*
* @param viewType ViewHolder type.
* @return ViewHolder of the specified type acquired from the pool, or {@code null} if none
* are present.
*/
@Nullable
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList