The problem

  • The adapter of RecycleView is an adapter mode. The adapter of RecycleView is an adapter mode.

Adapter mode

  • The most common thing to use in recycleView is adapter, also called adapter mode, so let’s take a look at what’s there
The class adapter
  • The class adapter overrides the target method that calls the Adaptee method statically by integrating the inheritance relationship with the target method and implementing the Target method
  • The object adapter is dynamic by delegating to an adaptee that holds an Adaptee object
  • Class adapters can redefine implementation behavior, whereas object adapters have a harder time redefining adaptive behavior, but it’s easier to add behavior.
public interface Target { public void request(); } public class Adaptee {public void specialRequest(){system.out.println (" The Adaptee class has a special function..." ); Public class AdapterInfo extends Adaptee implements Target {public static void main(String[] args) { Target adaptee = new AdapterInfo(); adaptee.request(); } @Override public void request() { super.specialRequest(); }}Copy the code

Object adapter: Used a lot

  • In fact, it is similar to static proxy mode
Class Adapter implements Target{private Adaptee Adaptee; Public Adapter (Adaptee Adaptee) {this. Adaptee = Adaptee; } public void request () {/ / here is to use the delegate done special function enclosing adaptee. SpecificRequest (); }}Copy the code
  • Classes differ from object adapters: the class adapter pattern needs to create an Adaptee itself, whereas the object adapter pattern can directly use an existing Adaptee instance to transform the interface

The adapter RecycleView

  • To understand his adapter pattern, we must look at his inheritance
Public class RecyclerView extends ViewGroup implements ScrollingView, Class FooterAdapter extends recyclerView. adapter < recyclerview. ViewHolder> //Adapter  public abstract static class Adapter<VH extends RecyclerView.ViewHolder> { private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable(); private boolean mHasStableIds = false; Static class AdapterDataObservable extends AdapterDataObservable extends public Adapter() {//mObservable creates an internal class for each Adapter Observable<RecyclerView.AdapterDataObserver> { AdapterDataObservable() { }Copy the code
  • Through the above analysis, we use RecycleView.Adapter is an abstract class defined by the Adapter. The method of this abstract class should be provided for the internal use of RecycleView, so a FootAdapter implementation class is implemented and then passed to recycleView. setAdapt Er (Adapter) is an object adapter
  • And the object Adapter can be replaced at any time, we can change the specific implementation of Adapter in RecycleView so as to load different Adapter according to different types of different interface!
  • View the creation of setAdapter
Public abstract static class Adapter<VH extends ViewHolder> {static classes provide an Observable for Adapter data changes. Each class has its own Observable private final AdapterDataObservable mObservable = new AdapterDataObservable(); //setAdapter public void setAdapter(Adapter adapter) { // bail out if layout is frozen setLayoutFrozen(false); setAdapterInternal(adapter, false, true); requestLayout(); } //setAdapterInternal: replace old adapter with new adapter, and trigger listener mObserver private void setAdapterInternal(@nullable Adapter) adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter ! = null) { mAdapter.unregisterAdapterDataObserver(mObserver); . / / cancellation of the old adapter observer mAdapter onDetachedFromRecyclerView (this); // recycleView}.... mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter ! = null) { adapter.registerAdapterDataObserver(mObserver); / / registered observers adapter. OnAttachedToRecyclerView (this); }... }Copy the code
RecycleView cache
  • The reason why ListView is replaced by RecycleView is because of its excellent caching mechanism
The ListView cache
  • ListView only has two levels of cache, RecycleBin cache,
    1. MActiveViews: Level 1 is the view visible on the cache screen
    2. MScrapViews: This is an ArrayList[] array, each type corresponds to its own ArrayList cache
  • RecycleView caches ViewHolder. List caches ViewHolder
Four levels of cache
  1. Level 1 Cache: mAttachedScrap and mChangedScrap: The cache with the highest priority will be used by RecycleView to obtain the viewHolder from these two caches. The former stores the viewHolder currently on the screen, and the latter stores the viewHolder whose data is updated, for example, when adapter.NotifyItem is called The Changed() method updates the entry
    • mAttachedScrap : He represents a ViewHolder stored on the screen that is actually a ViewHolder that has been detached from the screen but is about to be added to the screen, for example by sliding up and down to bring up a new Item, and then calling onLayout of LayoutManager again The Children method will scrap all viewholers on the screen and add them to mAttachedScrap. Then, when redrawing each ItemView, the mAttachedScrap will be used first The procedure does not re-onBindViewholder
  2. Level 2 cache: mCachedViews: the default size is 2. It is usually used to store the prefetched viewHolder. At the same time, when a viewHolder is recycled, it may also store part of the viewHolder
    • The default size of RecycleView is 2, but it is usually 3,3 is the default size of 2+ the number of prefetch 1, so the size of mCacheView is 3 when the RecycleView is loaded for the first time (for example, LinerLayoutManager layout).
  3. Level 3 cache: ViewCachedExtension: Custom cache, usually not used
  4. RecycleViewPool: Cache ViewHolder according to viewType. Each viewType has an array size of 5, which can be changed dynamically
Several state values of the ViewHolder
  • Read RecycleView source code, everywhere see ViewHolder isInvalid,isRemoved, isBound, isTmpDetached, isScrap. IsUpdated these methods, in fact,is a state machine
  • IsInvalid: flag_invalid: indicates whether the current ViewHolder isInvalid. This occurs in three common cases
    1. Call the adapter. NotifyDataSetChanged () method
    2. Manual call RecycleView. InvalidateItemDecorations () method
    3. Call the setAdapter or swapAdapter() method of RecycleView
  • IsRemoved: Flag_removed: indicates whether the current ViewHolder has been removed, in general, been removed part of the data sources, and then call the adapter. NotifyItemRemoved ()
    • That is, after removing a piece of data from data
    public void removeItem(int position){ data.remove(posiiton); notifyItemRemoved(position); notifyItemRangeChanged(position, data.size() - position); OnBindViewHolder will not be called}Copy the code
  • IsBound: flag_bound: indicates whether the current ViewHolder has already called onBindViewHolder
  • IsTmpdetached: flag_tmp_detached: indicates whether the current ItemView should detach from the RecycleView parent.
    1. Manually called RecycleView. DetachView related methods
    public void detachViewFromParent(int offset) { final View view = getChildAt(offset); if (view ! = null) { final ViewHolder vh = getChildViewHolderInt(view); if (vh ! = null) { if (vh.isTmpDetached() && ! vh.shouldIgnore()) { throw new IllegalArgumentException("called detach on an already" + " detached child " + vh + exceptionLabel()); } if (DEBUG) { Log.d(TAG, "tmpDetach " + vh); } vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); / / detachView View (View) to invoke the callback, set the Flag}} RecyclerView. Enclosing detachViewFromParent (offset); }Copy the code
    1. Retrieving a viewHolder from mHideViews detach the ItemView associated with the viewHolder first
  • IsScrap: No flag status. MScrapContainer is null, indicating whether the ViewHolder is discarded
  • IsUpdated: flag_update: indicates whether the ViewHolder isUpdated.
    1. There are three cases where the isInvalid method exists
    2. The Adapter.onBindViewholder method is called
    3. Call the adapter. NotifyItemChanged method
  • At the top we have a mHiddenViews, which does not count in the level 4 cache because it only has elements during the animation and is cleared after the animation ends, so it cannot count in the level 4 cache
  1. Above level cache, there are two, that is, if the call the adapter notifyItemChanged (), to bring back the callback to the LayoutManager onLayoutChildren (), so he is two and have what differentiation? Let’s look at some code logic:
Void scrapView(View View) {final viewHolder holder = mAttachedScrap or mChangedScrap getChildViewHolderInt(view); If (holder. HasAnyOfTheFlags (ViewHolder FLAG_REMOVED | ViewHolder. FLAG_INVALID) / / tags at the same time remove and invalid | |! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { if (holder.isInvalid() && ! holder.isRemoved() && ! mAdapter.hasStableIds()) { throw new IllegalArgumentException("Called scrap view with an invalid view." + " Invalid views cannot be reused from scrap, they should rebound from" + " recycler pool." + exceptionLabel()); } holder.setScrapContainer(this, false); mAttachedScrap.add(holder); } else { if (mChangedScrap == null) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this, true); mChangedScrap.add(holder); } } boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, viewHolder.getUnmodifiedPayloads()); }Copy the code
  • The mAttachedScrap contains ViewHolder in two states:
    1. Is marked as both remove and invalid
    2. Completely unchanged from viewHolder
    3. This has to do with RecyclerView ItemAnimator, if ItemAnimator is empty or ItemAnimator canReuseUpdatedViewHolder method to true, will be placed into the mAttachedScrap. So normally, when does it return true? As can be seen from the SimpleItemAnimator source, when ViewHolder’s isInvalid method returns true, it will be added to mAttachedScrap. That is, if the ViewHolder fails, it will also be put into mAttachedScrap.
  • MchangedScrap inside is isUpdated returns true situation, is called the adapter. NotifyItemChanged () and RecycleView ItemAnimator join isn’t empty
  • Also see the difference between the mHiddenViews and the mAttachedScrap/mChangedScrap array
    1. MHiddenViews only stores the ViewHolder of the animation, which is emptied naturally after the animation, for the possibility of reuse during the animation
    2. The Scrap array and mHiddenViews do not conflict. The same Viewholder may exist for both, but this does not affect the removal of the mHiddenViews at the end of the animation
The reuse of RecycleView
  • The above analysis only explains the nature of the four-level cache, but does not explain its reuse principle. We started with the layoutstate.next () method. We know that the RecycleView is given to LayoutManager to get a ViewHolder object when laying out the itemView. This is where the natural reuse logic is
    • Next () call rule: Scrollby RecycleView rewrite of the View – > scrollByInternal () – > mLayout. ScrollHorizontallyBy ()/mLayout scrollVerticallyBy () – > Called when ->scrollBy() -> Fill () -> Next () is swiped in LayoutManager
// We take linearlayoutManager. next as an example View Next (Recycler Recycler) {if (this.mscraplist! = null) { return this.nextViewFromScrapList(); } else { View view = recycler.getViewForPosition(this.mCurrentPosition); this.mCurrentPosition += this.mItemDirection; return view; }}Copy the code
  • Can see through next to obtain recycleView LinerLayoutManager under a viewHolder object, call recycleView. GetViewForPosition is the final call TryGetViewHolderForPositionByDeadline (), the core of this is the true reuse
  1. Obtain the corresponding viewHolder by position: The priority is high because each viewHolder has not been changed because the viewHolder corresponding to an itemView has been updated, so other viewHolder can quickly correspond to the original itemView on the screen
If (position > = 0 && position < RecyclerView. This. MState. GetItemCount ()) {/ / position legal Boolean fromScrapOrHiddenOrCache  = false; RecyclerView.ViewHolder holder = null; if (RecyclerView.this.mState.isPreLayout()) { holder = this.getChangedScrapViewForPosition(position); // Get fromScrapOrHiddenOrCache from mChangedScrap = holder! = null; } if (holder == null) { holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder ! = null) { if (! Enclosing validateViewHolderForOffsetPosition (holder)) {/ / check if the current position position match, if not match slippery out or failure if (! dryRun) { holder.addFlags(4); if (holder.isScrap()) { RecyclerView.this.removeDetachedView(holder.itemView, false); holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } // If onBindViewHolder is loaded from the viewPool, onBindViewHolder will be reloaded from the viewPool this.recycleViewHolderInternal(holder); } holder = null; } else { fromScrapOrHiddenOrCache = true; }}}Copy the code
  • The above source is divided into two steps:
    1. Get the ViewHolder from mChangedScrap, which holds the updated ViewHolder
    2. ViewHolder respectively from mAttachedScrap mHiddenViews, mCachedViews
  1. First, get the ViewHolder from mChangedScrap: If the current is a pre-layout phase, get the ViewHolder from mChangedScrap
    • Pre-layout: preLayout, that is, when the current RecycleView is in the dispatchLayoutStep1 stage, it is called pre-layout
    • The dispatchLayoutStep2 stage is called the real layout stage
    • DispatchLayoutStep3 is called the postLayout stage
    • To enable pre-layout, you must have an itemAnimator, and the LayoutManager of each recycleView must enable pre-animation
  • The ViewHolder changed will be placed in the mChangedScrap array only if the ItemAnimator is not empty. Since the ViewHolder at the same location is different before and after the Chang animation, the mChangedScrap cache is used for pre-layout, and the mChangedScrap cache is not used for formal layout, ensuring that the ViewHolder at the same location is different before and after the chang animation
if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! = null; }Copy the code
  • The second operation, fetch from another cache
if (holder == null) { holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder ! = null) { if (! this.validateViewHolderForOffsetPosition(holder)) { if (! dryRun) { holder.addFlags(4); if (holder.isScrap()) { RecyclerView.this.removeDetachedView(holder.itemView, false); holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } this.recycleViewHolderInternal(holder); } holder = null; } else { fromScrapOrHiddenOrCache = true; }}}Copy the code
  • From mAttachedScrap respectively. MHiddenViews, mCachedViews ViewHolder, effective, but also need to determine whether ViewHolder invalid if need to do some cleanup operations remove from the cache, and then back into the mCacheViews or RecycleViewPool cache
    • Therefore, values from the primary and secondary caches are added to the RecycleView to display the original ViewHolder data. There is no need to re-use onBindViewHolder() to assign values. Only the original child can be reused, and the new child cannot be taken from the two levels of cache
    • MREcycleViewPool: caches the ViewHolder and resets the data, which is equivalent to a new ViewHolder, except that onCreateView() is not used and onBindViewHolder() is used to bind the data
  • So let’s look at the values from the various caches
final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList(); final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList(); / / the cache size is the default 2 plus layoutManager setting determines void updateViewCacheSize () {int extraCache = RecyclerView. Enclosing mLayout! = null ? RecyclerView.this.mLayout.mPrefetchMaxCountObserved : 0; this.mViewCacheMax = this.mRequestedCacheMax + extraCache; for(int i = this.mCachedViews.size() - 1; i >= 0 && this.mCachedViews.size() > this.mViewCacheMax; --i) { this.recycleCachedViewAt(i); Void recycleCachedViewAt(int cachedViewIndex) {// Recycle recycleCachedViewAt(int cachedViewIndex) { RecyclerView.ViewHolder viewHolder = (RecyclerView.ViewHolder)this.mCachedViews.get(cachedViewIndex); this.addViewHolderToRecycledViewPool(viewHolder, true); this.mCachedViews.remove(cachedViewIndex); } RecyclerView.ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { int scrapCount = this.mAttachedScrap.size(); // Get from mAttachedScrap, int cacheSize if available; RecyclerView.ViewHolder vh; for(cacheSize = 0; cacheSize < scrapCount; ++cacheSize) { vh = (RecyclerView.ViewHolder)this.mAttachedScrap.get(cacheSize); if (! vh.wasReturnedFromScrap() && vh.getLayoutPosition() == position && ! vh.isInvalid() && (RecyclerView.this.mState.mInPreLayout || ! vh.isRemoved())) { vh.addFlags(32); return vh; } } if (! DryRun) {/ / default/false/obtain the View from the mHiddrenViews View = RecyclerView. Enclosing mChildHelper. FindHiddenNonRemovedView (position); if (view ! = null) { vh = RecyclerView.getChildViewHolderInt(view); RecyclerView.this.mChildHelper.unhide(view); int layoutIndex = RecyclerView.this.mChildHelper.indexOfChild(view); if (layoutIndex == -1) { throw new IllegalStateException("layout index should not be -1 after unhiding a view:" + vh + RecyclerView.this.exceptionLabel()); } RecyclerView.this.mChildHelper.detachViewFromParent(layoutIndex); this.scrapView(view); vh.addFlags(8224); return vh; } // Get cacheSize from mCacheViews = this.mcachedviews.size (); for(int i = 0; i < cacheSize; ++i) { RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mCachedViews.get(i); if (! holder.isInvalid() && holder.getLayoutPosition() == position) { if (! dryRun) { this.mCachedViews.remove(i); } return holder; } } return null; }Copy the code
  • The above analysis is to obtain the ViewHolder through position. If we verify that position is legal, the viewType must be correct and corresponding, but when obtaining the ViewHolder through viewType,position may be invalid. There are three steps through viewType:
    1. If adapter hasStableIds = true, the search criteria are viewType and ID
    2. If adapter.hasStablleIds = false, use viewCacheExtension to retrieve ViewHolder in RecycleViewPool
    3. If no suitable ViewHolder is found, a new ViewHolder object is created by calling the Adapter’s onCreateViewHolder
  • First of all, we just analyzed the first two kinds of cache operations, the third kind of personal Settings do not analysis, then the fourth kind of cache from the RecycleViewPool how to do? First of all, what is he?
public static class RecycledViewPool { private static final int DEFAULT_MAX_SCRAP = 5; SparseArray<RecyclerView.RecycledViewPool.ScrapData> mScrap = new SparseArray(); Private int mAttachCount = 0; private int mAttachCount = 0; Static class ScrapData {final ArrayList< recyclerView. ViewHolder> mScrapHeap = new ArrayList(); ViewHolder int mMaxScrap = 5; // For each different viewType, the same viewType is saved in the same ArrayList, and a maximum of 5 ViewHolder int mMaxScrap = 5; // For each different viewType, the same viewType is saved in the same ArrayList. long mCreateRunningAverageNs = 0L; long mBindRunningAverageNs = 0L; ScrapData() { } }Copy the code
  • Then its reuse code logic is:
int offsetPosition; int type; if (holder == null) { offsetPosition = RecyclerView.this.mAdapterHelper.findPositionOffset(position); / / get the position corresponding to the offset of the if (offsetPosition < 0 | | offsetPosition > = RecyclerView. Enclosing mAdapter. GetItemCount ()) { // If the current offset does not appear on the screen, the RecycleView is not updated, so setData must notify the UI update throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item position " + position + "(offset:" + offsetPosition + ")." + "state:" + RecyclerView.this.mState.getItemCount() + RecyclerView.this.exceptionLabel()); } type = RecyclerView.this.mAdapter.getItemViewType(offsetPosition); / / get the current position corresponding to the type of the if (RecyclerView. This. MAdapter. HasStableIds ()) { // If the new hasStableIds returns true, even if the ViewHolder fails to be retrieved by Position, the ViewHolder will be retrieved by ViewType. Type determines whether the current position exists in the cache this.getScrapOrCachedViewForId(RecyclerView.this.mAdapter.getItemId(offsetPosition), type, dryRun); if (holder ! = null) { holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; If (holder == null && this.mViewCacheExtension! = null) { View view = this.mViewCacheExtension.getViewForPositionAndType(this, position, type); if (view ! = null) { holder = RecyclerView.this.getChildViewHolder(view); if (holder == null) { throw new IllegalArgumentException("getViewForPositionAndType returned a view which does not have a ViewHolder" + RecyclerView.this.exceptionLabel()); } if (holder.shouldIgnore()) { throw new IllegalArgumentException("getViewForPositionAndType returned a view that is ignored. You must call stopIgnoring before returning this view." + RecyclerView.this.exceptionLabel()); } // Create a recycleViewPool for user == null this.getRecycledViewPool().getRecycledView(type); // Get the last of the five arrays and remove a data from the cache array. = null) { holder.resetInternal(); / / just to get the layout file, reset the inside information, so that the setting of the subsequent use the if (RecyclerView. FORCE_INVALIDATE_DISPLAY_LIST) {enclosing invalidateDisplayListInt (holder); }}} if (holder == null) {onCreateViewHolder creates a new ViewHolder long start = if there are no values in the upper cache RecyclerView.this.getNanoTime(); if (deadlineNs ! = 9223372036854775807L && ! this.mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { return null; } holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type); Create a new ViewHolder if (recyclerView.allow_thread_gap_work) {RecyclerView innerView = // create a new ViewHolder if (recyclerView.allow_thread_gap_work) {RecyclerView innerView = RecyclerView.findNestedRecyclerView(holder.itemView); if (innerView ! = null) { holder.mNestedRecyclerView = new WeakReference(innerView); } } long end = RecyclerView.this.getNanoTime(); this.mRecyclerPool.factorInCreateTime(type, end - start); }Copy the code
  • To sum up, it is the reuse mechanism of RecycleView. Let’s look at the recycling mechanism
RecycleView recycling mechanism
  • All frameworks exist for reuse, of course, after the first creation there will be recycling, otherwise how to reuse? At the same time understand these two processes, for our use of RecycleView and its principle analysis will be more clear!
  • First, review the reuse process: recycling, of course, is also one-to-one
    1. Check whether the scrap array is available, available directly reuse
    2. Check whether the mCachedViews array is available
    3. Look in the mHiddenViews array, which is an array of animations
    4. RecycleViewPool searches for availability based on type and returns the layout if it exists
    5. Did not find from the adapter. OnCreateViewHolder to create a new ViewHolder object to return
  • The corresponding recycling process is as follows:
    1. Scrap array
    2. MCachedViews array
    3. MHiddenViews array
    4. RecycleViewPool array
  1. Scrap Array recycling
void scrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); If (holder. HasAnyOfTheFlags (ViewHolder FLAG_REMOVED | ViewHolder. FLAG_INVALID) / / this method has been analyzed above | |! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { if (holder.isInvalid() && ! holder.isRemoved() && ! mAdapter.hasStableIds()) { throw new IllegalArgumentException("Called scrap view with an invalid view." + " Invalid views cannot be reused from scrap, they should rebound from" + " recycler pool." + exceptionLabel()); } holder.setScrapContainer(this, false); mAttachedScrap.add(holder); } else { if (mChangedScrap == null) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this, true); mChangedScrap.add(holder); }}Copy the code
  • Add to scrap array scrapView scrapView scrapView scrapView scrapView scrapView
    1. getScrapOrHiddenOrCachedHolderForPosition() : When a ViewHolder is fetched from mHiddenViews, the ViewHolder is removed from the mHiddenViews and then scrapView is invoked to place the ViewHolder into the Scrap array
    if (! dryRun) { View view = mChildHelper.findHiddenNonRemovedView(position); if (view ! = null) { // This View is good to be used. We just need to unhide, detach and move to the // scrap list. final ViewHolder vh = getChildViewHolderInt(view); mChildHelper.unhide(view); int layoutIndex = mChildHelper.indexOfChild(view); if (layoutIndex == RecyclerView.NO_POSITION) { throw new IllegalStateException("layout index should not be -1 after " + "unhiding a view:" + vh + exceptionLabel()); } mChildHelper.detachViewFromParent(layoutIndex); scrapView(view); vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); return vh; }}Copy the code
    1. Called in the scrapOrRecycleView() method of LayoutManager, there are two cases
      1. The layoutManager-related methods are called manually
      2. RecycleView creates a layout (requestLayout)
    private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); if (viewHolder.shouldIgnore()) { if (DEBUG) { Log.d(TAG, "ignoring view " + viewHolder); } return; } if (viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}Copy the code
  1. MCacheViews recycling
  • There are many mCacheViews recovery paths, which can be roughly divided into three categories:
    1. Redesign of the recycling, this kind of circumstance is calling adapter. The main notifyDataSetChange (), and hasStableIds returns false, here you can see why call notifyDataSetChange () method efficiency is so low, at the same time also know why Writing hasStableIds improves efficiency because notifyDataSetChange causes the RecycleView to recycle the ViewHolder at level 2 If you overwrite hasStableIds and return true, you will refresh only those that have changed, and not those that have not
    //notifyDataSetChange() calls Observable polling onChanged public void onChanged() { RecyclerView.this.assertNotInLayoutOrScroll((String)null); RecyclerView.this.mState.mStructureChanged = true; RecyclerView.this.processDataSetCompletelyChanged(true); if (! RecyclerView.this.mAdapterHelper.hasPendingUpdates()) { RecyclerView.this.requestLayout(); / / request to layout}} / / call this clear CacheViews if (RecyclerView. This. MAdapter = = null | |! RecyclerView.this.mAdapter.hasStableIds()) { this.recycleAndClearCachedViews(); }Copy the code
    1. When a ViewHolder is retrieved from the level-1 cache during reuse, if the ViewHolder is no longer compatible with the level-1 cache, the ViewHolder will be removed from the level-1 cache and added to mCacheViews
    2. When call removeAnimatingView method, if the current ViewHolder is marked as remove, invoked recycleViewHolderInternal () to recycle the corresponding ViewHolder removeAnimatingView calling Timing indicates that the current ItemAnimator is done
  1. MHiddenViews array
    • The condition for a ViewHolder to recycle into the mHiddenView array is relatively simple. If the current operation supports animation, the addAnimatingView method of RecyclerView will be called. And in this method we’re going to add the View that we’re animating to the mHiddenView array. This is usually done during animation because mHiddenViews only has elements during animation.
  2. RecycleViewPool
    • Is recycleViewPool with mCacheView recycleViewHolderInternal way to recovery, just as do not meet mCacheView condition is that the number is greater than 2, will be placed into the recycleViewPool
  3. Why would overwriting the hasStableIds method to return true be efficient?
    • Reuse: First look at the next() call code
    If (madapter.hasstableids ()) {// If (madapter.hasstableids ()) {// If (madapter.hasstableids ()) {// If (madapter.hasstableids ()) {// If (madapter.hasstableids ()) getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder ! = null) { // update position holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; }}Copy the code
    • Recycling:
    private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); if (viewHolder.shouldIgnore()) { if (DEBUG) { Log.d(TAG, "ignoring view " + viewHolder); } return; } if (viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! MRecyclerView. MAdapter. HasStableIds ()) {/ / if it is true, go below removeViewAt (index); recycler.recycleViewHolderInternal(viewHolder); } else {detachViewAt(index); detachViewAt(index); recycler.scrapView(view); / / recovered to scrap in the array, level 1 cache mRecyclerView. MViewInfoStore. OnViewDetached (viewHolder); }}Copy the code
The data load
  • So if you’re going to get the data from the cache or you’re going to get a ViewHolder from a new onCreateView, you’re going to load the data. From the above analysis, we know that the cache ViewHolder contains data in the first-level and second-level caches, which can be reused directly. However, for the four-level or newly created ViewHolder, it is equivalent to rebuilding a ViewHolder, where there is no data, so we need to bind OnBindViewHolder
    • Is still in recycleView tryGetViewHolderForPositionByDeadline method
// The ViewHolder is bound to a location; MPosition, mItemId, and mItemViewType are all valid. If (mState. IsPreLayout () && holder. IsBound ()) {/ / if the current is bound, don't have in tryBindViewHolderByDeadline - > onBindViewHolder / / 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 + exceptionLabel()); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); / / this method will be called to rewrite onBindViewHolder bound = tryBindViewHolderByDeadline (holder, offsetPosition, position, deadlineNs); Void resetInternal() {mFlags = 0; . Hold.isbound () Boolean isBound() {return (mFlags & FLAG_BOUND)! = 0; // So reconstructs from the viewPool and oncreateView are rebound, and other caches are notCopy the code
  • At this point,recycleView layout and loading data are explained, The layout of the loading is delegated to the LayoutManager implementation, and the data is entrusted to the adapter onBindViewHolder, recycleView only responsible for the viewHolder cache and interaction, the adapter by the observer pattern set observables, once you have Data updates inform the View to redraw the requestLayout
Q&A
  1. NotifyDataSetChanged of RecycleView Indicates the cause of image flashing
    • Using notifyDataSetChanged only, when relayout, remove View first, then cache to mCachedViews, ViewCahceExtension(personal implementation), In RecycleViewPool, the layoutManager.next () method is used to reuse the value in recycleView. If the layoutManager.next () method is not available in the pool, oncreateViewHolder is called to re-inflate the View. Lead to flash
    • Use notifyDataSetChanged() + hasStableIds() true, + duplicate getItemId() : to prevent duplicate jitter or refresh of single entries, mAttachedScrap will be cached, so no recreateViewholder will be created