[Android Framework シリーズ] 第 12 章 RecycleView 関連の原則と 4 レベルのキャッシュ戦略分析

1 RecyclerView の概要

RecyclerViewwidgetは、リスト データを柔軟に表示できる非常に強力なツールです。を学び始めたときRecyclerView、複雑なリスト インターフェイスには利用できるリソースがたくさんあるのに、単純なリスト表示にはリソースがほとんどないことがわかりました。RecyclerViewこのシステムの構造は一見複雑に見えますが、深く理解すると非常にシンプルで明快であることがわかります一般的にディスプレイリストコントロール
RecyclerViewとして使用され、多くの優れたパフォーマンスを持っています。Androidリサイクル プール戦略では、遅延なく数億のデータをロードでき、アダプター モードではあらゆる表示要件を表示できます。
RecyclerViewベルトコンベアのように、ベルトコンベアの原理を活かして、常にユーザーが目にしているデータだけがメモリにロードされ、目に見えないデータはロードを待っています。ベルトコンベアは10億レベルの商品を連続的に搬送でき、RecyclerView10億レベルの商品を積載した状態で表示することもできますItem

1.3 RecyclerView アーキテクチャのコア コンポーネント

  1. 回收池: 任意のItemコントロールをリサイクルし、Itemその型に適合するコントロールを返すことができます。
    たとえば、onBinderViewHodlerメソッドの最初のパラメータはリサイクル プールから返されます。
  2. 适配器: リスト表示の実装を支援するアダプター インターフェイス、RecyclerViewユーザー インターフェイスの表示と操作を分離するアダプター モード
  3. RecyclerView: これは、主に境界値の判断を実現するためのタッチ イベントの相互作用であり、ユーザーのタッチ フィードバックに従って、リサイクル プール オブジェクトとアダプター オブジェクト間の作業を調整します。

私たちは次のような質問をしながら勉強しますRecyclerView

  1. ListviewRecycerviewキャッシュの違い
  2. RecyclerView抜け出したやつはどこへView行ったの?
  3. RecyclerViewリサイクルプールの再利用方法View
  4. RecyclerView4 レベルのキャッシュ メカニズム

1.4 RecyclerView スライド関連

ご存知のとおり、リストRecyclerViewを実装するパフォーマンスandroidは非常に優れています。パフォーマンスが良い理由は何でしょうか? 鍵となるのは、view加工時にリサイクルして再利用することです。リストがスライドするとリサイクルされて再利用されるので、スライドコールバックから解析してitemViewみましょうonTouchEvent

1.4.1 基本概念

  1. ViewHolder: View のコンテナ。View は ViewHolder に対応します。
  2. Recycler: RecyclerView の内部クラス。主に View のリサイクルと再利用を担当します。
  3. LinearLayoutManager: RecyclerView の線形レイアウト マネージャー

1.4.2 スライド時の関数呼び出しチェーン

ここに画像の説明を挿入
ここでは、後で 4 レベル キャッシュの分析に関連するアイデアを理解するのに役立つように、スライド ダウン時の関数呼び出しチェーンについて一般的に理解しています。

1.4.3 onMeasrueの初期化

RecyclerView開発者は、レイヤー「wrap_content」または「match_parent」の幅と高さを設定することを好みます。したがって、実際のコンテンツを通じて RecyclerView の高さを決定する必要があります
情况1。項目の数が不十分な場合、たとえば、RecyclerView が 2 つの項目しか読み込まない場合、子コントロールの合計の高さによって測定された高さが優先されます
情况2。項目の数が実際の画面の高さを超えている場合は、match_parent が優先されます。つまり、最大の高さになります。

1.4.4 onLayout

RecyclerViewViewGroup から継承されたコンテナ クラス コントロールとして。サブコントロールを正しく配置するには、onLayout メソッドを実装する必要があります。手書きの RecyclerVIew は垂直であるため、配置は上から下になります。同時に、すべての項目をメモリにロードしないように、正確な制御も必要です

1.4.5 イベントのインターセプト

RecyclerViewコンテナ コントロールとして、スライド イベントをインターセプトする必要があります。ユーザーが指をスライドすると、すべてのサブアイテムがスライドします。スライド中にサブアイテムはイベントを受け取ることはできません。RecyclerVIew が静止しているとき、子項目はクリック イベントを受け取る必要があります。

2 RecyclerView アダプターとリサイクル プールの動作メカニズム

RecyclerViewここではまず、関連する読み込みロジックを図の形式で理解します。

2.1 RecyclerView で読み込まれる最初の画面

ここに画像の説明を挿入

2.2 RecyclerView の 2 番目の画面

ここに画像の説明を挿入

2.3 リサイクル池のリサイクル戦略

ここに画像の説明を挿入

2.4 リサイクルプール充填戦略

ここに画像の説明を挿入

2.5 リカバリプールの設計

ここに画像の説明を挿入

3 Recycler リサイクルと再利用の表示

ここに画像の説明を挿入

3.1 主要な回復方法の分析

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 主要な再利用方法の分析

tryGetViewHolderForPositionByDeadline
1次キャッシュから取得 mChangeScrap 2
次キャッシュから取得 mCachedViews 3
次キャッシュから取得 mViewCacheExtension 4次キャッシュ
から取得 キャッシュプール キャッシュ
内の値を取得できない場合は、直接作成
バインドされていない、古い、バインドする

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 レベル4キャッシュ機構

4.1 レベル 1 キャッシュ - キャッシュの断片化

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 二次キャッシュ - キャッシュリスト

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 レベル 3 キャッシュ - カスタム キャッシュ

このレベルのキャッシュはユーザー定義であり、ここでは詳しく説明しません。

4.4 レベル 4 キャッシュ - キャッシュ プール

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;
 }

おすすめ

転載: blog.csdn.net/u010687761/article/details/132525589