使用AsyncLayoutInflater预加载,加快列表渲染

引子

列表卡顿是一个很常见的问题,通常的理解是滑动过程中卡顿,有很多常规的处理方法,从最简单的复用到降低布局复杂度到部分布局动态加载。本文介绍下列表初次填充数据时卡顿的处理方法。

AsyncLayoutInflater

在查找页面fps过低的问题中,我发现在列表首次填充数据进行显示时,由于列表中将要显示在屏幕中的ViewHolder都要被创建,进而这些ViewHolder的View都要被Inflate出来,因此会占用UI线程大段时间,普通情况下有(100-500ms)的耗时。这段耗时按常规的处理方法处理,只能减少,不能避免。
一次看源码中无意遇到AsyncLayoutInflater类,心想难道inflate()方法可以异步执行?百度了AsyncLayoutInflater类的介绍,看了下源码,发现还真的可以异步加载View,正好可以处理这个问题。

处理方法

1.在Adapter创建时,使用AsyncLayoutInflater异步创建若干个ViewHolder的View,放入ViewType和ArrayList组成的SparseArray<ArrayList>中。具体预加载的类型和数目根据业务的特点进行适配,避免预加载过少导致效果不大,避免预加载过多一直用不上浪费内存.
2.在Adapter的onCreateViewHolder()回调中,从SparseArray<ArrayList>中按ViewType取出ArrayList,并remove()出View,进行使用;如果没有,则使用inflate()进行创建。
经过这样的处理,列表首次加载出来,立刻开始快速滚动,都很流畅,耗时基本上都在findViewById()和onBindViewHolder()上。

注意事项

在这个过程中,需要注意一些问题:

1.异步

使用AsyncLayoutInflater创建的View,由于是在异步线程中进行Inflate操作,所以在View及其子View的构造方法中,Looper.myLooper()返回为null。如果想使用绑定到主线程的Handler,需要通过new Handler(Looper.getMainLooper())来创建。另外,如果一些必须在UI线程中执行的操作,需要使用Handler切换到主线程中;
比如RxView.clicks(),就要求在UI线程中调用。

RxView.clicks(View)
.subscribe(action, Action1 { t -> t.printStackTrace() })
2.吞吐量

所有AsyncLayoutInflater的实例共用一个异步线程进行inflate处理,没来得及处理的任务会被缓存起来;最大允许缓存的任务数为10;超过10个,则会导致添加任务的线程阻塞,一直等到缓存的任务数目少于10个。所以不要可着劲添加任务,如果想添加很多任务,可以在一些任务的回调中添加其他任务;
比如这样

    protected fun asyncInflateView(asyncLayoutInflater: AsyncLayoutInflater, layoutResId: Int, count: Int, recycleBin: RecycleBin<View>) {
        if (count <= 0) {
            return
        } else {
            asyncLayoutInflater.inflate(layoutResId, null) { view, resid, parent ->
                if (!fragment.isDestroyed) {
                    recycleBin.put(layoutResId, view)
                    view.setTag(R.id.cache_layout_res_id, layoutResId)
                    asyncInflateView(asyncLayoutInflater, layoutResId, count - 1, recycleBin)
                }
            }
        }
    }

这个地方插一句,为什么会出现阻塞的情况呢,由于异步Inflate线程使用的是普通的线程,所以当没有任务时,线程需要进入到阻塞状态,因而任务缓存队列使用的是ArrayBlockingQueue这种数据结构,那么插入任务时,如果超过容量,就会导致插入任务线程阻塞,直到有容量可以存储。

3.兜底策略

如果AsyncLayoutInflater异步inflate任务失败,会回退到UI线程中inflate,所以要仔细留意Logcat中是否有"Failed to inflate resource in the background! Retrying on the UI thread"日志;

4.tip

AsyncLayoutInflater每执行完一个任务,就会向UI线程中回调一次,如果出现多个回调接近于同时先后得到执行,那一定是由于主线程中进行某种耗时操作被阻塞了,这个时候可以考虑优化下程序的其他部分了。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值