1 Introduction to RecyclerView
RecyclerView
is a very powerful tool widget
that can help you flexibly display list data. When I started learning RecyclerView
, I found that there are many resources available for complex list interface, but very few resources for simple list display. Although RecyclerView
the structure of the system may seem complicated at first glance, you will find that it is actually very simple and clear after you understand it deeply.
RecyclerView
Generally used as Android
a display list control, it has many excellent performances. The recycling pool strategy can load hundreds of millions of data without lagging, and the adapter mode can display any display requirements.
RecyclerView
Just like a conveyor belt, making full use of the principle of the conveyor belt, only the data that the user sees will always be loaded into the memory, and the data that cannot be seen is waiting to be loaded. The conveyor belt can continuously transport billion-level goods, RecyclerView
and can also display loading billion-level goods Item
.
1.3 Core components in the RecyclerView architecture
回收池
: Can recycle anyItem
control and returnItem
a control that conforms to the type;
for example,onBinderViewHodler
the first parameter in the method is returned from the recycling pool适配器
: Adapter interface, which often assistsRecyclerView
in implementing list display; adapter mode, which separates user interface display from interactionRecyclerView
: It is the interaction of touch events, mainly to realize the boundary value judgment; according to the user's touch feedback, coordinate the work between the recycling pool object and the adapter object
We study with these questions RecyclerView
:
Listview
andRecycerview
cache differenceRecyclerView
Where did the one that slipped outView
go?RecyclerView
How to reuse the recycling poolView
RecyclerView
The four-level cache mechanism
1.4 RecyclerView sliding related
As we all know, the performance of implementing lists RecyclerView
in android
is very good, so what is the reason for the good performance? The key lies in its view
recycling and reuse during processing. When the list is sliding, it will be itemView
recycled and reused, so let's onTouchEvent
analyze it from the sliding callback
1.4.1 Basic concepts
- ViewHolder: The container of View, a View corresponds to a ViewHolder
- Recyler: The internal class of RecyclerView, which is mainly responsible for the recycling and reuse of View
- LinearLayoutManager: RecyclerView's linear layout manager
1.4.2 Function call chain when sliding
Here we have a general understanding of the function call chain when sliding down, to help understand the ideas related to the analysis of the four-level cache later.
1.4.3 onMeasrue initialization
RecyclerView
Developers like to set the width and height of the layer wrap_content or match_parent. Therefore, it is necessary to determine the height of RecyclerView through the actual content
情况1
: When the number of items is insufficient, for example, RecyclerView only loads 2 Items, the height measured by the total height of the child controls shall prevail:
情况2
When the number of items exceeds the actual screen height, the match_parent shall prevail, that is maximum height
1.4.4 onLayout
RecyclerView
As a container class control inherited from ViewGroup. The onLayout method must be implemented to place the sub-controls correctly. Since our handwritten RecyclerVIew is vertical, the placement is from top to bottom. At the same time, in order not to load all items into memory, accurate control is also required
1.4.5 Event interception
RecyclerView
As a container control, it is necessary to intercept sliding events. When the user slides his finger, all sub-items will slide. Sub-items cannot receive any events during sliding. When the RecyclerVIew is still, the child Item needs to receive the click event
2 Working mechanism of RecyclerView adapter and recycling pool
Here we first understand RecyclerView
the relevant loading logic in the form of pictures.
2.1 The first screen loading in RecyclerView
2.2 The second screen in RecyclerView
2.3 Recycling Pond Recycling Strategy
2.4 Recycling Pool Filling Strategy
2.5 Design of recovery pool
3 RecyclerView recycling and reuse
3.1 Analysis of key methods of recovery
RecyclerView.java
void recycleViewHolderInternal(ViewHolder holder) {
...
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN))
// 1) 先尝试放到cacheView中
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 如果 mCachedViews 已经满了,把第0个位置的移除并放到 缓存池中
recycleCachedViewAt(0);
cachedViewSize--;
}
if (!cached) {
// 2) 如果CacheView中没放进去,就放到 缓存池中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
...
}
3.2 Analysis of key methods of reuse
tryGetViewHolderForPositionByDeadline
Get it from the first-level cache mChangeScrap Get it
from the second-level cache mCachedViews Get
it from the third-level cache mViewCacheExtension
Get it from the fourth-level cache cache pool
If you don’t get the value in the cache, create it directly
Unbound and outdated, bind it
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
ViewHolder holder = null;
// 1) 从一级缓存 changed scrap 中取
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 2)从二级缓存 cache中取
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
// 3. 从三级缓存 CacheExtension 中取
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
// 4) 从四级缓存 缓存池中取
if (holder == null) {
// fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
// 5)缓存中都没有拿到值,就直接创建
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
}
// 6)已经 bind过了,不会再去绑定,未绑定过时,进行绑定
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
// 尝试 bindView
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
return holder;
}
4 Level 4 cache mechanism
4.1 Level 1 cache - cache fragmentation
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// find by position
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
4.2 Second level cache - cache list
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// 1) 先从mAttachedScrap中取,取到便返回
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
if (type == holder.getItemViewType()) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
if (holder.isRemoved()) {
if (!mState.isPreLayout()) {
holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
}
}
return holder;
} else if (!dryRun) {
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
quickRecycleScrapView(holder.itemView);
}
}
}
// 2)二级缓存,从mCachedViews中取
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
//从mCachedViews中取到后便返回
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
4.3 Level 3 cache - custom cache
This level of cache is user-defined and will not be explained in detail here.
4.4 Level 4 cache - cache pool
public ViewHolder getRecycledView(int viewType) {
//从mScrap中根据view的类型来取出一个
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
//从 scrapData 中拿最后一个数据,先进后出
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
static class ScrapData {
//ViewHolder是作为一个List被存进来的
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
// 缓存池中 list的大小是5,也就是每个类型的view缓存池中存储5个
int mMaxScrap = 5;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}