Android ListView 与 RecyclerView 对比浅析:缓存机制

作者: tummyhuang

一. 背景

RecyclerView是谷歌官方出的一个用于大量数据展示的新控件,可以用来代替传统的ListView,更加强大和灵活。

最近,自己负责的业务,也遇到这样的一个问题,关于是否要将ListView替换为RecyclerView?

秉承着实事求是的作风,弄清楚RecyclerView是否有足够的吸引力替换掉ListView,我从性能这一角度出发,研究RecyclerView和ListView二者的缓存机制,并得到了一些较有益的"结论",待我慢慢道来。

同时也希望能通过本文,让大家快速了解RecyclerView与ListView在缓存机制上的一些区别,在使用上也更加得心应手吧。

PS:相关知识: ListView与RecyclerView缓存机制原理大致相似,如下图所示:

滑动过程中,离屏的ItemView即被回收至缓存,入屏的ItemView则会优先从缓存中获取,只是ListView与RecyclerView的实现细节有差异.(这只是缓存使用的其中一个场景,还有如刷新等)

PPS:本文不贴出详细代码,结合源码食用更佳!

二. 正文

2.1 缓存机制对比

1. 层级不同: RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。

具体来说: ListView(两级缓存):

RecyclerView(四级缓存):

ListView和RecyclerView缓存机制基本一致:

1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView; 2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用. 3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。

2. 缓存不同:

1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为: View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态); 2). ListView缓存View。

缓存不同,二者在缓存的使用上也略有差别,具体来说: ListView获取缓存的流程:

RecyclerView获取缓存的流程:

1). RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView:

而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView),相关代码如下:

//AbsListView源码:line2345
        //通过匹配pos从mScrapView中获取缓存
        final View scrapView = mRecycler.getScrapView(position);
        //无论是否成功都直接调用getView,导致必定会调用createView
        final View child = mAdapter.getView(position, scrapView, this);
        if (scrapView != null) {
            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
            } else {
                ...
            }
        }

2). ListView中通过pos获取的是view,即pos-->view; RecyclerView中通过pos获取的是viewholder,即pos --> (view,viewHolder,flag); 从流程图中可以看出,标志flag的作用是判断view是否需要重新bindView,这也是RecyclerView实现局部刷新的一个核心。

2.2 局部刷新

由上文可知,RecyclerView的缓存机制确实更加完善,但还不算质的变化,RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。

(RecyclerView和ListView添加,移除Item效果对比)

结合RecyclerView的缓存机制,看看局部刷新是如何实现的: 以RecyclerView中notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重新绘制,过程为: onMeasure()-->onLayout()-->onDraw()

其中,onLayout()为重点,分为三步: dispathLayoutStep1():记录RecyclerView刷新前列表项ItemView的各种信息,如Top,Left,Bottom,Right,用于动画的相关计算; dispathLayoutStep2():真正测量布局大小,位置,核心函数为layoutChildren(); dispathLayoutStep3():计算布局前后各个ItemView的状态,如Remove,Add,Move,Update等,如有必要执行相应的动画.

其中,layoutChildren()流程图:

当调用notifyItemRemoved时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag(流程图中红色部分):

当调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次.

需要指出,ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。

三.结论

1、在一些场景下,如界面初始化,滑动等,ListView和RecyclerView都能很好地工作,两者并没有很大的差异:

文章的开头便抛出了这样一个问题,微信Android客户端卡券模块,大部分UI都是以列表页的形式展示,实现方式为ListView,是否有必要将其替换成RecyclerView呢?

答案是否定的,从性能上看,RecyclerView并没有带来显著的提升,不需要频繁更新,暂不支持用动画,意味着RecyclerView优势也不太明显,没有太大的吸引力,ListView已经能很好地满足业务需求。

2、数据源频繁更新的场景,如弹幕:http://www.jianshu.com/p/2232a63442d6 等RecyclerView的优势会非常明显;

进一步来讲,结论是: 列表页展示界面,需要支持动画,或者频繁更新,局部刷新,建议使用RecyclerView,更加强大完善,易扩展;其它情况(如微信卡包列表页)两者都OK,但ListView在使用上会更加方便,快捷。

Ps:仅从一个角度做了对比,盲人摸象,有误跪求指正。

四.参考资料

1. ListView a. Android-23源码 b. Android ListView工作原理解析,带你从源码的角度彻底理解:http://blog.csdn.net/guolin_blog/article/details/44996879 c. Android自己动手写ListView学习其原理:http://blog.csdn.net/androiddevelop/article/details/8734255

2. RecyclerView a. RecyclerView-v7-23.4.0源码 b. RecyclerView剖析:http://blog.csdn.net/qq_23012315/article/details/50807224 c. RecyclerView剖析:http://blog.csdn.net/qq_23012315/article/details/51096696

本文来源于:WeMobileDev 微信公众号

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏杨建荣的学习笔记

dg的奇怪问题终结和分区问题答疑 (r7笔记第77天)

今天来说几个问题,一个是对昨天《让我焦灼的四个问题》的升华,不能起博眼球的题目,技术分析给大家兜底了,你们看看有没有类似的问题。 还有几个小问题说说今天的感受和...

3355
来自专栏杨建荣的学习笔记

测试环境的迁移式升级和数据整合(r8笔记第27天)

很多时候,大家工作中都会有一种被动的思维,那就是能不动就不动,从求稳的角度来看无可厚非,但是从风险的角度来说,还是有待商榷的。如果存在风险,还保持原样很可能就是...

2704
来自专栏北京马哥教育

tomcat调优之启动参数

Linux系统中tomcat的启动参数 export JAVA_OPTS="-server -Xms1400M -Xmx1400M -Xss512k -XX:+...

29610
来自专栏美团技术团队

移动端性能监控方案Hertz

性能问题是造成App用户流失的罪魁祸首之一。App的性能问题包括崩溃、网络请求错误或超时、响应速度慢、列表滚动卡顿、流量大、耗电等等。而导致App性能低下的原因...

5073
来自专栏CSDN技术头条

时序列数据库武斗大会之 OpenTSDB 篇

刘斌,OneAPM后端研发工程师,拥有10多年编程经验,参与过大型金融、通信以及Android手机操作系的开发,熟悉Linux及后台开发技术。曾参与翻译过《第...

2706
来自专栏杨建荣的学习笔记

ORACLE 10g 升级 11g问题汇总(31天)

按照计划开始了生产库的升级,环境基于linux 64位. uname: Linux 2.6.18-308.el5 #1 SMP Fri Jan 27 17:1...

4109
来自专栏落影的专栏

iOS开发笔记(九)— 数据库、Crash、内存问题分析

分享iOS开发中遇到的问题,和相关的一些思考,本次内容包括:UIKit的iOS11问题、数据库问题定位、线上Crash处理、内存问题分析。

702
来自专栏三流程序员的挣扎

Android 优化——电量优化

从低功率到高功率大约 1.5s,从空闲态到高功率大约 2s,秒。在应用中每创建一个新的网络连接,网络(射频)模块都会转换到高功率状态(Radio Full P...

642
来自专栏nimomeng的自我进阶

OC优化指南

a) Reusing UITableViewCell:利用cellWithTableView:cellIdentifier:nibName: b)...

481
来自专栏杨建荣的学习笔记

一条简单的报警信息发现的oracle bug(r8笔记第42天)

系统中有这样一条报警信息,看似比较简单,但是引起了我的注意,主要原因是因为这是一个10gR2的备库,备库如果出现这样的问题,看起来似乎是在归档删除上存在一些问题...

2634

扫码关注云+社区