最近用到RecyclerView,想研究RecyclerView和ListView复用机制的区别,这篇文章以解析源码的方式解析ListView复用机制。


ListView复用是通过AbsListView的RecycleBin内部类来实现的,源码注释如下:

/**     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the     * start of a layout. By construction, they are displaying current information. At the end of     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that     * could potentially be used by the adapter to avoid allocating views unnecessarily.     */
RecycleBin是2级的存储结构,

ActiveViews: 当前屏幕上的活动View

ScrapViews: 废弃View,可复用的旧View

再看RecycleBin的成员变量

//回收Listener,当View变为可回收,即ScrapView时,会通过mRecyclerListener通知注册者,listener可通过setRecyclerListener注册private RecyclerListener mRecyclerListener;/** * The position of the first view stored in mActiveViews. */// 第一个活动view的position,即第一个可视view的positionprivate int mFirstActivePosition;/** * Views that were on screen at the start of layout. This array is populated at the start of * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. * Views in mActiveViews represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */// 活动view的集合private View[] mActiveViews = new View[0];/** * Unsorted views that can be used by the adapter as a convert view. *//**废弃的可修复view集合,复用时传递到Adapter#getView方法的convertView参数。 * 因为item type可能大于1,只有view type相同的view之间才能复用,所以是个二维数组 */private ArrayList[] mScrapViews;// ListView item type数量private int mViewTypeCount;// 当前的废弃view数组,定义这个成员是为了在mViewTypeCount为1时使用方便,不需要去取mScrapViews的第一个元素private ArrayList mCurrentScrap;// 被跳过的,不能复用的view集合。view type小于0或者处理transient状态的view不能被复用。private ArrayList mSkippedScrap;// 处于transient状态的view集合,处于transient状态的view不能被复用,如view的动画正在播放,// transient是瞬时、过渡的意思,关于transient状态详见android.view.View#PFLAG2_HAS_TRANSIENT_STATEprivate SparseArray mTransientStateViews;// 如果adapter的hasStableIds方法返回true,处于过度状态的view保存到这里。因为需要保存view的position,而且处于过度状态的view一般很少,// 这2个成员用了稀疏数组。具体不需要case,知道是保存转换状态view的集合就行。private LongSparseArray mTransientStateViewsById;


从RecycleBin成员变量的定义基本可以看出复用的原理:

1. 废弃的view保存在一个数组中,复用时从中取出

2. 拥有相同view type的view之间才能复用,所以mScrapViews是个二维数组

3. 处于transient状态的view不能被复用

再来看方法

setViewTypeCount: 这个方法在给ListView设置adapter时调用,取值是adapter#getViewTypeCount()
public void setViewTypeCount(int viewTypeCount) {                                           if (viewTypeCount < 1) {                                                                    throw new IllegalArgumentException("Can't have a viewTypeCount < 1");               }                                                                                       //noinspection [] scrapViews = new ArrayList[viewTypeCount];                            for (int i = 0; i < viewTypeCount; i++) {                                                   scrapViews[i] = new ArrayList();                                              }                                                                                       mViewTypeCount = viewTypeCount;                                                         mCurrentScrap = scrapViews[0];                                                          mScrapViews = scrapViews;                                                           }                                                                                       

markChildrenDirty: 当ListView size或position变化时,设置mScrapViews和transient views的forceLayout flag,在下一次被复用时会重新布置。
public void markChildrenDirty() {                                   if (mViewTypeCount == 1) {                                          final ArrayList scrap = mCurrentScrap;                    final int scrapCount = scrap.size();                            for (int i = 0; i < scrapCount; i++) {                              scrap.get(i).forceLayout();                                 }                                                           } else {                                                            final int typeCount = mViewTypeCount;                           for (int i = 0; i < typeCount; i++) {                               final ArrayList scrap = mScrapViews[i];                   final int scrapCount = scrap.size();                            for (int j = 0; j < scrapCount; j++) {                              scrap.get(j).forceLayout();                                 }                                                           }                                                           }                                                               if (mTransientStateViews != null) {                                 final int count = mTransientStateViews.size();                  for (int i = 0; i < count; i++) {                                   mTransientStateViews.valueAt(i).forceLayout();              }                                                           }                                                               if (mTransientStateViewsById != null) {                             final int count = mTransientStateViewsById.size();              for (int i = 0; i < count; i++) {                                   mTransientStateViewsById.valueAt(i).forceLayout();          }                                                           }                                                           }                                                               

shouldRecycleViewType: 判断有当前viewType的view是否应该被回收复用,viewType小于0的不复用
public boolean shouldRecycleViewType(int viewType) {       return viewType >= 0;                              }                                                      

fillActiveViews: 将屏幕上所有活动view填充到mActiveViews中,childCount为需保存的最小view个数,即当前屏幕上ListView所展示的view数,注意不是adapter的item数。 firstActivePosition为第一个可视view的position,即view在adapter中的
/**                                                                                          * Fill ActiveViews with all of the children of the AbsListView.                             *                                                                                           * @param childCount The minimum number of views mActiveViews should hold                    * @param firstActivePosition The position of the first view that will be stored in          *        mActiveViews                                                                       */                                                                                         void fillActiveViews(int childCount, int firstActivePosition) {                                 if (mActiveViews.length < childCount) {                                                         mActiveViews = new View[childCount];                                                    }                                                                                           mFirstActivePosition = firstActivePosition;                                                                                                                                             //noinspection MismatchedReadAndWriteOfArray                                                final View[] activeViews = mActiveViews;                                                    for (int i = 0; i < childCount; i++) {                                                          View child = getChildAt(i);                                                                 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();           // Don't put header or footer views into the scrap heap                                     if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {                             // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.                    //        However, we will NOT place them into scrap views.                                 activeViews[i] = child;                                                                     // Remember the position so that setupChild() doesn't reset state.                          lp.scrappedFromPosition = firstActivePosition + i;                                      }                                                                                       }                                                                                       }                                                                    

getActiveView: 查找mActiveView中指定position的view,找到后将从mActiveViews中移除
/**                                                                                      * Get the view corresponding to the specified position. The view will be removed from   * mActiveViews if it is found.                                                          *                                                                                       * @param position The position to look up in mActiveViews                               * @return The view if it is found, null otherwise                                       */                                                                                     View getActiveView(int position) {                                                          int index = position - mFirstActivePosition;                                            final View[] activeViews = mActiveViews;                                                if (index >=0 && index < activeViews.length) {                                              final View match = activeViews[index];                                                  activeViews[index] = null;                                                              return match;                                                                       }                                                                                       return null;                                                                        }                                                                    

getScrapView: 查找复用的view,先通过position从adapter中找到view type,然后再从相应的scrap中查找
/**                                                                    * @return A view from the ScrapViews collection. These are unordered. */                                                                   View getScrapView(int position) {                                         final int whichScrap = mAdapter.getItemViewType(position);            if (whichScrap < 0) {                                                     return null;                                                      }                                                                     if (mViewTypeCount == 1) {                                                return retrieveFromScrap(mCurrentScrap, position);                } else if (whichScrap < mScrapViews.length) {                             return retrieveFromScrap(mScrapViews[whichScrap], position);      }                                                                     return null;                                                      }                                                                    

retrieveFromScrap: 从指定的scrap中查找可复用的view,分别3个优先级: 1. 如adapter有stable ids,则先比较id 2. 如adapter无stable ids,则查找是否有在当前position被回收利用的view 3. 如优先级1,2都没查找到,则取最近加入scrap中的view
private View retrieveFromScrap(ArrayList scrapViews, int position) {          final int size = scrapViews.size();                                             if (size > 0) {                                                                     // See if we still have a view for this position or ID.                         for (int i = 0; i < size; i++) {                                                    final View view = scrapViews.get(i);                                            final AbsListView.LayoutParams params =                                                 (AbsListView.LayoutParams) view.getLayoutParams();                                                                                                      if (mAdapterHasStableIds) {                                                         final long id = mAdapter.getItemId(position);                                   if (id == params.itemId) {                                                          return scrapViews.remove(i);                                                }                                                                           } else if (params.scrappedFromPosition == position) {                               final View scrap = scrapViews.remove(i);                                        clearAccessibilityFromScrap(scrap);                                             return scrap;                                                               }                                                                           }                                                                               final View scrap = scrapViews.remove(size - 1);                                 clearAccessibilityFromScrap(scrap);                                             return scrap;                                                               } else {                                                                            return null;                                                                }                                                                           }                                                                               

addScrapView: 将可复用的view加入scrap数组中
/**                                                                                              * Puts a view into the list of scrap views.                                                     * 

* If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * * @param scrap The view to add * @param position The view's position within its parent */ void addScrapView(View scrap, int position) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { // Can't recycle, but we don't know anything about the view. // Ignore it completely. return; } lp.scrappedFromPosition = position; // Remove but don't scrap header or footer views, or views that // should otherwise not be recycled. final int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { //不能被复用的view type,并且不为header或footer时加入skipped scrap // Can't recycle. If it's not a header or footer, which have // special handling and should be ignored, then skip the scrap // heap and we'll fully detach the view later. if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { getSkippedScrap().add(scrap); } return; } scrap.dispatchStartTemporaryDetach(); // The the accessibility state of the view may change while temporary // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { //view处于transient状态 if (mAdapter != null && mAdapterHasStableIds) { //adpater has stable ids,加入mTransientStateViewsById中 // If the adapter has stable IDs, we can reuse the view for // the same data. if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap); } else if (!mDataChanged) { //数据未变化,加入mTransientStateViews中 // If the data hasn't changed, we can reuse the views at // their old positions. if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<>(); } mTransientStateViews.put(position, scrap); } else { //adapter为null或adpater不为null,无stable ids,且数据变化,则丢弃scrap view // Otherwise, we'll have to remove the view and start over. getSkippedScrap().add(scrap); } } else { //不处于transient状态,将scrap view加入mScarpViews中 if (mViewTypeCount == 1) { //view type count为1时,为方便,直接操作mCurrentScrap mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }


scrapActiveViews: 将mActiveViews中剩余的所有view移到mScrapViews中
/**                                                                                    * Move all views remaining in mActiveViews to mScrapViews.                            */                                                                                   void scrapActiveViews() {                                                                 final View[] activeViews = mActiveViews;                                              final boolean hasListener = mRecyclerListener != null;                                final boolean multipleScraps = mViewTypeCount > 1;                                                                                                                          ArrayList scrapViews = mCurrentScrap;                                           final int count = activeViews.length;                                                 for (int i = count - 1; i >= 0; i--) {                                                    final View victim = activeViews[i];                                                   if (victim != null) {                                                                     final AbsListView.LayoutParams lp                                                             = (AbsListView.LayoutParams) victim.getLayoutParams();                        final int whichScrap = lp.viewType;                                                                                                                                         activeViews[i] = null;                                                                                                                                                      if (victim.hasTransientState()) {                                                         // Store views with transient state for later use.                                    victim.dispatchStartTemporaryDetach();                                                                                                                                      if (mAdapter != null && mAdapterHasStableIds) {                                           if (mTransientStateViewsById == null) {                                                   mTransientStateViewsById = new LongSparseArray();                           }                                                                                     long id = mAdapter.getItemId(mFirstActivePosition + i);                               mTransientStateViewsById.put(id, victim);                                         } else if (!mDataChanged) {                                                               if (mTransientStateViews == null) {                                                       mTransientStateViews = new SparseArray();                                   }                                                                                     mTransientStateViews.put(mFirstActivePosition + i, victim);                       } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {                               // The data has changed, we can't keep this view.                                     removeDetachedView(victim, false);                                                }                                                                                 } else if (!shouldRecycleViewType(whichScrap)) {                                          // Discard non-recyclable views except headers/footers.                               if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {                                      removeDetachedView(victim, false);                                                }                                                                                 } else {                                                                                  // Store everything else on the appropriate scrap heap.                               if (multipleScraps) {                                                                     scrapViews = mScrapViews[whichScrap];                                             }                                                                                                                                                                           victim.dispatchStartTemporaryDetach();                                                lp.scrappedFromPosition = mFirstActivePosition + i;                                   scrapViews.add(victim);                                                                                                                                                     if (hasListener) {                                                                        mRecyclerListener.onMovedToScrapHeap(victim);                                     }                                                                                 }                                                                                 }                                                                                 }                                                                                                                                                                           pruneScrapViews();                                                                }                                                                                     

剩下的方法由于篇幅所限,不一一贴出源码,在此只对方法的功能做下说明:
//清除所有scrap view和transient viewvoid clear();//获取当前position下的transient view,如无则返回nullView getTransientStateView(int position);//获取mSkippedScrapprivate ArrayList getSkippedScrap();//清除mSkippedScrapvoid removeSkippedScrap();//裁剪mScarpViews,确保size不大于mActiveViews的size;移除已不是transient state的viewprivate void pruneScrapViews();//将所有scrap views放入指定的views中,这个方法没弄明白,不知何时调用void reclaimScrapViews(List views);//为每一个scrap和active view设置缓存背影色void setCacheColorHint(int color);//从ListView层次中移除viewprivate void removeDetachedView(View child, boolean animate);



未完待续




更多相关文章

  1. 初学Android,手机管理器之监听手机来电(六十三)
  2. android与C# WebService基于ksoap通信(Android篇)
  3. Android实现信号强度监听的方法
  4. android中各种组件的生命周期问题
  5. 禁止应用获取手机信息
  6. android中返回页面并刷新
  7. 源码分析android 系统framework(一)之Activity 与 Window 与 View
  8. Android(安卓)TabLayout 不显示标题的解决方法
  9. Android开发指南(39) —— Testing Fundamentals

随机推荐

  1. android快速上手(一)java基本知识学习
  2. Android编程之文件操作
  3. Android EditText属性介绍及监听内容变化
  4. 开机启动流程
  5. Android Handler详解
  6. eclipse 遭遇及解决 debug调试时 因Andro
  7. 记录Android应用程序行为-peachbox
  8. ‍Android(安卓)尺寸和分辨率
  9. Day6 快速学习OkHttp3的九大用法
  10. android webkit CSS3及Render初窥