探究RecyclerView的ViewHolder复用
Iesha70Q
8年前
<h3>啥是RecyclerView</h3> <ul> <li> <p>A flexible view for providing a limited window into a large data set.</p> <p>一个在大小有限的窗口内展示大量数据集的view。恩,我的翻译一向不咋滴。。所以原文也放上了。</p> <p>RecyclerView网上很多文都说是用来取代ListView和GridView的,事实上RecyclerView的确可以做到ListView和GridView能做的事,而且他将ViewHolder和Adapter都作为内部类,写在了RecyclerView中。先不管这把所有类都写在RecyclerView内部的做法是否好,ViewHolder作为复用的单位,避免了不必要的findViewById。</p> </li> </ul> <h3>一个使用RecyclerView的示例</h3> <p>在进行探究之前,首先回顾一下我们是如何使用一个RecyclerView的。</p> <p>第一步在布局文件里加上RecyclerView:</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/rv_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout></code></pre> <p>第二部,给RecyclerView的item编写布局:</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" android:padding="8dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/test" /> </LinearLayout></code></pre> <p>第三步,为RecyclerView写一个Adapter:</p> <pre> <code class="language-java">package com.xiasuhuei321.test; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.support.v7.widget.RecyclerView.ViewHolder; /** * Created by xiasuhuei321 on 2016/12/25. * author:luo * e-mail:xiasuhuei321@163.com */ public class TestAdapter extends RecyclerView.Adapter { Context mContext; public TestAdapter(Context context) { this.mContext = context; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ItemViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_test, parent, false)); } @Override public int getItemCount() { return 100; } @Override public void onBindViewHolder(ViewHolder holder, int position) { } class ItemViewHolder extends ViewHolder { public ItemViewHolder(View itemView) { super(itemView); } } }</code></pre> <p>这里只是简单的演示,代码写的都非常的简单。。各位都不要模仿。。。</p> <p>第四步,给RecyclerView设置对应的布局和Adapter:</p> <pre> <code class="language-java">package com.xiasuhuei321.test; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; /** * Created by xiasuhuei321 on 2016/12/25. * author:luo * e-mail:xiasuhuei321@163.com */ public class TestActivity extends AppCompatActivity { private RecyclerView mList; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); mList = (RecyclerView) findViewById(R.id.rv_list); mList.setLayoutManager(new LinearLayoutManager(this)); mList.setAdapter(new TestAdapter(this)); } }</code></pre> <p>最后看下效果</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a20fe8781ff94c2288bc53f7a7292734.png"></p> <p>通过以上的流程,对RecyclerView的简单使用就过完了,在这个流程中,可以看出编写Adapter是一个关键,事实上RecyclerView和ListView都一样,都是通过Adapter来设置和管理每一个item的。</p> <h3>ViewHolder与复用</h3> <p>在复写RecyclerView.Adapter的时候,需要我们复写两个方法:</p> <ul> <li>onCreateViewHolder</li> <li>onBindViewHolder</li> </ul> <p>这两个方法从字面上看就是创建ViewHolder和绑定ViewHolder的意思,来看一下源码中对我们实现的这两个方法的注释:</p> <pre> <code class="language-java">/** * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent * an item. * <p> * This new ViewHolder should be constructed with a new View that can represent the items * of the given type. You can either create a new View manually or inflate it from an XML * layout file. * <p> * The new ViewHolder will be used to display items of the adapter using * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display * different items in the data set, it is a good idea to cache references to sub views of * the View to avoid unnecessary {@link View#findViewById(int)} calls. * * @param parent The ViewGroup into which the new View will be added after it is bound to * an adapter position. * @param viewType The view type of the new View. * * @return A new ViewHolder that holds a View of the given view type. * @see #getItemViewType(int) * @see #onBindViewHolder(ViewHolder, int) */</code></pre> <p>当RecyclerView需要一个新的类型的item的ViewHolder的时候调用这个方法。</p> <p>第二段描述是讲如何创建这个ViewHolder,跳过。</p> <p>新的ViewHolder将会被用来通过adapter调用onBindViewHolder展示item。由于它将会被复用去展示在数据集中的不同items,所以缓存View的子view引用去避免不必要的对findViewById方法的调用是一个好主意。</p> <p>看了上面的这段话,我产生了一个疑问,第一段话的意思仿佛是只有在需要新的类型的ViewHolder的时候才需要调用这个方法。如果是这样,的确可以从侧面说明他是以ViewHolder为单位来实现复用的。为了验证我的想法,我在onCreateViewHolder和onBindViewHolder方法中加入了计数的代码,看一下log:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/c14c5116865da5d50b059daf1cea61f1.png"></p> <p style="text-align:center">log</p> <p>从中可以看出并不是像我想的那样,只调用了一次,稍微想一下也很容易想明白,因为他是通过ViewHolder复用不假,我这里只有一种ViewType,上下滑动的时候需要的ViewHolder种类是只有一种,但是需要的ViewHolder对象数量并不止一个。所以在后面创建了5个ViewHolder之后,需要的数量够了,无论我怎么滑动,他都只需要复用以前创建的对象就行了。</p> <p>在这里,感觉ViewHolder的类型和对象数量有点像Java中Class和对象的关系。Java中第一次将.class装载入JVM虚拟机的时候,会生成一个Class对象,以后所有这个类的对象都由Class生成。是不是有点像呢?</p> <p>看到了这个log之后,我的第一反应是在这个ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,看一下源码:</p> <pre> <code class="language-java">/** * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. * * @see #onCreateViewHolder(ViewGroup, int) */ public final VH createViewHolder(ViewGroup parent, int viewType) { TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; TraceCompat.endSection(); return holder; }</code></pre> <p>可以发现这里并没有限制,那么是不是在调用这个createViewHolder方法的时候做了限制呢?</p> <pre> <code class="language-java">View getViewForPosition(int position, boolean dryRun) { if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount()); } boolean fromScrap = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; } // 1) Find from scrap by position if (holder == null) { holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { // recycle this scrap if (!dryRun) { // we would like to recycle this but need to make sure it is not used by // animation logic etc. holder.addFlags(ViewHolder.FLAG_INVALID); if (holder.isScrap()) { removeDetachedView(holder.itemView, false); holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { fromScrap = true; } } } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount()); } final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrap = true; } } if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); if (holder == null) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view which does not have a ViewHolder"); } else if (holder.shouldIgnore()) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before" + " returning this view."); } } } if (holder == null) { // fallback to recycler // try recycler. // Head to the shared pool. if (DEBUG) { Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " + "pool"); } holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null) { holder = mAdapter.createViewHolder(RecyclerView.this, type); if (DEBUG) { Log.d(TAG, "getViewForPosition created new ViewHolder"); } } } // This is very ugly but the only place we can grab this information // before the View is rebound and returned to the LayoutManager for post layout ops. // We don't need this in pre-layout since the VH is not updated by the LM. if (fromScrap && !mState.isPreLayout() && holder .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); if (mState.mRunSimpleAnimations) { int changeFlags = ItemAnimator .buildAdapterChangeFlagsForAnimations(holder); changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, holder, changeFlags, holder.getUnmodifiedPayloads()); recordAnimationInfoIfBouncedHiddenView(holder, info); } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); holder.mOwnerRecyclerView = RecyclerView.this; mAdapter.bindViewHolder(holder, offsetPosition); attachAccessibilityDelegate(holder.itemView); bound = true; if (mState.isPreLayout()) { holder.mPreLayoutPosition = position; } } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrap && bound; return holder.itemView; }</code></pre> <p>可以看出的确是有条件的。当然,在此不具体分析,不然可能会深入细节无法自拔。</p> <h3>Recycler && RecycledViewPool</h3> <p>说实话,上面分析完,我也有点没方向,因为毕竟整个RecyclerView一万多行代码在那,不知道看哪了,不过好在网上有篇文曾干过和我差不多的事 RecyclerView源码分析 ,前人指了条路,跟着看一下源码好了。</p> <p>Recycler:</p> <pre> <code class="language-java">/** * A Recycler is responsible for managing scrapped or detached item views for reuse. * * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but * that has been marked for removal or reuse.</p> * * <p>Typical use of a Recycler by a {@link 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 {@link android.view.View#isLayoutRequested() requested layout} * may be repositioned by a LayoutManager without remeasurement.</p> */</code></pre> <p>Recycler负责管理废弃(scrapped)或者分离(detach)的item。</p> <p>scrapped指的是仍然在RecyclerView上但是已经被标记了移除或者复用。</p> <p>一个对Recycler的经典的使用时LayoutManager,它通过Recycler为adapter的数据集的特定位置获取一个view。如果这个view将被复用但被认为是“dirty”的,那么这个adapter将调用方法重新绑定它。如果不是,这个view可以迅速的被LayoutManager复用而不用进一步的处理。Clean view无需调用request layout,不需要重新测量就能复用。</p> <p>RecycledViewPool:</p> <pre> <code class="language-java">/** * 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 SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>(); private SparseIntArray mMaxScrap = new SparseIntArray(); private int mAttachCount = 0; private static final int DEFAULT_MAX_SCRAP = 5; public void clear() { mScrap.clear(); } public void setMaxRecycledViews(int viewType, int max) { mMaxScrap.put(viewType, max); final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); if (scrapHeap != null) { while (scrapHeap.size() > max) { scrapHeap.remove(scrapHeap.size() - 1); } } } public ViewHolder getRecycledView(int viewType) { final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); if (scrapHeap != null && !scrapHeap.isEmpty()) { final int index = scrapHeap.size() - 1; final ViewHolder scrap = scrapHeap.get(index); scrapHeap.remove(index); return scrap; } return null; } int size() { int count = 0; for (int i = 0; i < mScrap.size(); i ++) { ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i); if (viewHolders != null) { count += viewHolders.size(); } } return count; } public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); final ArrayList scrapHeap = getScrapHeapForType(viewType); if (mMaxScrap.get(viewType) <= scrapHeap.size()) { return; } if (DEBUG && scrapHeap.contains(scrap)) { throw new IllegalArgumentException("this scrap item already exists"); } scrap.resetInternal(); scrapHeap.add(scrap); } void attach(Adapter adapter) { mAttachCount++; } void detach() { mAttachCount--; } /** * Detaches the old adapter and attaches the new one. * <p> * RecycledViewPool will clear its cache if it has only one adapter attached and the new * adapter uses a different ViewHolder than the oldAdapter. * * @param oldAdapter The previous adapter instance. Will be detached. * @param newAdapter The new adapter instance. Will be attached. * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same * ViewHolder and view types. */ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious) { if (oldAdapter != null) { detach(); } if (!compatibleWithPrevious && mAttachCount == 0) { clear(); } if (newAdapter != null) { attach(newAdapter); } } private ArrayList<ViewHolder> getScrapHeapForType(int viewType) { ArrayList<ViewHolder> scrap = mScrap.get(viewType); if (scrap == null) { scrap = new ArrayList<>(); mScrap.put(viewType, scrap); if (mMaxScrap.indexOfKey(viewType) < 0) { mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); } } return scrap; } }</code></pre> <p>老规矩,先看注释:RecycledViewPool让你在多个RecyclerView之间共享View。如果你想要在RecyclerView间循环利用view,创建一个RecyclerViewPool的实例然后调用setRecyclerViewPool方法。</p> <p>如果你不提供一个那么RecyclerView将为他自己自动的创建一个RecycledViewPool。</p> <p>接下来看下里面的代码,内部有一个SparseArray一个SparseIntArray,看到这终于感觉快看到点子上了,毕竟看起来就像是两个放东西的容器,应该是离真相不远了。先看下SparseArray是个啥,点进去看注释第一句就是 SparseArrays map integers to Objects ,这是一个integer和对象的映射,他内部也是有两个数组一个是integer作为键的int[] mKeys的int类型的数组,另外一个是Object[] mValues的对象数组。而结合他在RecycledViewPool中的定义 SparseArray<ArrayList<ViewHolder>> mScrap;这种定义,表明是一个integer映射ViewHolder的集合。这个该怎么理解呢?在我们的实际使用中,很可能会有非常多种类的viewType,那么这个时候同一类的ViewHolder就保存在同一个ArrayList中,而在RecyclerView内部ViewType都是通过int类型的数字来代表的,正好符合。由此可以大致可以确定这个mScrap就是保存ViewHolder的关键了。</p> <p>而SparseIntArray则是Integer映射Integer,在这可以结合setMaxRecycledViews方法中的第一行代码 mMaxScrap.put(viewType,max),可以看出这是表明了一种ViewType对应的可保存对象集合的最大尺寸。</p> <p>大致了解了下RecycledViewPool,然后回头去看一下之前被我跳过的getViewForPosition:</p> <pre> <code class="language-java">View getViewForPosition(int position, boolean dryRun) { if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount()); } boolean fromScrap = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; } // 1) Find from scrap by position if (holder == null) { holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { // recycle this scrap if (!dryRun) { // we would like to recycle this but need to make sure it is not used by // animation logic etc. holder.addFlags(ViewHolder.FLAG_INVALID); if (holder.isScrap()) { removeDetachedView(holder.itemView, false); holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { fromScrap = true; } } } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount()); } final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrap = true; } } if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); if (holder == null) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view which does not have a ViewHolder"); } else if (holder.shouldIgnore()) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before" + " returning this view."); } } } if (holder == null) { // fallback to recycler // try recycler. // Head to the shared pool. if (DEBUG) { Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " + "pool"); } holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null) { holder = mAdapter.createViewHolder(RecyclerView.this, type); if (DEBUG) { Log.d(TAG, "getViewForPosition created new ViewHolder"); } } } // This is very ugly but the only place we can grab this information // before the View is rebound and returned to the LayoutManager for post layout ops. // We don't need this in pre-layout since the VH is not updated by the LM. if (fromScrap && !mState.isPreLayout() && holder .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); if (mState.mRunSimpleAnimations) { int changeFlags = ItemAnimator .buildAdapterChangeFlagsForAnimations(holder); changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, holder, changeFlags, holder.getUnmodifiedPayloads()); recordAnimationInfoIfBouncedHiddenView(holder, info); } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); holder.mOwnerRecyclerView = RecyclerView.this; mAdapter.bindViewHolder(holder, offsetPosition); attachAccessibilityDelegate(holder.itemView); bound = true; if (mState.isPreLayout()) { holder.mPreLayoutPosition = position; } } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrap && bound; return holder.itemView; }</code></pre> <p>在具体分析这个方法之前,先给出这个类内部几个参数的大致意思:</p> <p>private ArrayList<ViewHolder> mAttachedScrap</p> <p>private ArrayList<ViewHolder> mChangedScrap 与RecyclerView分离的ViewHolder列表。</p> <p>private ArrayList<ViewHolder> mCachedViews ViewHolder缓存列表。</p> <p>private ViewCacheExtension mViewCacheExtension 开发者控制的ViewHolder缓存</p> <p>private RecycledViewPool mRecyclerPool 提供复用ViewHolder池。</p> <p>可以看到源码中已经给了我们步骤提示:</p> <ul> <li>If there is a changed scrap, try to find from there<br> 从mChangedScrap中寻找ViewHolder</li> <li>1) Find from scrap by position<br> 如果上一步未找到ViewHolder,则从mAttachedScrap中通过position找</li> <li>2) Find from scrap via stable ids, if exists<br> 如果上一步未找到且存在stable id,则通过id在mAttachedScrap中找ViewHolder</li> <li>如果上一步未找到且mViewCacheExtension不为空,则在mViewCacheExtension中找ViewHolder</li> <li>如果上一步未找到则通过RecycledCiewPool寻找ViewHolder</li> <li>如果上一步未找到则通过Adapter的createViewHolder创建一个新的ViewHolder</li> </ul> <p>如此一来经历了以上的步骤,一个ViewHolder便会先从缓存中读取,如果都无法匹配到,则会新创建一个。如此便实现了ViewHolder的复用。</p> <h3> </h3> <p> </p> <p>来自:http://www.jianshu.com/p/d7ec36aa8e4b</p> <p> </p>