前言
在這篇文章中辰斋,我們將學(xué)習(xí)如何優(yōu)化 Android 中的 RecyclerView 性能。通過這些優(yōu)化瘸味,我們可以使 RecyclerView 滾動(dòng)流暢宫仗。
當(dāng)我們?cè)谖覀兊?Android 應(yīng)用程序中實(shí)現(xiàn) RecyclerView 時(shí),有時(shí)我們會(huì)遇到這樣的問題: RecyclerView 項(xiàng)目滾動(dòng)不順暢旁仿。這會(huì)導(dǎo)致糟糕的用戶體驗(yàn)藕夫,因?yàn)槲覀兊?Android 應(yīng)用程序似乎很滯后。
讓我們看看我們可以做些什么來提高 RecyclerView 的性能枯冈,從而獲得平滑的滾動(dòng)毅贮。
RecyclerView 優(yōu)化技巧
讓我們開始吧
使用圖像加載庫
由于垃圾回收(GC)運(yùn)行在主線程上,UI無響應(yīng)的原因之一是內(nèi)存的不斷分配和釋放尘奏,導(dǎo)致GC運(yùn)行非常頻繁滩褥。通過使用位圖池的概念,我們可以避免它炫加。
最好的部分是像 Glide瑰煎、Fresco 這樣的 Image-Loading 庫使用了這個(gè)位圖池概念。因此琢感,請(qǐng)始終使用圖像加載庫丢间。
將所有與圖像相關(guān)的任務(wù)委托給這些庫探熔。
設(shè)置圖像寬度和高度
如果我們的圖像寬度和高度是動(dòng)態(tài)的(不是固定的)驹针,并且我們imageUrl從服務(wù)器獲取。
如果我們事先沒有設(shè)置正確的圖像寬度和高度诀艰,UI 會(huì)在加載(下載圖像)和設(shè)置圖像到 ImageView 的過渡過程中閃爍(實(shí)際上是在下載完成時(shí)使其可見)柬甥。
所以饮六,我們應(yīng)該要求我們的后端開發(fā)者發(fā)送圖像大小或縱橫比,據(jù)此苛蒲,我們可以計(jì)算出所需的 ImageView 的寬度和高度卤橄。
然后,我們將只能先設(shè)置寬度和高度臂外。因此沒有閃爍窟扑。問題解決了!
少做 onBindViewHolder 方法
我們的 onBindViewHolder 方法應(yīng)該做的工作很少漏健。我們應(yīng)該檢查我們的 onBindViewHolder 方法并對(duì)其進(jìn)行優(yōu)化嚎货。通過這樣做,我們可以看到 RecyclerView 的改進(jìn)蔫浆。
使用通知項(xiàng)目 RecyclerView API
每當(dāng)我們有刪除殖属、更新、添加項(xiàng)目的用例時(shí)瓦盛,使用通知項(xiàng)目 API洗显。
<pre class="public-DraftStyleDefault-pre" data-offset-key="7inds-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="b345q" data-offset-key="7inds-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
adapter.notifyItemRemoved(position)
adapter.notifyItemChanged(position)
adapter.notifyItemInserted(position)
adapter.notifyItemRangeInserted(start, end)
</pre>
</pre>
另外,檢查DiffUtil原环。
萬一我們被迫使用notifyDataSetChanged()基于我們的用例挠唆,我們可以嘗試setHasStableIds(true).
<pre class="public-DraftStyleDefault-pre" data-offset-key="rvf3-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="b345q" data-offset-key="rvf3-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
adapter.setHasStableIds(true)
</pre>
</pre>
它指示數(shù)據(jù)集中的每個(gè)項(xiàng)目是否可以用 Long 類型的唯一標(biāo)識(shí)符表示。
即使我們調(diào)用notifyDataSetChanged()嘱吗,它也不必處理整個(gè)適配器的完全重新排序损搬,因?yàn)樗梢哉业侥硞€(gè)位置的項(xiàng)目是否與以前相同并且做更少的工作。
避免嵌套視圖
如果可能柜与,我們應(yīng)該避免嵌套視圖巧勤,并盡可能創(chuàng)建平面視圖。嵌套會(huì)降低 RecyclerView 的性能弄匕。平面視圖提高了性能颅悉。
使用 setHasFixedSize
如果所有項(xiàng)目的高度相等,我們應(yīng)該使用這種方法迁匠。添加以下內(nèi)容并檢查性能剩瓶。
<pre class="public-DraftStyleDefault-pre" data-offset-key="c13hk-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="b345q" data-offset-key="c13hk-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
recyclerView.setHasFixedSize(true)
</pre>
</pre>
使用 setRecycledViewPool 優(yōu)化嵌套 RecyclerView
眾所周知,RecyclerView 盡可能地遵循“Reuse View Using Pool”的原則城丧。
檢查 RecyclerView 如何在內(nèi)部工作延曙?
如果我們有嵌套 RecyclerView 的用例。
<pre class="public-DraftStyleDefault-pre" data-offset-key="8lih4-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
<pre class="Editable-styled" data-block="true" data-editor="b345q" data-offset-key="8lih4-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">
- OuterRecyclerView
- InnerRecyclerViewOne
- InnerRecyclerViewTwo
</pre>
</pre>
但默認(rèn)情況下亡哄,優(yōu)化適用于特定的 RecyclerView枝缔,因?yàn)樘囟ǖ?RecyclerView 有自己的視圖池。
池不會(huì)在具有相同視圖類型的兩個(gè) RecyclerView 之間共享。
所以愿卸,我們可以做的是灵临,我們可以創(chuàng)建一個(gè) ViewPool 并將其傳遞給所有內(nèi)部的 RecyclerViews,以便它像下面這樣共享:
class OuterRecyclerViewAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// code removed for brevity
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// code removed for brevity
viewHolderOne.innerRecyclerViewOne.setRecycledViewPool(viewPool)
// code removed for brevity
viewHolderTwo.innerRecyclerViewTwo.setRecycledViewPool(viewPool)
}
// code removed for brevity
}
這將提高滾動(dòng)性能趴荸,因?yàn)樗鼘㈤_始重用共享 ViewPool 中的視圖儒溉。
使用 setItemViewCacheSize
我們可以通過設(shè)置 ItemView Cache Size來進(jìn)行實(shí)驗(yàn)。
recyclerView.setItemViewCacheSize(cacheSize)
根據(jù)官方文檔:它設(shè)置在將它們添加到可能共享的回收視圖池之前要保留的屏幕外視圖的數(shù)量发钝。屏幕外視圖緩存保持對(duì)附加適配器中的更改的感知顿涣,允許 LayoutManager 重用這些未修改的視圖,而無需返回到適配器重新綁定它們酝豪。
這意味著當(dāng)我們滾動(dòng) RecyclerView 以使視圖幾乎完全脫離屏幕時(shí)园骆,RecyclerView 將保留它,以便我們可以將其滾動(dòng)回視圖而無需onBindViewHolder()再次調(diào)用寓调。
一般來說锌唾,我們不會(huì)改變 View Cache 的大小,而是嘗試一下夺英,如果它適合你晌涕,就實(shí)現(xiàn)它。
這些是我們可以做的事情來提高 RecyclerView 的性能⊥疵酰現(xiàn)在就是這樣余黎。
作者:Amit Shekhar
鏈接:https://blog.mindorks.com/recyclerview-optimization