ListView和RecycleView的性能比对
ListView相比RecyclerView,有一些优点:
addHeaderView()
,addFooterView()
添加头视图和尾视图。- 通过”android:divider”设置自定义分割线。
setOnItemClickListener()
和setOnItemLongClickListener()
设置点击事件和长按事件。
这些功能在RecyclerView中都没有直接的接口,要自己实现(虽然实现起来很简单),因此如果只是实现简单的显示功能,ListView无疑更简单。
RecyclerView相比ListView,有一些明显的优点:
- 默认已经实现了View的复用,不需要类似
if(convertView == null)
的实现,而且回收机制更加完善。 - 默认支持局部刷新。
- 容易实现添加item、删除item的动画效果。
- 容易实现拖拽、侧滑删除等功能。
RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。
局部刷新
ListView实现局部刷新
我们都知道ListView通过adapter.notifyDataSetChanged()
实现ListView的更新,这种更新方法的缺点是全局更新,即对每个Item View都进行重绘。但事实上很多时候,我们只是更新了其中一个Item的数据,其他Item其实可以不需要重绘。
这里给出ListView实现局部更新的方法:
public void updateItemView(ListView listview, int position, Data data){int firstPos = listview.getFirstVisiblePosition();int lastPos = listview.getLastVisiblePosition();if(position >= firstPos && position <= lastPos){ //可见才更新,不可见则在getView()时更新//listview.getChildAt(i)获得的是当前可见的第i个item的viewView view = listview.getChildAt(position - firstPos);VH vh = (VH)view.getTag();vh.text.setText(data.text);}
}
可以看出,我们通过ListView的getChildAt()
来获得需要更新的View,然后通过getTag()
获得ViewHolder,从而实现更新。
RecyclerView实现局部刷新
RecyclerView提供了notifyItemInserted()
,notifyItemRemoved()
,notifyItemChanged()
等API更新单个或某个范围的Item视图。
缓存机制
ListView与RecyclerView缓存机制原理大致相似,如下图所示:
image
过程中,离屏的ItemView即被回收至缓存,入屏的ItemView则会优先从缓存中获取,只是ListView与RecyclerView的实现细节有差异.(这只是缓存使用的其中一个场景,还有如刷新等)
缓存机制对比
1. 层级不同:
RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
具体来说:
ListView(两级缓存):
image
RecyclerView(四级缓存):
image
ListView和RecyclerView缓存机制基本一致:
1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.
3). RecyclerView的优势在于
- mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;
- mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势。
客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。
2. 缓存不同:
1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
2). ListView缓存View。
缓存不同,二者在缓存的使用上也略有差别,具体来说:
ListView获取缓存的流程:
image
RecyclerView获取缓存的流程:
image
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实现局部刷新的一个核心.
局部刷新
由上文可知,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()流程图:
image
image
当调用notifyItemRemoved时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag(流程图中红色部分):
image
当调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次.
需要指出,ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是”一锅端”,将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。
回收机制源码分析
ListView回收机制
ListView为了保证Item View的复用,实现了一套回收机制,该回收机制的实现类是RecycleBin,他实现了两级缓存:
View[] mActiveViews
: 缓存屏幕上的View,在该缓存里的View不需要调用getView()
。ArrayList<View>[] mScrapViews;
: 每个Item Type对应一个列表作为回收站,缓存由于滚动而消失的View,此处的View如果被复用,会以参数的形式传给getView()
。
接下来我们通过源码分析ListView是如何与RecycleBin交互的。其实ListView和RecyclerView的layout过程大同小异,ListView的布局函数是layoutChildren()
,实现如下:
void layoutChildren(){//1. 如果数据被改变了,则将所有Item View回收至scrapView //(而RecyclerView会根据情况放入Scrap Heap或RecyclePool);否则回收至mActiveViewsif (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {recycleBin.fillActiveViews(childCount, firstPosition);}//2. 填充switch(){case LAYOUT_XXX:fillXxx();break;case LAYOUT_XXX:fillXxx();break;}//3. 回收多余的activeViewmRecycler.scrapActiveViews();
}
其中fillXxx()
实现了对Item View进行填充,该方法内部调用了makeAndAddView()
,实现如下:
View makeAndAddView(){if (!mDataChanged) {child = mRecycler.getActiveView(position);if (child != null) {return child;}}child = obtainView(position, mIsScrap);return child;
}
其中,getActiveView()
是从mActiveViews中获取合适的View,如果获取到了,则直接返回,而不调用obtainView()
,这也印证了如果从mActiveViews获取到了可复用的View,则不需要调用getView()
。
obtainView()
是从mScrapViews中获取合适的View,然后以参数形式传给了getView()
,实现如下:
View obtainView(int position){final View scrapView = mRecycler.getScrapView(position); //从RecycleBin中获取复用的Viewfinal View child = mAdapter.getView(position, scrapView, this);
}
接下去我们介绍getScrapView(position)
的实现,该方法通过position得到Item Type,然后根据Item Type从mScrapViews获取可复用的View,如果获取不到,则返回null,具体实现如下:
class RecycleBin{private View[] mActiveViews; //存储屏幕上的Viewprivate ArrayList<View>[] mScrapViews; //每个item type对应一个ArrayListprivate int mViewTypeCount; //item type的个数private ArrayList<View> mCurrentScrap; //mScrapViews[0]View getScrapView(int position) {final int whichScrap = mAdapter.getItemViewType(position);if (whichScrap < 0) {return null;}if (mViewTypeCount == 1) {return retrieveFromScrap(mCurrentScrap, position);} else if (whichScrap < mScrapViews.length) {return retrieveFromScrap(mScrapViews[whichScrap], position);}return null;}private View retrieveFromScrap(ArrayList<View> scrapViews, int position){int size = scrapViews.size();if(size > 0){return scrapView.remove(scrapViews.size() - 1); //从回收列表中取出最后一个元素复用} else{return null;}}
}
RecyclerView回收机制
RecyclerView和ListView的回收机制非常相似,但是ListView是以View作为单位进行回收,RecyclerView是以ViewHolder作为单位进行回收。
Recycler是RecyclerView回收机制的实现类,他实现了四级缓存:
- mAttachedScrap: 缓存在屏幕上的ViewHolder。
- mCachedViews: 缓存屏幕外的ViewHolder,默认为2个。ListView对于屏幕外的缓存都会调用
getView()
。 - mViewCacheExtensions: 需要用户定制,默认不实现。
- mRecyclerPool: 缓存池,多个RecyclerView共用。
在上文Layout Manager中已经介绍了RecyclerView的layout过程,但是一笔带过了getViewForPosition()
,因此此处介绍该方法的实现。
View getViewForPosition(int position, boolean dryRun){if(holder == null){//从mAttachedScrap,mCachedViews获取ViewHolderholder = getScrapViewForPosition(position,INVALID,dryRun); //此处获得的View不需要bind}final int type = mAdapter.getItemViewType(offsetPosition);if (mAdapter.hasStableIds()) { //默认为falseholder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);}if(holder == null && mViewCacheExtension != null){final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); //从if(view != null){holder = getChildViewHolder(view);}}if(holder == null){holder = getRecycledViewPool().getRecycledView(type);}if(holder == null){ //没有缓存,则创建holder = mAdapter.createViewHolder(RecyclerView.this, type); //调用onCreateViewHolder()}if(!holder.isBound() || holder.needsUpdate() || holder.isInvalid()){mAdapter.bindViewHolder(holder, offsetPosition);}return holder.itemView;
}
从上述实现可以看出,依次从mAttachedScrap, mCachedViews, mViewCacheExtension, mRecyclerPool寻找可复用的ViewHolder,如果是从mAttachedScrap或mCachedViews中获取的ViewHolder,则不会调用onBindViewHolder()
,mAttachedScrap和mCachedViews也就是我们所说的Scrap Heap;而如果从mViewCacheExtension或mRecyclerPool中获取的ViewHolder,则会调用onBindViewHolder()
。
RecyclerView局部刷新的实现原理也是基于RecyclerView的回收机制,即能直接复用的ViewHolder就不调用onBindViewHolder()
。
转载:https://www.jianshu.com/p/4f9591291365
ListView和RecycleView的性能比对相关推荐
- listview 和RecycleView区别
RecyclerView和ListView之间的区别 在我们要搞清楚一个问题之前,首先要搞清楚为什么,是什么, 最后才能得出结论 一:为什么会出现recycleView RecyclerView并不会 ...
- Android截屏截图方法汇总(Activity、View、ScrollView、ListView、RecycleView、WebView截屏截图)
Android截屏 Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途 一.Activity截屏 1.截Activity界面(包含空白的 ...
- DataBinding使用指南(一)DataBinding基本使用,双向绑定,ListView RecycleView使用
databing使用指南 简介 简单使用 双向绑定 ListView.RecycleView中的使用 . ListView ListView 中数据的简单展示 数据源改变后数据更新方式 . Recyc ...
- 【Android】RecycleView简单仿漫画APP图片相关样式
真的真的想不到起什么标题好了,这次的内容真的是太简单了,没有什么挑战性,一天以内就完成了.最近在学kotlin,也会有一份kotlin的代码,鉴于很多人都是从java开始进行android开发的,ko ...
- 【转载】RecycleView使用详解
原文链接:https://blog.csdn.net/u012721519/article/details/54692366 一.RecycleView简要介绍 RecycleView是support ...
- RecycleView和ViewPager冲突解决与原理
1.概述 在实际开发中,我们经常遇到需要在ListView或RecycleView头部添加ViewPager实现Banner轮播效果,并需要添加下拉刷新,上拉加载功能. 但,横向滑动ViewPager ...
- php swiper 下拉刷新,SwipeRefreshLayout的使用(下拉刷新)
在布局中导入 SwipeRefreshLayout在V4包下,作为一个布局,可以在内部嵌套如listview,recycleView等,这里作为例子嵌套了一个listview android:id=& ...
- 2016年Android实习岗位 腾讯二面+阿里一面
转自:http://www.nowcoder.com/discuss/3906?pos=20&type=0&order=0 腾讯 一面 1.看着简历中的技能Java/c/Android ...
- Flutter学习之认知基础组件
一.前言 前一天,学习了Dart语法,对Dart的语法和特性有了更深一步的了解.今天,来学习Flutter的基础控件,身为Android开发者都知道,一开始入坑Android就要熟悉学习其控件,如:T ...
最新文章
- Blender全流程制作真实感3D产品学习教程
- 手写html5游戏,HTML5 手写输入法
- mysql中groupby会用到索引吗_mysql order by 与索引的使用
- Caffe训练ImageNet
- linux下confstr与uname函数_获取C库与内核信息
- python3 isinstance用法_对python中assert、isinstance的用法详解
- js向jsp传中文出现乱码的解决方法
- C语言试题四十七之程序定义了N×M的二维数组,并在主函数中自动赋值。请编写函数function(int a[N][M], int m),该函数的功能是:将数组右上半三角元素中的值乘以m。
- Python学习 - 之super函数
- Virtual Treeview 5 0 0的安装以及入门
- Spring框架XML配置文件使用外部Bean属性注入
- opencv cv.waitKey(60) 0xff 含义和作用
- 计算机组策略没有权限,运行组策略或程序时提示没有权限
- oracle 获取日期的毫秒_Oracle date timestamp 毫秒 - 时间函数总结(转)
- 正则html在线测试,正则表达式在线测试工具
- 【产品人卫朋】2022年产品人必备的13个设计类网站(1.0版)
- Deep Learning 最优化方法
- Android录制声音文件(音频),并播放
- Instance Tunnel 使用
- 【程序】STM32使用SPI接口读取93C46存储器上的数据(非软件模拟SPI时序)