El fantástico RecyclerView.ViewHolder y dónde se crearon

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).








All Articles