1 RecyclerView の概要
RecyclerView
widget
は、リスト データを柔軟に表示できる非常に強力なツールです。を学び始めたときRecyclerView
、複雑なリスト インターフェイスには利用できるリソースがたくさんあるのに、単純なリスト表示にはリソースがほとんどないことがわかりました。RecyclerView
このシステムの構造は一見複雑に見えますが、深く理解すると非常にシンプルで明快であることがわかります。一般的にディスプレイリストコントロール
RecyclerView
として使用され、多くの優れたパフォーマンスを持っています。Android
リサイクル プール戦略では、遅延なく数億のデータをロードでき、アダプター モードではあらゆる表示要件を表示できます。
RecyclerView
ベルトコンベアのように、ベルトコンベアの原理を活かして、常にユーザーが目にしているデータだけがメモリにロードされ、目に見えないデータはロードを待っています。ベルトコンベアは10億レベルの商品を連続的に搬送でき、RecyclerView
10億レベルの商品を積載した状態で表示することもできますItem
。
1.3 RecyclerView アーキテクチャのコア コンポーネント
回收池
: 任意のItem
コントロールをリサイクルし、Item
その型に適合するコントロールを返すことができます。
たとえば、onBinderViewHodler
メソッドの最初のパラメータはリサイクル プールから返されます。适配器
: リスト表示の実装を支援するアダプター インターフェイス、RecyclerView
ユーザー インターフェイスの表示と操作を分離するアダプター モードRecyclerView
: これは、主に境界値の判断を実現するためのタッチ イベントの相互作用であり、ユーザーのタッチ フィードバックに従って、リサイクル プール オブジェクトとアダプター オブジェクト間の作業を調整します。
私たちは次のような質問をしながら勉強しますRecyclerView
。
Listview
とRecycerview
キャッシュの違いRecyclerView
抜け出したやつはどこへView
行ったの?RecyclerView
リサイクルプールの再利用方法View
RecyclerView
4 レベルのキャッシュ メカニズム
1.4 RecyclerView スライド関連
ご存知のとおり、リストRecyclerView
を実装するパフォーマンスandroid
は非常に優れています。パフォーマンスが良い理由は何でしょうか? 鍵となるのは、view
加工時にリサイクルして再利用することです。リストがスライドするとリサイクルされて再利用されるので、スライドコールバックから解析してitemView
みましょうonTouchEvent
1.4.1 基本概念
- ViewHolder: View のコンテナ。View は ViewHolder に対応します。
- Recycler: RecyclerView の内部クラス。主に View のリサイクルと再利用を担当します。
- 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
RecyclerView
ViewGroup から継承されたコンテナ クラス コントロールとして。サブコントロールを正しく配置するには、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;
}