美文网首页
RecyclerView

RecyclerView

作者: 竹叶儿青 | 来源:发表于2020-07-02 20:53 被阅读0次

    1. 前置知识

    • Recycle (view): A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction.

    • Recycle是一个旧的用于展示adapter特定位置的数据的view,存放到cache中,后面会被重用,用于展示同样类型的数据。使用Recycle可以提高性能,因其跳过(避免)了一些布局的初始化操作。

    • Scrap (view): A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty.

    • Scrap的名词意思是“碎片,残羹剩饭”,动词意思是“废弃,抛弃”。Scrap指已经进入暂时的detached状态的view,当满足以下3个条件之一时会重用:

      1. 还没有彻底的从父RecyclerView中detached;
      2. 不需要rebind,因此没有修改;
      3. view被视为dirty而被adapter修改了。
    • Dirty (view): A child view that must be rebound by the adapter before being displayed.

    • Dirty指必须重新绑定的view。

    • layout position: Position of an item in the latest layout calculation. This is the position from the LayoutManager's perspective.

    • adapter position: Position of an item in the adapter. This is the position from the Adapter's perspective.

    • These two positions are the same except the time between dispatching adapter.notify* events and calculating the updated layout.
      两种不同的position,大部分时间都是相同的,仅在调用adapter的nofity*方法时和计算更新的布局时不同。

    2. 复用

    复用的是ViewHolder,关键部分在RecyclerView内部类Recycler的 tryGetViewHolderForPositionByDeadline方法。
    看一下Recycler类的几个关键成员变量:

     public final class Recycler {
            final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
            ArrayList<ViewHolder> mChangedScrap = null;
    
            final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    
            private final List<ViewHolder>
                    mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    
            private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
            int mViewCacheMax = DEFAULT_CACHE_SIZE;
    
            RecycledViewPool mRecyclerPool;
    
            private ViewCacheExtension mViewCacheExtension;
    
            static final int DEFAULT_CACHE_SIZE = 2;
    

    关键方法:

    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) {
                        // We are NOT sending the offsetPosition because LayoutManager does not
                        // know it.
                        final View view = mViewCacheExtension
                                .getViewForPositionAndType(this, position, type);
                        ...
                       if (holder == null) { // fallback to pool
                        holder = getRecycledViewPool().getRecycledView(type);
                         ...
                       if (holder == null) {
                        long start = getNanoTime();
                        if (deadlineNs != FOREVER_NS
                                && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                            // abort - we have a deadline we can't meet
                            return null;
                        }
                        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    

    总的来说,复用有6层:

    1. If there is a changed scrap, try to find from there
      从changedScrap获取
    2. Find by position from scrap/hidden list/cache
      根据position从scrap/hidden list/cache获取,用到mAttachedScrap、mCachedViews
    3. Find from scrap/cache via stable ids, if exists
      根据stable id从scrap/cache获取,用到mAttachedScrap、mCachedViews
    4. 从mViewCacheExtension获取,这个是用户自定义的
    5. fallback to pool
      从RecycledViewPool中获取
    6. 调用mAdapter.createViewHolder(RecyclerView.this, type)创建ViewHolder

    3. 回收

    回收的同样是ViewHolder。
    关键在LayoutManager的removeAndRecycleViewAt方法:

    public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
                final View view = getChildAt(index);
                removeViewAt(index);
                recycler.recycleView(view);
            }
    
      public void recycleView(@NonNull View view) {
                // This public recycle method tries to make view recycle-able since layout manager
                // intended to recycle this view (e.g. even if it is in scrap or change cache)
                ViewHolder holder = getChildViewHolderInt(view);
                if (holder.isTmpDetached()) {
                    removeDetachedView(view, false);
                }
                if (holder.isScrap()) {
                    holder.unScrap();
                } else if (holder.wasReturnedFromScrap()) {
                    holder.clearReturnedFromScrapFlag();
                }
                recycleViewHolderInternal(holder);
    
     void recycleViewHolderInternal(ViewHolder holder) {
      ...
     int cachedViewSize = mCachedViews.size();
                        if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                            recycleCachedViewAt(0);
                            cachedViewSize--;
                        }
      ...
     mCachedViews.add(targetCacheIndex, holder);
    
     if (!cached) {
              addViewHolderToRecycledViewPool(holder, true);
              recycled = true;
                    }
    

    总的来说,回收有以下3步:

    1. 如果mCachedViews的size超过了mViewCacheMax,则将最久远的viewholder,即position为0的,放入RecycledViewPool中;
    2. 将当前viewHolder插入mCachedViews的适当位置,放在最近使用过的view后面;
    3. 如果没有缓存,则直接加入RecycledViewPool中。

    相关文章

      网友评论

          本文标题:RecyclerView

          本文链接:https://www.haomeiwen.com/subject/auelqktx.html