Android RecyclerView性能优化

2018/6/15 posted in  Android

前言

近期由于项目中使用RecyclerView加载的数据数量较大,所以必须对RecyclerView进行优化。

数据处理和视图加载分离

数据加载一般都是异步加载,有时候可能加载完数据就直接丢给VH进行处理。其实,数据的处理逻辑我们也应该放在异步处理,这样Adapter在notify change后,ViewHolder就可以简单无压力地做数据与视图的绑定逻辑。将数据处理逻辑与网络异步线程放在一起,粘在用户角度,最多就是网络刷新时间稍长一点。

数据优化

  • 远端数据量较大时,我们采取分页拉取的方式,并对其进行缓存,提升二次加载熟读。
  • 对于新增或者删除数据,通过DiffUtil来进行局部刷新数据,而不是一味地全局刷新数据。
void onNewDataArrived(List<News> news) {
    List<News> oldNews = myAdapter.getItems();
    DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
    myAdapter.setNews(news);
    result.dispatchUpdatesTo(myAdapter);
}

布局优化

减少过渡绘制

减少布局层级,可以考虑使用自定义 View 来减少层级,或者更合理地设置布局来减少层级

减少 xml 文件 inflate 时间

这里的 xml 文件不仅包括 layoutxml,还包括 drawablexmlxml 文件 inflateItemView 是通过耗时的 IO 操作,尤其当 Item 的复用几率很低的情况下,随着 Type 的增多,这种 inflate 带来的损耗是相当大的,此时我们可以用代码去生成布局,即 new View() 的方式,只要搞清楚 xml 中每个节点的属性对应的 API 即可。使用StaticLayout和DynamicLayout代替TextView,使用自定义View代替LayoutInflater.inflate(xml)文件

减少 View 对象的创建

一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的创建也会消耗大量时间,所以要尽可能简化 ItemView;设计 ItemType 时,对多 ViewType 能够共用的部分尽量设计成自定义 View,减少 View 的构造和嵌套。

Bitmap传递

Android以OpenGL Texture的形式来展示bitmap,当bitmap第一次展示时,它会以width * height大小的Texture形式传递到GPU上,所以要保证bitmap的大小不会大于其展示大小,要知道上传过程是阻塞主线程的。一般传递一张1920 * 1080的Texture不会超过10ms。

对象分配和垃圾回收

虽然Android 5.0上使用ART来减少GC停顿时间,但仍然会造成卡顿。尽量避免在循环内创建对象导致GC。要知道,创建对象需要分配内存,而这个时机会检查内存是否足够来决定需不需要进行GC。

预加载

// 通过复写指定预加载的像素值。
LinearLayoutManager.getExtraLayoutSpace();
和

// 设置预加载itemview数目。
RecycleView.setItemViewCacheSize(size);

其他

  • 升级 RecycleView 版本到 25.1.0 及以上使用 Prefetch 功能
  • 如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源
  • 设置 RecyclerView.addOnScrollListener(listener); 来对滑动过程中停止加载的操作
  • 如果不要求动画,可以通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭来提升效率
  • TextView 使用 String.toUpperCase 来替代 android:textAllCaps="true"
  • 通过重写 RecyclerView.onViewRecycled(holder) 来回收资源
  • 通过 RecycleView.setItemViewCacheSize(size); 来加大 RecyclerView 的缓存,用空间换时间来提高滚动的流畅性
  • 如果多个 RecycledView 的 Adapter 是一样的,比如嵌套的 RecyclerView 中存在一样的 Adapter,可以通过设置 RecyclerView.setRecycledViewPool(pool); 来共用一个 RecycledViewPool
  • ItemView 设置监听器,不要对每个 Item 都调用 addXxListener,应该大家公用一个 XxListener,根据 ID 来进行不同的操作,优化了对象的频繁创建带来的资源消耗
  • 值相同避免再次刷新,TextView的text相同,就不需要再调用setText方法,对比的损耗往往小于绘制
  • 通过 getExtraLayoutSpace 来增加 RecyclerView 预留的额外空间(显示范围之外,应该额外缓存的空间),如下所示:
new LinearLayoutManager(this) {
    @Override
    protected int getExtraLayoutSpace(RecyclerView.State state) {
        return size;
    }
};

参考

《RecyclerView 性能优化》