异步加载分部视图_异步视图持有者创建的回收者视图功能

异步加载分部视图

Recycler solves a lot of the developer’s problems and it becomes smarter and better every release. Mostly, it is enough to use it the right way, but sometimes flags juggling and views hierarchy optimization — are not a cure. You have to fight for your lists performance.

Recycler解决了许多开发人员的问题,并且每个版本都变得越来越聪明和更好。 通常,以正确的方式使用它就足够了,但是有时标记变戏法和视图层次结构优化是无法解决的。 您必须为列表性能而战。

1. 关于回收者观点的几句话 (1. Few words about recycler view)

Take a look at the image below. The stages of view holder are there. You can google recycler view principle if you need.

看看下面的图片。 视图持有者的阶段就在那里。 您可以根据需要使用Google Recycler视图原理。

Image for post

Take a look at the image below. The stages of view holder are there. You can google recycler view principle if you need.

看看下面的图片。 视图持有者的阶段就在那里。 您可以根据需要使用Google Recycler视图原理。

As for view holders binding and creation, in fact, the recycler has just two options: either to create a new holder or to use the existing one and bind it. Of course, we would like our view holders to be created more rarely, because it can become a heavy operation, to make matters worse, it happens in the UI thread. And it is totally painful, when the holders is being created and a user sees that your list is slowing down while it’s scrolling. Why does it happen? Because we spend a lot of time rendering frames. The special aspects of rendering in Android (https://developer.android.com/topic/performance/vitals/render).

至于视图持有者的绑定和创建,实际上,回收者只有两种选择:创建一个新的持有者或使用现有的持有者并对其进行绑定。 当然,我们希望很少创建视图持有者,因为它可能会成为繁重的操作,更糟糕的是,它会发生在UI线程中。 当创建持有人并且用户看到您的列表在滚动时速度变慢时,这是非常痛苦的。 为什么会发生? 因为我们花费很多时间来渲染框架。 Android中渲染的特殊方面( https://developer.android.com/topic/performance/vitals/render )。

2. 问题隔离 (2. The problem isolation)

Now we need a problem confirmation despite the user experience.

现在,尽管有用户体验,我们仍然需要问题确认。

fun createHolder(parent: ViewGroup, viewType: Int) : CompanyViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
        Thread.sleep(30) // simulation of hard work
        return when (viewType) {
            TYPE_GOOGLE -> GoogleViewHolder(view)
            TYPE_FACEBOOK -> FacebookViewHolder(view)
            TYPE_APPLE -> AppleViewHolder(view)
            TYPE_MICROSOFT -> MicrosoftViewHolder(view)
            TYPE_AMAZON -> AmazonViewHolder(view)
            TYPE_IBM -> IbmViewHolder(view)
            TYPE_INTEL -> IntelViewHolder(view)
            TYPE_QUALCOMM -> QualcommViewHolder(view)
            else -> throw IllegalArgumentException("View type not found")
        }
    }
}

I added “sleep” for clarity. The easiest thing we can do — to use developer settings.

为了清楚起见,我添加了“睡眠”。 我们可以做的最简单的事情-使用开发人员设置。

Image for post
Image for post

On the right screenshot there is a graph of the frames rendering. The green live — 16 ms, the red one — 32 ms. Obviously, I have a problem in my application. Another approach is more detailed — profiler.

在右侧的屏幕截图中,有一个帧渲染图。 绿色直播-16毫秒,红色直播-32毫秒。 显然,我的应用程序有问题。 另一种方法更为详细-分析器。

Image for post
Image for post

To make sure that the problem is in the view holders creation, you can get more information about the calls and the resources expense in other tabs of the profiler. But it is enough for my example.

为确保问题出在视图持有者的创建中,您可以在探查器的其他选项卡中获取有关调用和资源费用的更多信息。 但这对于我的例子就足够了。

3. 解决方案 (3. Solution)

Why not to create the view holders in the background thread and push them to the recycler view pull? We would solve the problem this way when our holders are created in the UI thread and annoy a user. Firstly, we should create our view pull.

为什么不在后台线程中创建视图持有者并将其推到回收者视图下拉菜单? 当在UI线程中创建我们的持有人并惹恼用户时,我们将以这种方式解决问题。 首先,我们应该创建视图拉。

@ExperimentalCoroutinesApi
class PrefetchRecycledViewPool(activity: Activity, coroutineScope: CoroutineScope) : RecyclerView.RecycledViewPool(), HolderPrefetcher {


    private val viewHolderCreator = ViewHolderCreator(activity, coroutineScope, ::putViewFromCreator)


    override fun setViewsCount(viewType: Int, count: Int, holderCreator: (fakeParent: ViewGroup, viewType: Int) -> RecyclerView.ViewHolder) {
        require(count > 0)
        viewHolderCreator.setPrefetchBound(holderCreator, viewType, count)
    }


    fun prepare() {
        viewHolderCreator.prepare()
        attachToPreventViewPoolFromClearing()
    }


    override fun putRecycledView(scrap: RecyclerView.ViewHolder) {
        val viewType = scrap.itemViewType
        setMaxRecycledViews(viewType, 20)
        super.putRecycledView(scrap)
    }


    override fun getRecycledView(viewType: Int): RecyclerView.ViewHolder? {
        val holder = super.getRecycledView(viewType)
        if (holder == null) {
            viewHolderCreator.itemCreatedOutside(viewType)
        }
        return holder
    }


    override fun clear() {
        viewHolderCreator.clear()
        super.clear()
    }


    private fun putViewFromCreator(scrap: RecyclerView.ViewHolder, creationTimeNs: Long) {
        factorInCreateTime(scrap.viewType, creationTimeNs)
        putRecycledView(scrap)
    }
}

HolderPrefetcherthe interface to count the holders and get their types.ViewHolderCreator — the class which does the magic for the holders creation. The code is below.attachToPreventViewPoolFromClearing() — is an important extension having the only method attach(), which needs to be called before RecycledViewPool addition to prevent its cleanup on a call setAdapter()getRecycledView(viewType: Int) — we override this method to catch a moment when the recycler has created the holders to prevent their further creation and extra work.fakeParent — needs to add our parameters from xml, e.g. margins

HolderPrefetcher 用于计数持有者并获取其类型的接口。 ViewHolderCreator —为所有者创建创造神奇效果的类。 代码如下。 attachToPreventViewPoolFromClearing ()—是具有唯一方法attach()的重要扩展,需要在添加RecycledViewPool之前调用该方法,以防止在调用setAdapter()getRecycledView(viewType:Int)时对其进行清理我们覆盖此方法以捕捉片刻回收商已经创建了持有人,以防止他们进一步创建和额外的工作。 fakeParent —需要从xml添加我们的参数,例如边距

@ExperimentalCoroutinesApi
internal class ViewHolderCreator(
    activity: Activity,
    private val coroutineScope: CoroutineScope,
    private val holderConsumer: (holder: RecyclerView.ViewHolder, creationTimeNs: Long) -> Unit
) {


    private val fakeParent by lazy { FrameLayout(activity) }
    private val createdOutsideChannel = Channel<ViewType>(1)
    private val enqueueChannel = Channel<ViewHolderWrapper>(1)
    private val createItemChannel = Channel<ViewHolderWrapper>(1)
    private val itemsCreated = SparseIntArray()
    private val itemsQueued = SparseIntArray()


    fun prepare() {
        coroutineScope.launch {
            createdOutsideChannel.consumeEach {
                createdOutside(it.viewType)
            }
        }


        coroutineScope.launch {
            enqueueChannel.consumeEach  {
                enqueueBatch(it.holderCreator, it.viewType, it.itemsCount)
            }
        }


        coroutineScope.launch {
            createItemChannel.consumeEach {
                createItem(it.holderCreator, it.viewType)
            }
        }
    }


    fun clear() {
        clearAndCancel()
        coroutineScope.cancel()
    }


    fun setPrefetchBound(holderCreator: HolderCreator, viewType: Int, count: Int) {
        coroutineScope.launch {
            enqueueChannel.send(ViewHolderWrapper(holderCreator, viewType, count))
        }
    }


    fun itemCreatedOutside(viewType: Int) {
        coroutineScope.launch {
            createdOutsideChannel.send(ViewType(viewType))
        }
    }


    private fun createdOutside(viewType: Int) {
        itemsCreated.put(viewType, itemsCreated[viewType] + 1)
    }


    private fun enqueueBatch(holderCreator: HolderCreator, viewType: Int, count: Int) {
        if (itemsQueued[viewType] >= count) return
        itemsQueued.put(viewType, count)


        val created = itemsCreated[viewType]
        if (created >= count) return


        enqueueItemCreation(holderCreator, viewType)
    }


    private suspend fun createItem(holderCreator: HolderCreator, viewType: Int) {
        val created = itemsCreated[viewType] + 1
        val queued = itemsQueued[viewType]
        if (created > queued) return


        val holder: RecyclerView.ViewHolder
        val start: Long
        val end: Long


        try {
            start = nanoTimeIfNeed()
            holder = holderCreator(fakeParent, viewType)
            end = nanoTimeIfNeed()
        } catch (e: Exception) {
            return
        }


        holder.viewType = viewType
        itemsCreated.put(viewType, created)


        withContext(Dispatchers.Main) {
            holderConsumer(holder, end - start)
        }
        if (created < queued) enqueueItemCreation(holderCreator, viewType)
    }


    private fun enqueueItemCreation(holderCreator: HolderCreator, viewType: Int) {
        coroutineScope.launch {
            createItemChannel.send(ViewHolderWrapper(holderCreator, viewType))
        }
    }


    private fun clearAndCancel() {
        createItemChannel.cancel()
        enqueueChannel.cancel()
        createdOutsideChannel.cancel()
        itemsQueued.clear()
        itemsCreated.clear()
    }


    private fun nanoTimeIfNeed() = if (ALLOW_THREAD_GAP_WORK) System.nanoTime() else 0L


    private class ViewHolderWrapper(
        val holderCreator: HolderCreator,
        val viewType: Int,
        val itemsCount: Int = 0
    )
}


private inline class ViewType(val viewType: Int)

Filter out the channels, I did it this way just for fun. In fact, you can use RX or to create a background thread.

过滤掉频道,我这样做只是为了好玩。 实际上,您可以使用RX或创建后台线程。

Image for post
now we have no frame drops
现在我们没有丢帧

4.差距工人 (4. Gap worker)

An interested listener could notice the method factorInCreateTime and gap worker reference. In short, we got a render thread besides UI thread, which took the right for the rendering in Android 5.0+. So, the creation and binding processes are going on the UI thread and, therefore, the render stage starts a bit later and we lose a frame. Google developers decided to move the creation and binding holders processes to the UI thread because it is cleared early on some frames. This is what gap worker does: it searches the moments when UI thread is cleared and tries to create and bind the holders. It is enabled since the 25th support as default. Also, it works with nested lists.

感兴趣的侦听器可能会注意到factorInCreateTime方法和gap worker参考。 简而言之,除了UI线程外,我们还有一个渲染线程,该线程适合在Android 5.0+中进行渲染。 因此,创建和绑定过程正在UI线程上进行,因此,渲染阶段稍后开始,并且我们丢失了一帧。 Google开发人员决定将创建和绑定持有人的流程移至UI线程,因为它已在某些框架上提前清除。 这就是gap worker的工作:它搜索清除UI线程的时刻,并尝试创建和绑定持有者。 自第25个支持默认启用。 此外,它还可以与嵌套列表一起使用。

That’s it! Now we get a pretty simple and, at the same time, sufficient optimization. We used it on a real project and it worked great for the heavy holders.

而已! 现在,我们得到了一个非常简单的同时足够的优化。 我们在一个实际项目中使用了它,对重型持有者来说效果很好。

翻译自: https://medium.com/@icesrgt/recycler-view-power-of-asynchronous-view-holders-creation-b3c9fe067702

异步加载分部视图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值