引子
列表卡顿是一个很常见的问题,通常的理解是滑动过程中卡顿,有很多常规的处理方法,从最简单的复用到降低布局复杂度到部分布局动态加载。本文介绍下列表初次填充数据时卡顿的处理方法。
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线程中回调一次,如果出现多个回调接近于同时先后得到执行,那一定是由于主线程中进行某种耗时操作被阻塞了,这个时候可以考虑优化下程序的其他部分了。