Imaginemos que ya ha optimizado su reciclador por dentro y por fuera:
setHasFixedSize (verdadero)
DiffUtil
las vistas son más planas que la tierra
la unión más rápida de espectadores en el salvaje oeste
Pero esto no es suficiente para ti y sigues buscando formas de optimizar. ¡Felicitaciones, ha llegado al artículo correcto!
Fondo
- , , , - viewType
(, , ), , , Chet Haase , . , ... .
RecyclerView.RecycledViewPool
, off the main thread , .. .
, , , , .
: (Supplier), — (Consumer).
RecyclerView.RecycledViewPool
, RecyclerView.RecycledViewPool
, Consumer.
RecycledViewPool
— , viewType
, . , RecycledViewPool
RecyclerView
. RecycledViewPool
, — , , GapWorker
, "", , viewType
.
Consumer
Consumer
class PrefetchViewPool(
private val defaultMaxRecycledViews: Int,
private val viewHolderSupplier: ViewHolderSupplier
) : RecyclerView.RecycledViewPool() {
private val recycledViewsBounds = mutableMapOf<Int, Int>()
init {
attachToPreventFromClearing()
viewHolderSupplier.viewHolderConsumer = ::putViewFromSupplier
viewHolderSupplier.start()
}
fun setPrefetchBound(viewType: Int, count: Int) {
recycledViewsBounds[viewType] = max(defaultMaxRecycledViews, count)
viewHolderSupplier.setPrefetchBound(viewType, count)
}
override fun putRecycledView(scrap: RecyclerView.ViewHolder) {
val viewType = scrap.itemViewType
val maxRecycledViews = recycledViewsBounds.getOrPut(viewType) { defaultMaxRecycledViews }
setMaxRecycledViews(viewType, maxRecycledViews)
super.putRecycledView(scrap)
}
override fun getRecycledView(viewType: Int): RecyclerView.ViewHolder? {
val holder = super.getRecycledView(viewType)
if (holder == null) viewHolderSupplier.onItemCreatedOutside(viewType)
return holder
}
override fun clear() {
super.clear()
viewHolderSupplier.stop()
}
private fun putViewFromSupplier(scrap: RecyclerView.ViewHolder, creationTimeNanos: Long) {
factorInCreateTime(scrap.itemViewType, creationTimeNanos)
putRecycledView(scrap)
}
}
, , RecyclerView.RecycledViewPool
— ( GapWorker
, Supplier)
, API , ... .
fun setPrefetchBound(viewType: Int, count: Int) {
recycledViewsBounds[viewType] = max(defaultMaxRecycledViews, count)
viewHolderSupplier.setPrefetchBound(viewType, count)
}
, ViewHolderSupplier
, setPrefetchBound
, .
, , , , .
override fun putRecycledView(scrap: RecyclerView.ViewHolder) {
val viewType = scrap.itemViewType
val maxRecycledViews = recycledViewsBounds.getOrPut(viewType) { defaultMaxRecycledViews }
setMaxRecycledViews(viewType, maxRecycledViews)
super.putRecycledView(scrap)
}
, , , / , Supplier , .
override fun getRecycledView(viewType: Int): RecyclerView.ViewHolder? {
val holder = super.getRecycledView(viewType)
if (holder == null) viewHolderSupplier.onItemCreatedOutside(viewType)
return holder
}
Supplier, , Consumer' :
init {
attachToPreventFromClearing()
viewHolderSupplier.viewHolderConsumer = ::putViewFromSupplier
viewHolderSupplier.start()
}
private fun putViewFromSupplier(scrap: RecyclerView.ViewHolder, creationTimeNanos: Long) {
factorInCreateTime(scrap.itemViewType, creationTimeNanos)
putRecycledView(scrap)
}
override fun clear() {
super.clear()
viewHolderSupplier.stop()
}
factorInCreateTime
— , , GapWorker
, - .
Supplier
Supplier
typealias ViewHolderProducer = (parent: ViewGroup, viewType: Int) -> RecyclerView.ViewHolder
typealias ViewHolderConsumer = (viewHolder: RecyclerView.ViewHolder, creationTimeNanos: Long) -> Unit
abstract class ViewHolderSupplier(
context: Context,
private val viewHolderProducer: ViewHolderProducer
) {
internal lateinit var viewHolderConsumer: ViewHolderConsumer
private val fakeParent: ViewGroup by lazy { FrameLayout(context) }
private val mainHandler: Handler = Handler(Looper.getMainLooper())
private val itemsCreated: MutableMap<Int, Int> = ConcurrentHashMap<Int, Int>()
private val itemsQueued: MutableMap<Int, Int> = ConcurrentHashMap<Int, Int>()
private val nanoTime: Long get() = System.nanoTime()
abstract fun start()
abstract fun enqueueItemCreation(viewType: Int)
abstract fun stop()
protected fun createItem(viewType: Int) {
val created = itemsCreated.getOrZero(viewType) + 1
val queued = itemsQueued.getOrZero(viewType)
if (created > queued) return
val holder: RecyclerView.ViewHolder
val start: Long
val end: Long
try {
start = nanoTime
holder = viewHolderProducer.invoke(fakeParent, viewType)
end = nanoTime
} catch (e: Exception) {
return
}
holder.setItemViewType(viewType)
itemsCreated[viewType] = itemsCreated.getOrZero(viewType) + 1
mainHandler.postAtFrontOfQueue { viewHolderConsumer.invoke(holder, end - start) }
}
internal fun setPrefetchBound(viewType: Int, count: Int) {
if (itemsQueued.getOrZero(viewType) >= count) return
itemsQueued[viewType] = count
val created = itemsCreated.getOrZero(viewType)
if (created >= count) return
repeat(count - created) { enqueueItemCreation(viewType) }
}
internal fun onItemCreatedOutside(viewType: Int) {
itemsCreated[viewType] = itemsCreated.getOrZero(viewType) + 1
}
private fun Map<Int, Int>.getOrZero(key: Int) = getOrElse(key) { 0 }
}
, , — Supplier. — viewType
, - Consumer. , , , , .
. , . — , , count
:
internal fun setPrefetchBound(viewType: Int, count: Int) {
if (itemsQueued.getOrZero(viewType) >= count) return
itemsQueued[viewType] = count
val created = itemsCreated.getOrZero(viewType)
if (created > count) return
repeat(count - created) { enqueueItemCreation(viewType) }
}
enqueueItemCreation
— .
, , createItem
, - - enqueueItemCreation
. , , , , - ? , , , ( ). viewType
, , , viewType
Consumer' .:
protected fun createItem(viewType: Int) {
val created = itemsCreated.getOrZero(viewType) + 1
val queued = itemsQueued.getOrZero(viewType)
if (created > queued) return
val holder: RecyclerView.ViewHolder
val start: Long
val end: Long
try {
start = nanoTime
holder = viewHolderProducer.invoke(fakeParent, viewType)
end = nanoTime
} catch (e: Exception) {
return
}
holder.setItemViewType(viewType)
itemsCreated[viewType] = itemsCreated.getOrZero(viewType) + 1
mainHandler.postAtFrontOfQueue { viewHolderConsumer.invoke(holder, end - start) }
}
viewHolderProducer
typealias
:
typealias ViewHolderProducer = (parent: ViewGroup, viewType: Int) -> RecyclerView.ViewHolder
( RecyclerView.Adapter.onCreateViewHolder
, )
, LayoutInflater.inflate
— ...
Supplier
Supplier ( onItemCreatedOutside
) — :
internal fun onItemCreatedOutside(viewType: Int) {
itemsCreated[viewType] = itemsCreated.getOrZero(viewType) + 1
}
Profit!
start
, stop
enqueueItemCreation
, /, ...
, , , ViewHolderSupplier
, , , , , core
, , (Kotlin Coroutines, RxJava2, RxJava3, Executor).