RecyclerView源码解析

596 阅读8分钟
原文链接: chanchangxing.me

RecyclerView渲染流程和获取缓存

RecyclerView也是一个View,也要遵循View的三个步骤。onMeasureonLayoutonDraw.

那么就来照着这个顺序来解剖RecyclerView。

解剖依照的项目是google的android sample

onLayout

RecyclerView中,子View都是在LayoutManger中进行计算大小,布局的。所以其实RecyclerViewonLayout()方法只是一个空壳。这里就可以简化RecyclerViewonLayout()方法。

class RecyclerView {
    void dispatchLayout() { 
        //... 
        mState.mIsMeasuring = false; 
        if (mState.mLayoutStep == State.STEP_START) { 
            // 在RecyclerView中,最重要的就是调用下面这个方法。
            dispatchLayoutStep2(); 
        }
      	
      	// ...
      	
        // step3中间有主要的流程,是关于缓存的,但是我们还没有走到缓存 
        // 所以我们就不看了 
        dispatchLayoutStep3(); 
    } 
}
class RecyclerView {
    private void dispatchLayoutStep2() { 
        //... 
        // 之后就进入最关键的onLayoutChildren。
      	// 也就是从这里开始,layout移交给LayoutManager处理。
        mLayout.onLayoutChildren(mRecycler, mState); 
        // ...
    } 
}
class LinearLayoutManager {
    @Override 
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 
    // layout algorithm: 
    // 1) by checking children and other variables, find an anchor coordinate and an anchor 
    // item position. 
    // 通过检查子项和其他变量,找到锚点坐标和锚点项目位置 
    // 2) fill towards start, stacking from bottom 
    // 从下往上填充,从底部开始堆 
    // 3) fill towards end, stacking from top 
    // 从上往下填充,从顶部开始堆 
    // 4) scroll to fulfill requirements like stack from bottom. 
    // create layout state 
    // 滚动以满足从底部堆栈的要求。创建布局状态 
    // 看到这里我们其实有
    if (mAnchorInfo.mLayoutFromEnd) { 
        //... 
    } else { 
        // 这里有两个fill(),这里要说明一下。
      	// 前面省略的代码有一个锚的概念.
        // 它通过一系列的计算得出自己要的锚在屏幕哪个位置.
        // 然后再在从锚点开始,从锚点到顶,从锚点到底进行layout和draw
        // 这里我们进入fill方法分析一下。
        fill(recycler, mLayoutState, state, false); 
	
      	// ...
      	
        fill(recycler, mLayoutState, state, false); 
        // ... 
    } 
    // ... 
    } 
}
class RecyclerView {
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { 
				
      	// ... 
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { 
          	// 得到的结果就是拿到了adapter的view,
            // 并加入到了RecyclerView,并且已经做了measure和layout
            // 需要进入这个方法进行分析。
            layoutChunk(recycler, state, layoutState, layoutChunkResult); 
            // ...
        } 
        // ... 
        return start - layoutState.mAvailable; 
    } 
}
class LinearLayoutManager {
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { 
        // 这个next()方法就是获取RecyclerView的缓存的ViewHolder的中				 // 的View的方法.
        // 缓存机制分为三层,
      	// 第一层通过CacheView把控,存在这里的ViewHolder不会调用onCreateViewHolder和onBindViewHolder方法。
      	// 第二层通过ViewCacheExtension实现,这层缓存通过用户自定义实现,但是这里有个很奇怪的事情是我并不知道该如何给RecyclerView,因为RecyclerView缓存ViewHolder的时候并没有一个接口导出来。
      	// 第三层通过RecyclerPool实现,RecyclerPool用viewType来存储ViewHolder。
      	// 这里就是获取缓存的重中之重了。那么来分析这个方法
        View view = layoutState.next(recycler); 
       
        // ... 
        LayoutParams params = (LayoutParams) view.getLayoutParams(); 
        if (layoutState.mScrapList == null) { 
            if (mShouldReverseLayout == (layoutState.mLayoutDirection 
            == LayoutState.LAYOUT_START)) { 
                // 这里addView做了两个操作 
                // 将这个view放入ChildHelper的bucket中 
                // 将view加入到RecyclerView中 
                addView(view); 
            } else { 
                
            } 
        } else { 
            // ... 
        } 
        // 后面有一系列的操作,各种计算,都是为了measuer和layout我们得 
        // 到的ViewHolder的view。 
    } 
}
class LinearLayoutManager.LayoutState { 
    View next(RecyclerView.Recycler recycler) { 
        //... 
        // 进入getViewForPosition()方法
        final View view = recycler.getViewForPosition(mCurrentPosition); 
        mCurrentPosition += mItemDirection; 
        
        return view; 
    } 
}
class Recycler { 
  	
    public View getViewForPosition(int position) { 
        // 直接进入getViewForPosition()方法。 
        return getViewForPosition(position, false); 
    } 
  
  	// 这个方法就是我们真正获取或者创建ViewHolder的地方了。
  	// 这里的逻辑和之前描述缓存的意思相同,
  	// 先通过CacheView、再通过ViewCacheExtension、最后通过RecyclerPool方法。如果三个缓存里都没有的话,那只能重新创建了。
		View getViewForPosition(int position, boolean dryRun) { 
        //...
      
        ViewHolder holder = null; 
        
        //... 
        // 1) Find from scrap by position 
        if (holder == null) { 
            // 这里我简单对逻辑进行解释。
          	// 这里通过position来查找缓存
          	// 1.在mAttachedScrap列表查找ViewHolder。
          	// 2.在mHiddenViews列表中查找View。但是我还没有找到这个缓存是什么个情况。
          	// 3.在mCachedViews列表中查找ViewHolder。这里可以真正的称之为第一层缓存。
            holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); 
        } 
        if (holder == null) { 
        		// ...	
          
        		// 2) Find from scrap via stable ids, if exists 
        		if (mAdapter.hasStableIds()) { 
              	// 这里通过adapter的itemId来在mAttachedScrap中查抄ViewHolder。
        				holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        		} 
          	// 这里就是用户自定义的缓存方法了,他只是调用了外面的接口,而接口返回的view就是要我们来提供了。说不定以后我们会学习一下怎么来设置自定义缓存
            if (holder == null && mViewCacheExtension != null) { 
            		// ...
            } 
        		if (holder == null) {
        				// 这里通过viewType去RecyclerViewPool来找相同的viewType的ViewHolder 
        				holder = getRecycledViewPool().getRecycledView(type); 
        		} 
            if (holder == null) { 
                // 走到这里说明已经没有缓存了,直接重新构建 // ViewHolder的生命周期了 
                holder = mAdapter.createViewHolder(RecyclerView.this, type); 
                //... 
            } 
        } 
        //... 
        boolean bound = false; 
        if (mState.isPreLayout() && holder.isBound()) { 
        		// ... 
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { 
        		// ... 
            // 这里最重要的就是走了adapter的bindViewHolder方法,
          	// 在CacheView不会进入这个逻辑,且不会调用bindViewHolder方法,而其他缓存出来的ViewHolder会进入这个方法。
            mAdapter.bindViewHolder(holder, offsetPosition); 
            // ... 
        } 
        // 这里的操作已经无伤大雅了。略过 
        return holder.itemView; 
    } 
}
onDraw

RecyclerViewonDraw()方法主要是用来渲染ItemDecoration(),而onDraw()方法里会调用onDrawChild(),对每个ViewHolder进行渲染,所以没啥好解释的。

小结一下:到这里我们总结了关于RecyclerView的初始化流程和获取缓存ViewHolder的流程。

RecyclerView自己做的事情其实是比较少的,对于Layout流程他差不多都是给到LayoutManager来执行,LayoutManger执行每个Item的onLayout方法。

RecyclerView的缓存是交给Recycler来管理,获取缓存的流程是通过CacheView、ViewCacheExtension、RecyclerPool来执行的。

CacheView的ViewHolder完全保持Item之前的状态,既不会走onCreateViewHolder也不会走onBindViewHolder。CacheView的默认最大容量是两个,可以自定义修改。

ViewCacheExtension暂时不知道怎么使用,网上也没有啥例子可以参考。

RecyclerPool是通过ViewType来进行管理的,多个RecyclerView可以共用一个RecyclerPool,当CacheView放不下的时候,栈底的元素就会放到RecyclerPool中。

RecyclerView滑动和放入缓存

上面的初始化和显示分析了获取缓存的流程和逻辑,而只是获取缓存,没有加入缓存的话那么这个分析也是没有意义的。

RecyclerView的的滑动就是加入缓存的一个入口,当页面上有Item被划出屏幕的时候,那么这个Item的ViewHolder就加入到缓存了。

触发滑动事件大部分是用户用手机滑动屏幕进行,所以滑动的入口就是在监听滑动事件的onTouchEvent的ACTION_MOVE事件

class RecyclerView {
		public boolean onTouchEvent(MotionEvent e) {
				switch (action) {
            case MotionEvent.ACTION_MOVE: {
              	// 这个是scroll事件的内部实现
              	if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                }
              	break;
            }
        }
		}
}
class RecyclerView {
		boolean scrollByInternal(int x, int y, MotionEvent ev) {
      	if (mAdapter != null) {
            if (y != 0) {
              	// 因为我们的设定是竖直方向进行滑动,所以是进入这个逻辑
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
            }
        }
    }
}

​ scrollVerticallyBy这个方法也是走的LayoutManager,所以说,LayoutManager还承接了滑动的真实逻辑。

class LinearLayoutManager {
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        return scrollBy(dy, recycler, state);
    }
  
  	int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
    }
}

我们又走到了fill方法,而fill方法就是保证新滑动出来的item能够被填充到RecyclerView中。因为我们之前已经在fill方法中分析过获取缓存的方法,这里分析fill方法我们把重点放到将Item放入缓存的流程。

class LinearLayoutManager {
		int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
				if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
          	// 这里就是回收Item的入口
            recycleByLayoutState(recycler, layoutState);
        }
    }
}
class LinearLayoutManager {
		private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
      			// 这个方法是从上开始回收Item。
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
    }
}
class LinearLayoutManager {
    } else {
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
              recycleChildren(recycler, 0, i);
              return;
            }
        }
    }
}

关于找出要回收的Item的逻辑比较有意思。

1.png1.png

所以他是通过滑动距离和每个item的底部的坐标来判断item的上一个item是否划出了屏幕。

下面来看看是怎么回收Item。

class LinearLayoutManager {
		private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return;
        }
        if (endIndex > startIndex) {
            for (int i = endIndex - 1; i >= startIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }
  
    public void removeAndRecycleViewAt(int index, Recycler recycler) {
      	// 获取这个ViewHolder的view。
        final View view = getChildAt(index);
      	// 在页面上删除这个已经移出的view。
        removeViewAt(index);
      	// 将view交给Recycler,Recycler来完成回收的动作
        recycler.recycleView(view);
    }
  
    public void recycleView(View view) {
        recycleViewHolderInternal(holder);
    }
  
  	void recycleViewHolderInternal(ViewHolder holder) {
        if (forceRecycle || holder.isRecyclable()) {
            if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
                int cachedViewSize = mCachedViews.size();
              	// 当cachedView的数量大于等于最大缓存数的时候,那么把最开始加入到CacheView的Item移出,把新的Item放进CacheView。然后把移出的Item放入到RecyclerPool中。
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                    recycleCachedViewAt(0);
                    cachedViewSize --;
                }
                if (cachedViewSize < mViewCacheMax) {
                  mCachedViews.add(holder);
                  cached = true;
                }
            }
          	// 这里我们再看看RecyclerPool是如何存储被回收的Item的。
            if (!cached) {
                addViewHolderToRecycledViewPool(holder);
                recycled = true;
            }
        } 
    }
  
  	void addViewHolderToRecycledViewPool(ViewHolder holder) {
        getRecycledViewPool().putRecycledView(holder);
  	}
}
class RecycledViewPool {
  	public void putRecycledView(ViewHolder scrap) {
      	// 首先拿到这个ViewHolder的viewType。
        final int viewType = scrap.getItemViewType();
      	// 通过这个viewType拿到相对应的存储的列表,简单介绍里面的逻辑,从一个map中拿到这个list,如果没有个这个kv值,那么重新创建一个list并放入到这个map,并且创建一个这个viewType的最大值放入到另一个map中。最大值默认为5.
        final ArrayList scrapHeap = getScrapHeapForType(viewType);
      	// 如果RecyclerPool中这个viewType的数量已经达到最大值,那么直接不存储了。
        if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
          	return;
        }
        
      	// 如果RecyclerPool中这个viewType的数量小于最大值,那么存入RecyclerPool中。
        scrap.resetInternal();
        scrapHeap.add(scrap);
  	}
}

至此,关于RecyclerView的缓存流程已经完成,这里我们总结一下,

1.RecyclerView分为三级缓存,cacheView,自定义缓存和RecyclerPool。

2.CacheView默认最大为两个,会保存Item的状态。(不会去调用onBindViewHolder)

3.RecyclerPool根据viewType来管理存储,不会保存Item的状态(会去调用onBindViewHolder)

4.自定义缓存只有让客户端程序员自己定义,并没有在回收Item的时候对其进行操作。