RecyclerView是我们开发中最常用的控件,RecyclerView是如何工作的,如何处理缓存的有助于解决一些使用RecyclerView的bug和优化RecyclerView

准备源码

分析源码之前我们需要先准备一份源码。

  1. 按照官方文档下载一份support源码

  2. 用AndroidStudio打开源码

1
2
3
#进入support目录
cd androidx-master-dev/frameworks/support
./studiow
  1. 我们可以在源码中添加一些日志

  2. 打包

1
./gradlew createArchive
  1. 创建一个工程,在项目的build.gradle文件的 “repositories"属性的顶部加上以下内容。这样项目中依赖的RecyclerView就是你添加了日志的RecyclerView
1
maven { url '/Volumes/android/androidx-master-dev/out/androidx/build/support_repo/' }

RecyclerView

RecyclerView内部定义了如下几个内部类。

我们接下来将逐个分析每个内部类的主要功能,和一些常用的方法。后面再把各个部分串起来看他们是如何工作的。

Adapter

Adapter是一个典型的适配器模式。负责将数据转换成ItemView,然后添加到RecyclerView

常用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);

public abstract void onBindViewHolder(@NonNull VH holder, int position);

public int getItemViewType(int position) {
  return 0;
}

* @see #notifyItemChanged(int)
* @see #notifyItemInserted(int)
* @see #notifyItemRemoved(int)
* @see #notifyItemRangeChanged(int, int)
* @see #notifyItemRangeInserted(int, int)
* @see #notifyItemRangeRemoved(int, int)
*/
public final void notifyDataSetChanged() {
  mObservable.notifyChanged();
}

ViewHolder

在使用ListView的时候我们都使用过ViewHolder.ViewHolder主要是为了防止多次findViewById.

LayoutManager

A LayoutManager is responsible for measuring and positioning item views within a RecyclerView as well as determining the policy for when to recycle item views that are no longer visible to the user. By changing the LayoutManager a RecyclerView can be used to implement a standard vertically scrolling list,a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock layout managers are provided for general use.

LayoutManager负责测量和定位RecyclerView中的项目视图,以及决定何时回收用户不再可见的项目视图。通过改变LayoutManager,一个RecyclerView可以被用来实现一个标准的垂直滚动列表,一个统一的网格,交错的网格,水平滚动的集合等等。系统提供了几种常用的布局管理器。

ItemDecoration

ItemDecoration顾名思义就是用来对Item进行装饰的。我们最常用的就是为每个Item添加分割线。

ItemAnimator

ItemAnimator定义了当适配器发生变化时的动画。我们可以调用RecyclerView的setItemAnimator方法设置ItemAnimator.系统为我们默认设置了一个DefaultItemAnimator

Recycler

Recycler的官方解释如下:

A Recycler is responsible for managing scrapped or detached item views for reuse.

Recycler负责管理废弃或拆分的item views,以便再利用。

A “scrapped” view is a view that is still attached to its parent RecyclerView but that has been marked for removal or reuse.

一个 ”废弃“的view是一个仍然附着在它的父RecyclerView上的view,但它已经被标记为移除或重用。

Typical use of a Recycler by a LayoutManager will be to obtain views for an adapter’s data set representing the data at a given position or item ID. If the view to be reused is considered “dirty” the adapter will be asked to rebind it. If not, the view can be quickly reused by the LayoutManager with no further work. Clean views that have not may be repositioned by a LayoutManager without remeasurement.

LayoutManagerRecycler的典型使用是为适配器的数据集获取给定位置或item ID的数据的视图。如果要重用的视图被认为是 “脏的”,适配器将被要求重新绑定它。如果不是,该视图可以被LayoutManager快速重用,而无需进一步的工作。没有的干净视图可以由LayoutManager重新定位,而无需重新测量。

简单来说Recycler用来缓存和从缓存中获取ViewHoder的。Recycler中定义了如下几个list来存放缓存:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
 //存放废弃的view
 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
 //存放改变的废弃的view
 ArrayList<ViewHolder> mChangedScrap = null;
 //缓存view
 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
 //最多缓存个数
 int mViewCacheMax = DEFAULT_CACHE_SIZE;
 //默认缓存为2
 static final int DEFAULT_CACHE_SIZE = 2;
 //更新缓存大小
void updateViewCacheSize() {
    int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
    mViewCacheMax = mRequestedCacheMax + extraCache;

    // first, try the views that can be recycled
    for (int i = mCachedViews.size() - 1;
            i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
        recycleCachedViewAt(i);
    }
}

查找这几个list的addremove以及clear方法调用。

mCachedViewsaddremove以及clear方法调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//从mCachedViews中移除
void recycleCachedViewAt(int cachedViewIndex) {
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    //从mCachedViews中移除添加到RecycledViewPool
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}
//添加到RecycledViewPool
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);
    View itemView = holder.itemView;
    //
    holder.mBindingAdapter = null;
    holder.mOwnerRecyclerView = null;
    //调用RecycledViewPool的putRecycledView方法
    getRecycledViewPool().putRecycledView(holder);
}
//添加到mCachedViews中
void recycleViewHolderInternal(ViewHolder holder) {
  if (forceRecycle || holder.isRecyclable()) {
      //存入到mCachedViews中
      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();
          //当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;
      }
      //不满足条件的时候存入RecyclerViewPool
      if (!cached) {
          addViewHolderToRecycledViewPool(holder, true);
          recycled = true;
      }
  } else {
      // NOTE: A view can fail to be recycled when it is scrolled off while an animation
      // runs. In this case, the item is eventually recycled by
      // ItemAnimatorRestoreListener#onAnimationFinished.

      // TODO: consider cancelling an animation when an item is removed scrollBy,
      // to return it to the pool faster
  }
}
//
void recycleAndClearCachedViews() {
    final int count = mCachedViews.size();
    for (int i = count - 1; i >= 0; i--) {
        recycleCachedViewAt(i);
    }
    mCachedViews.clear();
    //...
}

mAttachedScrapmChangedScrapaddremove以及clear方法调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
  * Mark an attached view as scrap.
  *
  * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
  * for rebinding and reuse. Requests for a view for a given position may return a
  * reused or rebound scrap view instance.</p>
  *
  * @param view View to scrap
  */
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集合中
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}
void clearScrap() {
    mAttachedScrap.clear();
    if (mChangedScrap != null) {
        mChangedScrap.clear();
    }
}
/**
  * Remove a previously scrapped view from the pool of eligible scrap.
  *
  * <p>This view will no longer be eligible for reuse until re-scrapped or
  * until it is explicitly removed and recycled.</p>
  */
void unscrapView(ViewHolder holder) {
    if (holder.mInChangeScrap) {
        mChangedScrap.remove(holder);
    } else {
        mAttachedScrap.remove(holder);
    }
    holder.mScrapContainer = null;
    holder.mInChangeScrap = false;
    holder.clearReturnedFromScrapFlag();
}

RecycledViewPool

RecycledViewPool lets you share Views between multiple RecyclerViews.

RecycledViewPool允许多个RecyclerView共享View.

If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool and use RecyclerView#setRecycledViewPool(RecycledViewPool).

如果你想在不同的RecyclerViews中循环使用视图。创建一个RecycledViewPool的实例并传递给RecyclerView#setRecycledViewPool(RecycledViewPool)

RecyclerView automatically creates a pool for itself if you don’t provide one.

如果你不提供一个RecycledViewPoolRecyclerView会自动为自己创建一个。

默认RecycledViewPoolRecyclergetRecycledViewPool方法中创建。

1
2
3
4
5
6
RecycledViewPool getRecycledViewPool() {
  if (mRecyclerPool == null) {
      mRecyclerPool = new RecycledViewPool();
  }
  return mRecyclerPool;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//缓存池
SparseArray<ScrapData> mScrap = new SparseArray<>();

static class ScrapData {
    final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;
    long mCreateRunningAverageNs = 0;
    long mBindRunningAverageNs = 0;
}
//存入缓存
public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    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);
}
//从缓存中获取ViewHolder
/**
  * 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<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        for (int i = scrapHeap.size() - 1; i >= 0; i--) {
            if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
                return scrapHeap.remove(i);
            }
        }
    }
    return null;
}