探究RecyclerView的ViewHolder复用

Iesha70Q 7年前
   <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>