1. 程式人生 > >Listview與Recycleview的區別-(用法及快取機制)

Listview與Recycleview的區別-(用法及快取機制)

用法上的區別

1、listview的用法

  • 繼承的時BaseAdapter,需要重寫四個方法
  • 不強制使用viewholder
  • 可以直接使用item的點選事件
  • 不用單獨設定分隔線
  • 不可以定向重新整理某一條資料

示例程式碼如下:專案程式碼詳見地址:

public class MyListAdapter<T> extends BaseAdapter {

    private static final String TAG = "MyListAdapter";

    private Context mContext;
//    private int itemViewId;
    private List<T> datas;

    public MyListAdapter(Context mContext, /*int itemViewId,*/ List<T> datas) {
        this.mContext = mContext;
//        this.itemViewId = itemViewId;
        this.datas = datas;
    }

    @Override
    public int getCount() {
        return datas == null ? 0 : datas.size();
    }

    @Override
    public T getItem(int position) {
        return datas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Log.d(TAG, "getView: "+position);
        MyHolder holder;
        if (convertView==null){
            convertView = View.inflate(mContext,R.layout.ada_item,null);
            holder = new MyHolder(convertView);
            convertView.setTag(holder);
        }else {
            holder = (MyHolder) convertView.getTag();
        }

        holder.itemTv.setText((CharSequence) datas.get(position));

        return convertView;
    }


    static class MyHolder {
        @BindView(R.id.item) TextView itemTv;

        public MyHolder(View view) {
            ButterKnife.bind(this,view);
        }
        }
        }

2、recycleview的用法

  • 繼承的是Recycleview.Adapter
  • 必須使用viewholder,封裝了view的複用
  • 使用佈局管理器管理佈局的樣式(橫向、豎向、網格、瀑布流佈局)
  • 點選事件可以使用給控制元件設定點選事件,也可以自定義點選事件。
  • 可以自定義繪製分隔線
  • 可以自定義item刪除增加的動畫效果
  • 可以定向重新整理某一條資料notifyItemChanged等眾多方法

例項程式碼如下:


    private static final String TAG = "MyRecycleAdapter";

    private Context mContext;
    private List<T> datas;  //可換成泛型

    public MyRecycleAdapter(Context mContext, List<T> datas) {
        this.mContext = mContext;
        this.datas = datas;
    }
    static class MyViewHolder extends RecyclerView.ViewHolder{

        @BindView(R.id.item) TextView itemTv;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this,itemView);
        }
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i){

        View inflate = LayoutInflater.from(mContext).inflate(R.layout.ada_item, viewGroup, false);
        return new MyViewHolder(inflate);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, final int i) {
        Log.d(TAG, "onBindViewHolder: "+i);
//        myViewHolder = new MyViewHolder()
        myViewHolder.itemTv.setText((CharSequence) datas.get(i));
        //設定點選事件---或者使用自定義介面實現
        myViewHolder.itemTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "點選了: "+i,Toast.LENGTH_LONG).show();
            }
        });
    }

    @Override
    public int getItemCount() {
        return datas==null ? 0:datas.size();
    }
}
recycleview 使用注意點:
  • 必須給recycleview設定佈局管理方式
  • 滑動方向的item佈局對應屬性(垂直-height,橫向-width)需要設定為wrap_content,否則會出現一個item佔滿整個螢幕的bug

快取上的區別

recycleview的快取機制

總結:
斜體樣式

原始碼分析:

recycleview的快取機制是通過內部類實現的,檢視recycleview中的recycler,宣告如下:

public final class Recycler {
 		//1級快取集合 --mAttachedScrap
        final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList();
        ArrayList<RecyclerView.ViewHolder> mChangedScrap = null;
        //2級快取集合 mCachedViews
        final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList();
        private final List<RecyclerView.ViewHolder> mUnmodifiableAttachedScrap;
        private int mRequestedCacheMax;
        int mViewCacheMax;
        //4級快取view池
        RecyclerView.RecycledViewPool mRecyclerPool;
        //3級快取
        private RecyclerView.ViewCacheExtension mViewCacheExtension;
        static final int DEFAULT_CACHE_SIZE = 2;
}

從getViewForPosition追蹤,最終走的是tryGetViewHolderForPositionByDeadline方法:

 if (RecyclerView.this.mState.isPreLayout()) {
 //mState.isPreLayout()預設為false,只有有動畫時才為true
                    holder = this.getChangedScrapViewForPosition(position);
                    fromScrapOrHiddenOrCache = holder != null;
 }
一級快取mAttached中獲取
二級快取mCachedViews中獲取

開始查詢item:

if (holder == null) {
		//獲取holder----接下來詳細介紹
                    holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                    if (holder != null) {
                    //檢驗holder是不是當前position
                        if (!this.validateViewHolderForOffsetPosition(holder)) {									
                            if (!dryRun) { //dryRun傳過來的值是false
                                holder.addFlags(4);
                                if (holder.isScrap()) {
//不是當前position---從mAttached中刪除                                    RecyclerView.this.removeDetachedView(holder.itemView, false);
                                    holder.unScrap();
                                } else if (holder.wasReturnedFromScrap()) {
                                    holder.clearReturnedFromScrapFlag();
                                }
								//把當前view儲存到mCachedViews或者 addViewHolderToRecycledViewPool中
                                this.recycleViewHolderInternal(holder);
                            }
							//並將holder置空,進入下一級查詢
                            holder = null;
                        } else {
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
                }

獲取holder:getScrapOrHiddenOrCachedHolderForPosition

mCachedViews的預設儲存大小是: DEFAULT_CACHE_SIZE = 2;

int scrapCount = this.mAttachedScrap.size();

            int cacheSize;
            RecyclerView.ViewHolder vh;
            //從mAttached中獲取holder
            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())) {
                ///holder是有效的,並且position相同
                    vh.addFlags(32);
                    return vh;
                }
            }
            
    ===================
    //從mCachedViews中獲取holder
     cacheSize = 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) {		
                ///holder是有效的,並且position相同
                    if (!dryRun) {
                    	//獲取完之後從mCachedViews中移除
                        this.mCachedViews.remove(i);
                    }
                    return holder;
                }
            }
            return null;

從原始碼可以看到,mCacheViews預設快取大小是 2,從mAttached和mCacheViews中取的holder都必須要求position是相同的位置,而且不關心type.

也就是說只有原來的位置可以重新複用這裡的viewholder,新的位置無法從mCachedViews中去viewholder。因此複用時也不需要重新bindView

在取完holder後才判斷位置是否正確,型別是否正確:

 boolean validateViewHolderForOffsetPosition(RecyclerView.ViewHolder holder) {
            if (holder.isRemoved()) {
                return RecyclerView.this.mState.isPreLayout();
            } else if (holder.mPosition >= 0 && holder.mPosition < RecyclerView.this.mAdapter.getItemCount()) {
                if (!RecyclerView.this.mState.isPreLayout()) {
                //type是否核對上
                    int type = RecyclerView.this.mAdapter.getItemViewType(holder.mPosition);
                    if (type != holder.getItemViewType()) {
                        return false;
                    }
                }

                if (RecyclerView.this.mAdapter.hasStableIds()) {
                    return holder.getItemId() == RecyclerView.this.mAdapter.getItemId(holder.mPosition);
                } else {
                    return true;
                }
            } else {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder adapter position" + holder + RecyclerView.this.exceptionLabel());
            }
        }
//position的位置沒有找到,根據Adapter的id再找一便
                int offsetPosition;
                int type;
                if (holder == null) {
                //
                    offsetPosition = RecyclerView.this.mAdapterHelper.findPositionOffset(position);
                    if (offsetPosition < 0 || offsetPosition >= RecyclerView.this.mAdapter.getItemCount()) {
                        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);
                    if (RecyclerView.this.mAdapter.hasStableIds()) {
                    //根據ID找
                        holder = this.getScrapOrCachedViewForId(RecyclerView.this.mAdapter.getItemId(offsetPosition), type, dryRun);
                        if (holder != null) {
                            holder.mPosition = offsetPosition;
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
三級快取:mViewCacheExtension

mViewCacheExtension是Recycleview中提供的一個抽象類,供開發者自定義快取機制。

 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());
                            }
                        }
                    }
四級快取:RecycledViewPool

根據type獲取holder,內部使用的儲存是使用SparseArray,儲存的是包含ViewHolder集合屬性的ScrapData物件。

//預設的快取大小為:DEFAULT_MAX_SCRAP = 5;
@Nullable
//根據type取的viewholder
        public RecyclerView.ViewHolder getRecycledView(int viewType) {
            RecyclerView.RecycledViewPool.ScrapData scrapData = (RecyclerView.RecycledViewPool.ScrapData)this.mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                ArrayList<RecyclerView.ViewHolder> scrapHeap = scrapData.mScrapHeap;
                //通過remove方法拿到的viewholder,把與傳入的type對應的ViewHolder集合中取最後一個ViewHolder來複用
                return (RecyclerView.ViewHolder)scrapHeap.remove(scrapHeap.size() - 1);
            } else {
                return null;
            }
        }
        =================
        //定義儲存的資料結構
         SparseArray<RecyclerView.RecycledViewPool.ScrapData> mScrap = new SparseArray();
           static class ScrapData {
            final ArrayList<RecyclerView.ViewHolder> mScrapHeap = new ArrayList();
            int mMaxScrap = 5;
            long mCreateRunningAverageNs = 0L;
            long mBindRunningAverageNs = 0L;

            ScrapData() {
            }
        }

快取在ViewPool裡的ViewHolder只匹配type。是通過remove方法取的最後一個。

取完ViewHolder後將holder重置,之後ViewHolder就可以作為一個全新的ViewHolder來使用,也就是這個ViewHolder需要重新繫結呼叫:onBindViewHolder()

if (holder == null) {
    holder = this.getRecycledViewPool().getRecycledView(type);
    if (holder != null) {
    	//重置ViewHolder
        holder.resetInternal();
        if (RecyclerView.FORCE_INVALIDATE_DISPLAY_LIST) {
            this.invalidateDisplayListInt(holder);
        }
    }
}
最終建立:createViewHolder
  holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type);

Recycleview的回收機制

由LayoutManager遍歷移出螢幕的卡位,然後對每個卡位進行回收操作,回收時,都是把ViewHolder裝到mCachedViews裡,如果mCachedViews滿了,則將第一個移動ViewPool裡。
在這裡插入圖片描述

原始碼分析

    void recycleViewHolderInternal(RecyclerView.ViewHolder holder) {
        if (!holder.isScrap() && holder.itemView.getParent() == null) {
          .........
                if (forceRecycle || holder.isRecyclable()) {
                    if (this.mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(526)) {
                        int cachedViewSize = this.mCachedViews.size();
                        if (cachedViewSize >= this.mViewCacheMax && cachedViewSize > 0) {                            //(mViewCacheMax 預設是2)如果滿了把第一個移ViewPool中去
                            this.recycleCachedViewAt(0);
                            --cachedViewSize;
                        }

                        int targetCacheIndex = cachedViewSize;
                        if (RecyclerView.ALLOW_THREAD_GAP_WORK && cachedViewSize > 0 && !RecyclerView.this.mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                            int cacheIndex;
                            for(cacheIndex = cachedViewSize - 1; cacheIndex >= 0; --cacheIndex) {
                                int cachedPos = ((RecyclerView.ViewHolder)this.mCachedViews.get(cacheIndex)).mPosition;
                                if (!RecyclerView.this.mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                    break;
                                }
                            }
                            targetCacheIndex = cacheIndex + 1;
                        }
						//新增到mCachedViews中
                        this.mCachedViews.add(targetCacheIndex, holder);
                        cached = true;
                    }
					//新增到 ViewPool中
                    if (!cached) {
                        this.addViewHolderToRecycledViewPool(holder, true);
                        recycled = true;
                    }
                }
				........
            }
        } else {
			......
        }
    }

**注意:**Recycleview是先複用再回收的。

綜上:listview的快取機制比Recycleview的較為簡單一些,當處理輕量級list時recycleBin的快取機制會是的顯示的效率更高,因此,listview還未過時。

後續會更新demo的快取機制分析過程,demo程式碼地址:https://github.com/MarinaTsang/TestDemo

參考部落格:

  1. https://blog.csdn.net/tencent_bugly/article/details/52981210
  2. https://www.jianshu.com/p/e44961f8add5
  3. https://www.cnblogs.com/dasusu/p/7746946.html