我们都知道在手机应用中显示列表数据是最常见的一种使用场景,比如新闻、微博、朋友圈等,但是由于移动设备的性能有限(尤其是内存),当我们在绘制列表视图时不可能将成百上千条数据一下子全部绘制到界面上,否则在低配手机上必然会引起应用卡顿甚至OOM,从而导致应用体验很差。在这种情况下我们该如何对应用进行优化呢?android中提供了listview和recyclerview两个列表视图控件来支持大量数据在界面上的高效显示,他们的核心思想都是通过缓存来优化界面处理过程中的性能问题。那么接下来我们就简单分析下listview和recyclerview的缓存设计。
为什么需要缓存
前面提到移动设备存在性能瓶颈,假设我们在一个页面上要加载显示1000条数据,如果直接new 1000个view实例然后显示这种方式在低配手机上很可能会造成界面卡顿甚至OOM,而且由于手机的屏幕尺寸有限而且一般都比较小,用户在使用过程中只能同看到屏幕内的几个view,而其余的不可见的那些view,对于用户来说其实是“无用”的,那么view这里就是我们可以优化的一个点了。我们可以根据手机屏幕内最多可容纳的view个数来创建很少的view实例用于显示在界面上,然后再使用一个小容量的缓存来存储不可见的view实例方便复用,这样我们就可以大大的节省应用占用的内存空间,从而优化用户的体验。当然这种优化方式也是基于用户的特殊使用场景,如果换一个使用场景(假设屏幕无限大,当然这种场景实际是不存在的),那么这种方式其实就无效了,其实想说的就是,没有一种优化方案是万能的,每一种优化都只有在基于特定场景下才能发挥最大的作用。
基于缓存的android列表视图
android提供了listview和recyclerview来展示列表视图,他们都利用了之前提到的缓存设计思想来优化大数据量的加载和显示。listview在较早的android版本中就出现了,其使用了二级缓存来避免创建不必要的view实例,当然缓存设计的加入在改善了程序运行效率的同时也势必会增加程序逻辑处理的复杂度,但是相比性能的提升这种改造成本是值得的。recyclerview是在android5.0版本之后出现的,其采用了四级缓存设计,通常我们使用的都只有三级。相比listview,其对性能的优化粒度更细,因此保证了效率更高,加之其设计和使用上更加灵活,所以目前大部分开发者选择recyclerview来实现列表数据和展示。接来下分别对这两个控件的缓存设计进行分析,本文仅仅描述缓存设计思路不涉及源码分析,想要深入了解请查阅末尾的参考文档。
ListView的缓存设计
通常为了进一步提高效率,我们会在ListView的adapter内部定义一个ViewHolder,这个ViewHolder跟RecyclerView的ViewHolder有点类似。其内部会保存每个ItemView的childView,这样我们在使用复用的ItemView(其实也就是ConvertView)刷新数据时,不用重新调用findViewById来定位childView了,这样可以提高运行效率,因为findViewById也是会按照广度优先遍历ItemView下的子结点,如果itemView下的层级结构比较深,开销也不可忽视。RecyclerView的缓存设计
ListView和RecyclerView对比
通过上面两张图我们可以看出,ListView和RecyclerView的缓存设计都包含了create和bind的过程,当缓存没有命中时通过create构建新的对象,当缓存命中时通过bind来复用对象。 在设计上,RecyclerView由于将布局(LayoutManager)、分割线(ItemDecoration)和动画(ItemAnimator)分离出来使得其对列表视图的控制要比ListView更加灵活。
在效率上,RecyclerView相比ListView要更加高效体现在其对缓存的控制上更加精细,主要体现在以下两点:
- 减少bind方法的调用频次。当视图滚动,缓存命中的情况下,ListView会将缓存中UI绑定的数据清空并重新初始化,而RecyclerView将缓存细分为两种,一种是mAttachedScrap,另一种是RecyclerViewPool。首先匹配mAttachedScrap,当mAttachedScrap中存在时直接使用其UI以及UI绑定的数据,不用重新初始化。这种通过增加少量内存开销从而减少bind方法的调用的方式,可以进一步提供视图的加载和显示效率。此外,RecyclerView还提供了局部刷新的方式来进一步减少了bind方法的调用频次,从而进一步优化了性能。
- 多个RecyclerView之间的缓存共享,RecyclerView的RecyclerViewPool可以由开发者自己指定,灵活控制缓存的容量大小,这在某些场景下,比如使用viewpager来实现多个类似列表页面的展示,是非常有用的,我们可以将多个页面的RecyclerViewPool进行复用从而进一步降低缓存的内存开销。
在使用上,四级缓存以及控制上的灵活设计,导致RecyclerView的实现比ListView复杂了许多,使用门槛上也更高了一些。所以,当我们在实现列表视图时需要根据实际的业务场景来进行选择。如果是一些比较简单的类型单一的静态列表页,使用ListView会更方便,代码量更少,当然如果你对RecyclerView封装的比较好,也可以做到使用很少的代码就能满足需求。如果是类型复杂或者需要频繁动态更新的列表页,还是推荐RecyclerView来实现。
总结
不论是ListView还是RecyclerView,他们的缓存设计思想都值得我们借鉴和学习。从预加载到懒加载,从全局刷新到局部刷新,这种时间换空间和空间换时间的优化思路不仅可以用在列表视图中,也可以用在其他的场景中。盘点一下我们在应用开发中的各种业务场景,朝着这个方向,相信你总能找出优化的点来。