一.RecyclerView显示
在使用RecyclerView时,需要结合Adapter来使用,一个RecyclerView需要一个Adapter,一个Adapter中对应着指定数量及指定type的item,即ViewHolder,那ViewHolder时如何创建及如何显示的?
在平时使用中,通过调用RecycerView.setAdapter()来显示内容,一起看一下执行流程:
a.RecyclerView
public void setAdapter(@Nullable Adapter adapter) {
......
setAdapterInternal(adapter, false, true);
requestLayout();
}
先执行了setAdapterInternal(adapter, false, true),看一下这个方法主要做了什么:
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
Layout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
}
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
可以看到在setAdapterInternal()里面先调用了unRegister和unDetached操作,接着执行了register和detached操作,register里面会执行adapter里面对应的register方法:
private final AdapterDataObservable mObservable = new AdapterDataObservable();
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
在registerAdapterDataObserver()里面执行的是AdapterDataObservable的registerObserver()方法,当我们在本地实现的adapter内部执行notifyDataSetChanged()及notifyItemChanged()等操作时,先会调用到AdapterDataObservable内部对应的方法:
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
........
........
}
在AdapterDataObservable里面最终会调用到RecyclerViewDataObserver里面对应的方法,执行view刷新的操作。
private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onChanged() {
......
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
......
}
......
......
void triggerUpdateProcessor() {
......
}
}
接着上面setAdapter()内部逻辑讲,然后调用了requestLayout(),调用该方法后,会执行onLayout():
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
}
在dispatchLayout()内部会调用到dispatchLayoutStep2():
private void dispatchLayoutStep2() {
......
mState.mItemCount = mAdapter.getItemCount();
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
....
}
在该方法内部,先通过adapter的getItemCount()来获取到item的数量,赋值给State()的mItemCount,后续在显示item时会使用到;然后调用mLayout的onLayoutChildren()方法,mLayout是在初始化RecyclerView时通过setLayoutManager()传入的LayoutManager,此处分析LinerLayoutManager。
b.LinerLayoutManager
LayoutManager是用来决定RecyclerView内部item显示的布局,LinerLayoutManager线性布局,GridLayoutManager是网格布局,本文主要分析LinerLayoutManager:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
.......
......
fill(recycler, mLayoutState, state, false);
......
......
}
在onLayoutChildren()内调用了fill()方法:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {
......
......
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
.......
layoutChunk(recycler, state, layoutState, layoutChunkResult);
.......
}
}
在fill()内部通过while()来不断的执行layoutChunk(),先看一下while()循环的条件之一:layoutState.hasMore(state),LayoutState是LinerLayoutManager内部的一个静态类,State是RecyclerView内部的一个类,注意别搞混了;
boolean hasMore(RecyclerView.State state) {
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}
通过以上看到,用到了getItemCount(),通过position跟item的数量来判断是否需要继续循环执行layoutChunk():
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
......
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
在layoutChunk()内部会通过layoutState.next(recycler)来获取当前position对应的view,然后通过addView(),最后执行layout来进行放置,这里我们不去关心是如何放置的,只关心是如何获取的item对应的view,主要看一下layoutState.next(recycler):
View next(RecyclerView.Recycler recycler) {
........
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
next()内部最终是通过recycler的getViewForPosition来获取view。
c.RecyclerView.Recycler
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
从getViewForPosition()内部逻辑来看,先调用tryGetViewHolderForPositionByDeadline()来获取到ViewHolder,然后获取到ViewHolder里面的itemView,先看一下tryGetViewHolderForPositionByDeadline():
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
.......
}
if (holder == null) {
//获取到position对应的type,需要本地实现,不实现的话,默认返回0,即一个type
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
......
}
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
......
}
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
.....
}
if (holder == null) {
.......
holder = mAdapter.createViewHolder(RecyclerView.this, type);
.......
}
}
......
......
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
........
return holder;
}
从上面可以看到,去获取holder时,会经历四步判断,即对应着四级缓存,下一节会详细介绍,如果从四级缓存中都没有获取到时,会执行createViewHolder()来进行创建:
注意一下:viewType是从本地实现的getItemType(postion)来获取的,如果没有实现,则默认为0,即都是统一类型;如果实现了,用途有二:1.本地根据对应的type来创建不同的view;2.RecyclerViewPool根据不同的type缓存对应数量为5的viewHolder。
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
return holder;
} finally {
TraceCompat.endSection();
}
}
最终会回调本地adapter的onCreateViewHolder()来创建对应的viewHolder,看一下ViewHolder这个类:
public abstract static class ViewHolder {
......
......
public ViewHolder(@NonNull View itemView) {
if (itemView == null) {
throw new IllegalArgumentException("itemView may not be null");
}
this.itemView = itemView;
}
......
......
}
通过以上可以看到,itemView是在构造方法中作为参数传入的,在创建本地ViewHolder时,我们会先通过LayoutInflater来inflate()对应position及itemtype的view,然后执行new ViewHolder(view)返回,此处itemView就是我们创建的view。
接着上面分析,接下来会调用tryBindViewHolderByDeadline(),先设置flag,最终会回调本地adapter的onBindViewHolder():
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
......
final int viewType = holder.getItemViewType();
mAdapter.bindViewHolder(holder, offsetPosition);
......
return true;
}
public final void bindViewHolder(@NonNull VH holder, int position) {
holder.mPosition = position;
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
}
以上就是在初始化RecyclerView,然后调用setAdapter()后的整个执行流程,用流程图总结一下:
recycleview显示流程.png
二.四级缓存机制
RecyclerView缓存机制分为四级,分别为:Scrap(mAttachedScrap)、Cache(mCachedViews)、ViewCacheExtension(mViewCacheExtension)、RecycledViewPool(mRecyclerPool)。
Scrap
就是屏幕内的缓存数据,可以直接拿来复用。
Cache
刚刚移出屏幕的缓存数据,默认大小是2个,当其容量被充满同时又有新的数据添加的时候,会根据FIFO原则,把先进入的缓存数据移出并放到下一级缓存中,然后再把新的数据添加进来。Cache里面的数据是干净的,即携带了原来的ViewHolder的所有数据信息,数据可以直接来拿来复用。需要注意的是,cache是根据position来寻找数据的,这个postion是根据第一个或者最后一个可见的item的position以及用户操作行为(上拉还是下拉)。
举个例子:当前屏幕内第一个可见的item的position是1,用户进行了一个下拉操作,那么当前预测的position就相当于(1-1=0),也就是position=0的那个item要被拉回到屏幕,此时RecyclerView就从Cache里面找position=0的数据,如果找到了就直接拿来复用。
ViewCacheExtension
google留给开发者自己来自定义缓存的,ViewCacheExtension适用场景:ViewHolder位置固定、内容固定、数量有限时使用。
使用方式如下:
//viewType类型为TYPE_SPECIAL时,设置四级缓存池RecyclerPool不存储对应类型的数据 因为需要开发者自行缓存
recyclerView.getRecycledViewPool().setMaxRecycledViews(DemoAdapter.TYPE_SPECIAL, 0);
//设置ViewCacheExtension缓存
recyclerView.setViewCacheExtension(new MyViewCacheExtension());
//实现自定义缓存ViewCacheExtension
class MyViewCacheExtension extends RecyclerView.ViewCacheExtension {
@Nullable
@Override
public View getViewForPositionAndType(@NonNull RecyclerView.Recycler recycler, int position, int viewType) {
//如果viewType为TYPE_SPECIAL,使用自己缓存的View去构建ViewHolder
// 否则返回null,会使用系统RecyclerPool缓存或者从新通过onCreateViewHolder构建View及ViewHolder
return viewType == DemoAdapter.TYPE_SPECIAL ? adapter.caches.get(position) : null;
}
}
Adapter.java
public SparseArray<View> caches = new SparseArray<>();//开发者自行维护的缓存
Adapter#onBindViewHolder中根据position设置的缓存:caches.put(position, sHolder.itemView);
三.源码分析
主要分析一下RecycledViewPool的流程:
RecycledViewPool
Cache默认的缓存数量是2个,当Cache缓存满了以后会根据FIFO(先进先出)的规则把Cache先缓存进去的ViewHolder移出并缓存到RecycledViewPool中,RecycledViewPool存储时是根据itemType来存储的,每个itemType对应的缓存数量是5个;看一下主要实现逻辑:
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
.........
.........
}
RecycledViewPool内部维护一个SparseArray<ScrapData>mScrap,ScrapData内部维护一个ArrayList<ViewHolder>mScrapHeap,最大值为5;
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;
}
......
scrap.resetInternal();
scrapHeap.add(scrap);
}
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
在执行putRecycledView(vh)时,先判断vh的type,根据type去获取ScrapData,如果获取到就返回,否则就创建一个新的ScrapData,然后加入到mScrap内,key为对应的type,再获取到ScrapData内部的mScrapHeap,判断size(),如果已经等于5,就不再存储了,否则先执行resetInternal()来清空数据,然后再加入到mScrapHeap里面;
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;
}
在执行getRecycledView(type)时,先根据type从mScrap内获取对应的ScrapData,如果不为空,则获取到ScrapData内部的mScrapHeap,再从heap中获取到ViewHolder,然后将对应位置的ViewHolder删除,后续put时再进行添加;
直观一点如下:
RecycledViewPool与Cache相比不同的是,从Cache里面移出的ViewHolder在存入RecycledViewPool之前ViewHolder的数据会被全部重置(resetInternal()),相当于一个新的ViewHolder,而且Cache是根据position来获取ViewHolder,而RecycledViewPool是根据itemType获取的,如果没有重写getItemType()方法,itemType就是默认的。因为RecycledViewPool缓存的ViewHolder是全新的,所以取出来的时候需要走onBindViewHolder()方法。
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
......
}
if (holder == null) {
.........
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
......
}
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
.......
}
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
........
}
if (holder == null) {
//create view holder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
.......
}
......
}
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()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
......
return holder;
}
直观一点如下:
RecyclerView缓存机制.png
四.ReceyclerView与ListView的区别
1)ListView布局单一,RecycleView可以根据LayoutManger有横向,瀑布和表格布局;
2)自定义适配器中,ListView的适配器继承ArrayAdapter;RecycleView的适配器继承RecyclerAdapter,并将范类指定为子项对象类ViewHolder(内部类)。
3)ListView优化需要自定义ViewHolder和判断convertView是否为null(不用每次都创建convertView和调用findViewById(),findViewById()这个方法是比较耗性能的操作,因为这个方法要找到指定的布局文件,进行不断地解析每个节点:从最顶端的节点进行一层一层的解析查询,找到后在一层一层的返回,如果在左边没找到,就会接着解析右边,并进行相应的查询,直到找到位置。特点:xml文件被解析的时候,只要被创建出来了,其孩子的id就不会改变了。根据这个特点,可以将孩子id存入到指定的集合中,每次就可以直接取出集合中对应的元素就可以了)。 而RecyclerView是强制存在规定好的ViewHolder。
4)绑定事件的方式不同,ListView是在主方法中ListView对象的setOnItemClickListener方法;RecyclerView则是在子项具体的View中去注册事件。
5) 缓存方式有区别:RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;ListView缓存View,同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView)。
6) 刷新方式:RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView;ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。
网友评论