文章目錄
歡迎大家入坑.
大家好,我是冰雪情緣,已經在 Android TV開發爬坑多年,也是一名TV開發開源愛好者.
Android TV 開源社區 https://gitee.com/kumei/Android_tv_libs
Android TV 文章專題 https://www.jianshu.com/c/3f0ab61a1322
Android TV QQ羣1:522186932 QQ羣2:468357191
寫在前面
不想看原理的小夥伴,可以直接去看後面優化相關的
爲何要了解ReycclerView的緩存機制,
第一,能更合理的使用緩存,保證應用的流暢性,低耗能;
第二,優化的能更到位;
第三,基礎更紮實,後續 提升技術能力的基石.
當你滿心歡喜 寫了一個類似上圖的 幾個 界面,但是手勢或遙控器 操作的時候,不是卡頓,就是內存,CPU爆炸,是不是一臉矇蔽,然後又無從下手,有那麼幾個函數,又不知道如何使用,性能優化也更無從下手,那麼我們進入正題吧,先去瞭解緩存
,再來根據緩存的知識點,進行優化的知識瞭解
。
理解緩存
簡單瞭解RecyclerView的流程
我們知道 RecyclerView 的優化 最重要是 減少 onCreateViewHolder(縮減createVH)
, onBindViewHolder(縮減bindVH)
的 耗時(時間)
和 調用次數
,下面我們將圍繞着這兩個東西來講解一些的 優化
事宜與相關的 緩存
知識.
先看看 RecyclerView 整個流程的簡單介紹的示意圖:
RecyclerView 包含了很多東西,比如 ItemDecoration,ItemAnimator,LayoutManger,Recycler
,ViewHolder,Adapter… …
這裏我們主要講解的是 Recycler
這個東西,它是什麼?
Recycler
主要是負責 ViewHolder 的創建
與複用(緩存的管理)
。
可以看看一張ReyclerVie的類圖,大概瞭解下他們之間的關係:
先簡單的看看幾個相關的函數:
RecyclerView相關函數 | Recycler 相關函數 | 含義 |
---|---|---|
setItemViewCacheSize | setViewCacheSize | 設置 cacheView緩存大小,默認2個 |
setRecycledViewPool | 設置緩存池(RecycledViewPool),每種類型默認5個 | |
setViewCacheExtension | 設置自定義緩存 | |
detachAndScrapAttachedViews | ||
setRecyclerListener | 設置緩存回調 | |
getViewForPosition | 獲取ViewHolder,從緩存中獲取,沒有則創建 | |
bindViewToPosition | 綁定 ViewHolder,會調用 Adapter.bindViewHolder -> onBindViewHolder |
有沒有思考過,setItemViewCacheSize 什麼時候設置,設置多少個,過程是如何的?
包括 setRecycledViewPool 也是,對於 createView, bindView 他們又有什麼影響?共享 RecycledViewPool 改如何去弄?
如何加快我們的界面加載速度以及複用?
setViewCacheExtension 什麼時候使用?
notify…刷新界面爲何還是要重新創建或者綁定,到底這麼回事?
獲取ViewHolder的流程
先簡單的介紹下獲取ViewHolder的流程
onLayoutChildre 會調用 detachAndScrapAttachedViews
分離 ViewHolder 存儲到相應的緩存(Recycler)
中去
這裏拿出了 LinearLayoutManger 佈局的流程(簡單的過程):
// layoutChunk 代碼
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
}
// LayoutState next 代碼
View next(RecyclerView.Recycler recycler) {
// getViewForPosition 這裏就是獲取 ViewHolder 的過程
// Recycler 主要是負責 ViewHolder 的 創建 與 複用(緩存的管理)
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
那我們先來看看getViewForPosition獲取ViewHolder的流程吧:
getViewForPosition 這個函數就是從 Recycler 取出 ViewHolder 交給 Adapter.
getViewForPosition 做了哪些事情?按照上面流程我們大概的瞭解到,就是將 Recycler 緩存中對應的 ViewHolder取出來,沒有則重新調用 createVH
創建ViewHolder.
Recylcer 緩存包含了(ChangedScrap
,AttachedScrap
,CachedViews
,ViewCacheExtension
,RecyclerPool
),後面會詳解去了解緩存相關的知識,我們先來看看代碼的過程
:
// 按照流程圖一步步看下去,我們一共分成了 7 步
ViewHolder holder = null;
// 第 1 步: 根據 position 獲取 changedScrap(被更新的ViewHolder)
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 第 2 步:根據 position 獲取 AttachedScrap(還在屏幕中的ViewHolder),CachedViews(緩存離開屏幕的viewHolder)
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
// 第 3 步:根據 ItemId 獲取 AttachedScrap,CachedViews
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
// 判斷是否開啓了 StableIds,如果要開啓,需要調用 Adapter.setHasStableIds(true)
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
}
}
// 第 4 步:根據 position, type 從 自定義緩存 獲取 ViewHolder
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
// 第 5 步:根據 type 從 RecycledViewPool 獲取對應的 ViewHolder
if (holder == null) {
holder = getRecycledViewPool().getRecycledView(type);
}
// 第 6 步:創建 ViewHolder
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
// 第 7 步:判斷是否需要 bindViewHolder
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// 判斷 是否bind過 或 是否已更新 或 是否失效
// tryBindViewHolderByDeadline
mAdapter.bindViewHolder(holder, offsetPosition);
}
上面已經大概的講解了 getViewForPosition 從 Recycler 獲取 ViewHolder 的過程,
這裏我們大概瞭解下 Recycler
的幾個緩存變量:
public final class Recycler {
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private ViewCacheExtension mViewCacheExtension;
private RecycledViewPool mRecyclerPool;
}
這幾個緩存變量有什麼區別?
這是優先級最高的緩存,RecyclerView在獲取ViewHolder時,優先會到這 mAttachedScrap 與 mChangedScrap 兩個緩存來找。其次纔是 mCachedViews,最後纔是RecyclerViewPool。
列出一個表格說明對應的意思(需要注意create,bind,優化相關):
緩存級別 | createVH |
bindVH |
變量 | 含義 |
---|---|---|---|---|
一級緩存(Scrap View) | 否 | 否 | mAttachedScrap | mAttachedScrap存儲的是當前還在屏幕中的ViewHolder。匹配機制按照position和id進行匹配 |
一級緩存(Scrap View) | 否 | 是 | mChangedScrap | mChangedScrap存儲的是數據被更新的ViewHolder,比如說調用了Adapter的 notifyXXX 方法 |
二級緩存(Cache View) | 否 | 否 | mCachedViews | 默認大小爲2,緩存離開屏幕的viewHolder. 解決兩點: 1. 頻繁進入/離開屏幕的ViewHolder導致的內存抖動的問題;2.還有用於保存Prefetch的ViewHoder. |
三級緩存(可選可配置) | 否 | 否 | ViewCacheExtension | 自定義緩存,通常用不到,getViewForPositionAndType 來實現自己的緩存 使用場景:位置固定 內容不變 數量有限 |
四級緩存(緩存池) | 否 | 是 | RecyclerViewPool | 根據ViewType來緩存ViewHolder,每個ViewType的數組大小默認爲5,可以動態的改變 緩存的ViewHolder需要重新綁定(bindView). 也可以 RecyclerView之間共享ViewHolder的緩存池Pool. |
其實再 bindView之前,有一個判斷 !holder.isBound() || holder.needsUpdate() || holder.isInvalid()
,這幾個是什麼意思呢?
判斷是否要重新綁定 ViewHolder,holder.isBound() || holder.needsUpdate() || holder.isInvalid(),下列看看全部相關意義。
ViewHolder的 isInvalid
、isRemoved
、isBound
、isTmpDetached
、isScrap
和 isUpdated
這幾個方法:
方法名 | 對應的Flag | 含義或者狀態設置的時機 |
---|---|---|
isInvalid |
FLAG_INVALID | 表示當前ViewHolder是否已經失效。通常來說,在3種情況下會出現這種情況:1.調用了Adapter的notifyDataSetChanged方法; 2. 手動調用RecyclerView的invalidateItemDecorations方法; 3. 調用RecyclerView的setAdapter方法或者swapAdapter方法。 |
isRemoved | FLAG_REMOVED | 表示當前的ViewHolder是否被移除。通常來說,數據源被移除了部分數據,然後調用Adapter的notifyItemRemoved方法。 |
isBound |
FLAG_BOUND | 表示當前ViewHolder是否已經調用了onBindViewHolder。 |
isTmpDetached | FLAG_TMP_DETACHED | 表示當前的ItemView是否從RecyclerView(即父View)detach掉。通常來說有兩種情況下會出現這種情況:1.手動了RecyclerView的detachView相關方法;2. 在從mHideViews裏面獲取ViewHolder,會先detach掉這個ViewHolder關聯的ItemView |
isScrap | 無Flag來表示該狀態,用mScrapContainer是否爲null來判斷 | 表示是否在mAttachedScrap或者mChangedScrap數組裏面,進而表示當前ViewHolder是否被廢棄。 |
isUpdated |
FLAG_UPDATE | 表示當前ViewHolder是否已經更新。通常來說,在3種情況下會出現情況:1.isInvalid方法存在的三種情況;2.調用了Adapter的onBindViewHolder方法;3. 調用了Adapter的notifyItemChanged方法 |
回收相關API
剛纔在 onLayoutChildre 的 detachAndScrapAttachedViews 這個函數很關鍵,分離了 屏幕上 ViewHolder 存儲到對應的緩存上。這裏再普及下其它相關的函數:
方法名 | 含義 |
---|---|
detachAndScrapAttachedViews(recycler) | detach輕量回收所有View |
detachAndScrapView(view, recycler) | detach輕量回收指定View |
detachView(view); | 超級輕量回收一個View,馬上就要添加回來 |
attachView(view); | 將上個方法detach的View attach回來 |
recycler.recycleView(viewCache.valueAt(i)); | detachView 後 沒有attachView的話 就要真的回收掉他們 |
recycle真的回收一個View ,該View再次回來需要執行onBindViewHolder方法
removeAndRecycleView(View child, Recycler recycler)
removeAndRecycleAllViews(Recycler recycler);
detach 和recycle的時機
一個View只是暫時被清除掉,稍後立刻就要用到,使用detach。它會被緩存進scrapCache
的區域。
一個View 不再顯示在屏幕上,需要被清除掉,並且下次再顯示它的時機目前未知 ,使用remove
。它會被以viewType分組,緩存進RecyclerViewPool
裏。
瞭解detach/attach View
addView和removeView方法,操作容器內的子視圖數組,觸發視圖重繪製
,觸發子視圖attach和detached窗體回調。
addViewInLayout和removeViewInLayou方法,與上面一樣,只是不會重繪視圖
。
attachViewToParent和detachViewFromParent方法,只會操作容器內的子視圖數組
。
注意: 一個View只被detach,沒有被recycle的話,不會放進RecyclerViewPool裏,會一直存在recycler的scrap 中。網上有人的Demo就是如此,因此View也沒有被複用,有多少ItemCount,就會new出多少個ViewHolder。
題外話:想了解的 自定義 Layoutmanger,可以去了解下 onLayoutChildren
進行自定義的佈局之前:
- 調用detachAndScrapAttachedViews方法把屏幕中的Items都分離出來,內部調整好位置和數據後,
detachAndScrapAttachedViews(recycler)這個方法就是將屏幕上可見的view緩存在scrap裏 。
先把所有的View先從RecyclerView中detach掉,然後標記爲"Scrap"狀態,表示這些View處於可被重用狀態(非顯示中)。
實際就是把View放到了Recycler中的一個集合中。
detachAndScrapAttachedViews()會根據情況,將原來的Item View放入Scrap Heap
或Recycle Pool
,從而在複用時提升效率。- 調用 Recycler的getViewForPosition(int position) 方法來獲取,通過addView方法來添加.
- 獲取到Item並重新添加了之後,需要對它進行測量,這時候可以調用measureChild或measureChildWithMargins方法,
- 根據需求來決定使用
layoutDecorated
還是layoutDecoratedWithMargins
方法;在自定義ViewGroup中,layout完就可以運行看效果了,但在LayoutManager還有一件非常重要的事情,就是回收了,我們在layout之後,還要把一些不再需要的Items回收,以保證滑動的流暢度;
自定義LayoutManger,可以瞭解這篇文章
刷新界面-緩存的處理
notifyItemChanged對緩存的影響
同樣只更新pos=1的數據,使用 notifyItemChanged(position)
或 notifyDataSetChanged()
有什麼區別?
notifyItemChanged(position)
notifyDataSetChanged()
名稱 | 區別 |
---|---|
notifyItemChanged(position),notifyItemChanged(int position, Object payload) 等 | 被更新的的item放入了ChangeScrp,不需要createVH ,但是需要bindVH |
notifyDataSetChanged() | 0~4放入了RecyclerPool,不需要createVH ,但是全部需要 bindVH |
滾動-緩存的處理
往上滑動的緩存過程
優化了解
概述
根據 Recycler 獲取或者存儲 緩存的流程,我們知道 RecyclerView 優化 最重要是 減少 createViewHolder, bindViewHolder 的耗時(時間)和調用次數,下面我們將圍繞着兩個東西來講解下一些簡單的優化事宜.
onCreateViewHolder
onBindViewHolder
想詳細瞭解繪製過度,耗時查找等優化事宜,參考這篇博客-性能優化學習筆記.
一些函數相關的Demo 可以看看這個
根據上面的兩個內容再補充一些細節:
- 合理使用緩存設置(setItemViewCacheSize,setViewCacheExtension,setRecycledViewPool)
- 注意耗時操作(尤其是屏幕滾動的時候,儘量 停止加載的操作)
- 減少佈局結構、減少過渡繪製,可以提高item的 measure 與 draw 的效率。也儘量避免多次measure & layout 次數(比如TextView可以進行有效優化)
TextView 可以使用 使用 StaticLayout 或者 DynamicLayout 的自定義 View 來代替它 - 取消默認動畫
mRecyclerView.setItemAnimator(null); 也可以改善一點點. - 調整draw緩存
mRecyclerView.setDrawingCacheEnabled(true); mRecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); - 慎用Alpha(不管是圖片還是View,都需要注意下)
也可以嘗試重寫 View 的 hasOverlappingRendering return false,提升一點點性能 - 儘量使用穩定的高版本RecyclerView,比如新版本(25.1.0 及以上)有 Prefetch 功能
- Item 高度是固定的話,RecyclerView.setHasFixedSize(true)
- onViewRecycled 可以回收一些資源.
- 設置更多預留空間(屏幕顯示範圍之外),重寫 getExtraLayoutSpace
啓動應用後,如果一整屏 item的時候,向下滑動,RecyclerView找不到緩存,它將創建一個新的item,導致有點延時的感覺. - RecyclerView沒有ItemClick方法,根據前面緩存的瞭解,建議 onCreateViewHolder 添加一次,避免多次調用 onBindViewHolder。
- diffutil 可以瞭解下,是一個不錯的工具,可以 判斷兩個數據集的差距
- swapadapter 也可以瞭解下. 以前 setAdapter 是要清空緩存的,可以很好的發揮緩存的性能. 應用場景是兩個數據源有很大的相似部分的情況下。
- linearlayoutmanager的onSaveInstanceState和onRestoreInstanceState可以瞭解下.
- 根據不同的機型(CPU,內存,內存使用情況 等等) 特性 進行對應的 優化策略(空間與時間 不可能達到100%完美平衡)
也可以瞭解下這個幾個函數(onTrimMemory,onLowMemory)
onTrimMemory(int level) :
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: // 你的用戶界面不再可見,此時應該釋放僅僅使用在UI上的大資源
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: // 系統處於低內存狀態,app正在運行且不會被殺死
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: // 系統正處於低內存狀態,app進程位於LRU List開始附近,儘管app進程被殺死的概率低,系統可能已經開始殺在LRU List中的後臺進程,此時應該釋放一些資源,防止被殺的機率
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: // 系統處於更低內存狀態,app正在運行且不會被殺死,可以釋放不用的資源提升app性能
ComponentCallbacks2.TRIM_MEMORY_MODERATE: // 系統正處於低內存狀態,app進程位於LRU List中部附近,如果系統進一步內存緊張,app進程可能會被殺掉
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: // 系統處於極低的內存狀態,app仍然不會被殺死。但如果不是放資源,系統開始殺後臺進程,此時app應該清理一些資源
ComponentCallbacks2.TRIM_MEMORY_COMPLETE: // 系統正處於低內存狀態,app進程是首先被殺進程之一,如果系統現在沒有恢復內存,應該立即釋放對app不重要的所有內容
RecyclerViewPool 注意事項!!!
我在測試代碼裏面 共用了一個 RecyclerViewPool 。
// 這裏需要,因爲使用 RecyclerViewPool,是以空間換時間,
// 需要注意內存情況,在內存低的情況,可以嘗試清理掉.
int max = 8;
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_1, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_2, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_3, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_4, max);
mRecycledViewPool.setMaxRecycledViews(TVUICommonItemView.ITEM_TYPE_5, max);
共用 RecyclerViewPool 注意幾個問題
類型(Type)爲0 的ItemView 還是在調用 onCreateViewHolder 的,一共調用了12次.
因爲並沒有使用 Pool裏面的緩存,爲何,裏面沒有東西哈,取不出來holder = getRecycledViewPool().getRecycledView(type);
,holder 爲 null。所以第一次顯示界面,還是在拼命的創建,這裏讓人很沮喪!!!
所以結論就是,雖然共用了RecyclerViewPool ,第一次加載頁面 或者 頁面快速滾動 又或者 想使用緩存Pool的時候,如果Pool裏面沒有,就需要重新創建。
反觀使用 Leanback 雖然也是共用 RecyclerViewPool,但 在滾動的過程中,如果之前的 Pool沒有緩存對應的類型,也是白塔,還是需要創建大量的ItemView,反正是挺耗時的.
當有多個頁面的切換的時候,如果另一個頁面的 RecyclerView被釋放掉了. 它頁面的之前的View會被清理掉. 下次再創建這個頁面,進入,如果沒有緩存使用,還是會進行創建.
再前面 我們已經分析過,當離開屏幕的Item會保存(putRecycledView)到對應的 類型下的緩存Pool(mScrap)裏面。
如何加速第一次加載的優化
這裏也是用空間換時間,避免 創建視圖耗費的時間,進而可以快速的顯示界面.
提前創建緩衝池的 ViewHolder,這樣可以減少 onCreateViewHolder
,但是還是需要 onBindViewHolder
// 看個簡單的小栗子
recycledViewPool.putRecycledView(adapter.createViewHolder(recyclerView, TYPE_1));
recycledViewPool.putRecycledView(adapter.createViewHolder(recyclerView, TYPE_1));
recycledViewPool.putRecycledView(adapter.createViewHolder(recyclerView, TYPE_2));
第一次進入界面會連續調用 onCreateViewHolder
,因爲 onCreateViewHolder沒有辦法去設置 ItemType,所以需要調用 createViewHolder
注意getItemViewType
當很多不同類型的控件的時候,不設置 ItemType返回的話,或者只返回 position,界面肯定會錯亂,也達不到優化的好處.
在 Adapter 的 getItemViewType 設置對應的類型
@Override
public int getItemViewType(int position) {
return mDatas.get(position).getItemType();
}
因爲緩存裏面會根據 Type 去取:holder = getRecycledViewPool().getRecycledView(type);
所以添加緩存的大小以及類型的也需要注意,和你的不同類型的控件記得要對應上。
mRecycledViewPool.setMaxRecycledViews(ItemView.TYPE_1, 10);
注意getItemId的使用
調用 notifyDataSetChanged 的時候,recyclerView 不知道到底發生了什麼,所以它只能認爲所有的東西都發生了變化,將所有的 viewHolder 都放入到 pool 中。
位置固定,比如,廣告位。
不會改變
數量合理,保存在內存中沒啥關係。
// Adapter.setHasStableIds(true);
// mAdapter.hasStableIds()
// 當設置了以上的 開關後,detachAndScrapAttachedViews -> scrapOrRecycleView 的 recycler.scrapView(view);
// 將緩存到 mAttachedScrap,最後直接使用,不需要創建,綁定數據,非常優化喔!!
public long getItemId(int position) {
Data data = datas.get(position);
return (data.getTitle()).hashCode();
}
// 當你的標題欄被修改了,調用 notifydatasetchanged 刷新.
// 如果沒有被更新,就會使用緩存,相當的優化.
// 或者一般這樣寫
@Override
public long getItemId(int position) {
return position;
}
思考:如果 getScrapOrCachedViewForId 去 獲取(getItemId )的 Id 被改變了,那麼 tryGetViewHolderForPositionByDeadline 接下來如何執行?自己思考下?然後單獨
onViewRecycled的使用
View 被回收的時候調用,所以在這個函數裏面可以釋放資源;
比如釋放一些圖片資源,Glid,ImageView等等.
思考:爲何這裏可以釋放掉不要的東西?
答:因爲 addViewHolderToRecycledViewPool -> dispatchViewRecycled -> mAdapter.onViewRecycled,所以 ViewHolder 加入了 RecycledViewPool,複用的時候,不需要創建View,但是需要重新綁定數據.
CacheView使用注意
CacheView 默認爲2個,主要是用來保存臨時滾動屏幕外的 ViewHolder,主要是爲了避免內存抖動.
比如你上下滾動又回來啦,只是短暫移除屏幕,又馬上使用了,這裏就不需要重新 創建 與 綁定.
CacheView 如果需要使用,其實就是 用空間換時間
. 因爲是實實在在的存在數組裏面的.
RecyclerView的單擊事件如何加?
一般人都會將單擊事件加在 onBindViewHolder裏面,這裏是錯誤的行爲!因爲我們分析過 CacheView默認只有兩個,如果新出的View能用到這個緩存,當然不用重新的 創建,綁定;但是大部分時間都是在使用 RecyclerPool,所以需要綁定數據,這就導致,頻繁的 單擊事件添加,內存抖動,這裏處理的方案是,在 ViewHolder裏面添加。
public static class ColorViewHolder extends RecyclerView.ViewHolder {
public ColorViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), getLayoutPosition(), Toast.LENGTH_LONG).show();
}
});
}
LinearLayoutMaanager.setInitialPrefetchItemCount()
RecyclerView.setHasFixedSize(true)
局部刷新,避免 false 會觸發,requestLayut()會重新走三大流程(onMeasure,onLayout,onDraw)
多個RecyclerView公用 RecyclerViewPool
根據設備的內存,CPU情況自動設定優化策略
參考資料
自定義LayoutManger,可以瞭解這篇文章
http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/
深入淺出RecyclerView
https://kymjs.com/code/2016/07/10/01/
Anatomy of RecyclerView: a Search for a ViewHolder (continued) [需要翻牆]
https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91
RecyclerView緩存原理,有圖有真相
https://juejin.im/post/5b79a0b851882542b13d204b
RecyclerView緩存機制(咋複用?)
https://github.com/MicroKibaco/CrazyDailyQuestion/issues/7
真正帶你搞懂 RecyclerView 的緩存機制
https://zhuanlan.zhihu.com/p/80475040
RecyclerView-LayoutManger相關:
RecyclerView解析之LinearLayoutManager
結束語
由於本人技術水平有限,有問題的地方還望一起探討,學習,互相 進步,謝謝.
不要忘記三連,點贊,關注,收藏.
更歡迎加入TV社區, QQ羣 與 關注TV文章專題.
Android TV 開源社區 https://gitee.com/kumei/Android_tv_libs
Android TV 文章專題 https://www.jianshu.com/c/3f0ab61a1322
Android TV QQ羣1:522186932
QQ羣2:468357191