RecyclerView性能优化之异步预加载 - 我的Android开源之旅 - SegmentFault 思否
之前我们讲过,在优化onCreateViewHolder方法的时候,可以降低item的布局层级,可以减少界面创建的渲染时间,其本质就是降低view的inflate时间。因为onCreateViewHolder最大的耗时部分,就是view的inflate。相信读过LayoutInflater.inflate源码的人都知道,这部分的代码是同步操作,并且涉及到大量的文件IO的操作以及锁操作,通常来说这部分的代码快的也需要几毫秒,慢的可能需要几十毫秒乃至上百毫秒也是很有可能的。 如果真到了每个ItemView的inflate需要花上上百毫秒的话,那么在大数据量的RecyclerView进行快速上下滑动的时候,就必然会导致界面的滑动卡顿、不流畅。
那么如何你的程序里真的有这样一个列表,它的每个ItemView都需要花上上百毫秒的时间去inflate的话,你该怎么做?
首先就是对布局进行优化,降低item的布局层级。但这点的优化往往是微乎其微的。
其次可能就是想办法让设计师重新设计,将布局中的某些内容删除或者折叠了,对暂不展示的内容使用ViewStub进行延迟加载。不过说实在话,你既然有能力让设计师重新设计的话,还干个球的开发啊,直接当项目经理不香吗?
最后你可能会考虑不用xml写布局,改为使用代码自己一个一个new布局。话说回来了,一个使用xml加载的布局都要花上上百毫秒的布局,可能xml都快上千行下去了,你确定要自己一个一个new下去?
以上的方式,都是建立在列表布局可以修改的情况下,如果我们使用的列表布局是第三方已经提供好的呢?(例如广告SDK等)
那么有没有什么办法既可以不用修改当前的xml布局,又可以极大地缩短布局的加载时间呢?毫无疑问,布局异步加载将为你打开新的世界。
原理
Google官方很早就发现了XML布局加载的性能问题,于是在androidx中提供了异步加载工具AsyncLayoutInflater。其本质就是开了一个长期等待的异步线程,在子线程中inflate view,然后把加载好的view通过接口抛出去,完成view的加载。
一般来说,对于复杂的列表,往往都对应了复杂的数据,而这复杂的数据往往又是通过服务器获取而来。所以一般来说,一个列表在加载前,往往先需要访问服务器获取数据,然后再刷新列表显示,而这访问服务器的时间大约也在300ms~1000ms之间。很多开发人员对这段时间往往没有加以利用,只是加上一个loading动画了事。
其实对于这一段事务真空的时间窗口,我们可以提前进行列表的ItemView的加载,这样等数据请求下来刷新列表的时候,我们onCreateViewHolder的时候就可以直接到已经事先预加载好的View缓存池中直接获取View传到ViewHolder中使用,这样onCreateViewHolder的创建时间几乎耗时为0,从而极大地提升了列表的加载和渲染速度。详细的流程可以参见下图:
实现
上面我简单地讲解了一下原理,下一步就是考虑如何实现这样的效果了。
预加载缓存池
首先在预加载前,我们需要先创建一个缓存池来存储预加载的View对象。
这里我选择使用SparseArray进行存储,key是Int型,存放布局资源的layoutId,value是Object型,存放的是这类布局加载View的集合。
这里的集合类型我选择的是LinkedList,因为我们的缓存需要频繁的添加和删除操作,并且LinkedList实现了Deque接口,具备先入先出的能力。
这里View的引用我选择的是软引用SoftReference,之所以不采用WeakReference, 目的就是希望缓存能多存在一段时间,避免内存的频繁释放和回收造成内存的抖动。