文章目录

  • ListView缓存机制小结
    • 前言
    • 概述
    • layout过程
      • 第一次Layout
      • 第二次Layout
    • 滑动事件
    • 参考资料

ListView缓存机制小结

前言

由于本人水平有限,如果文章中出现一些谬误,还请各位大佬指正。

概述

ListView的缓存通过父类AbsListView中的内部类RecycleBin实现,这个类中有两级缓存:ActiveViewsScrapViews

  • ActiveViews用来缓存滑动后还留在屏幕内的itemview,供layout过程使用
  • ScrapViews用来缓存滑出ListView的itemview,供onTouchEvent方法使用

RecycleBin

可以看到,mScrapViews缓存是一个ArrayList类型的数组,数组的长度代表itemview的类型个数,每个索引的ArrayList的长度表示缓存的滑出屏幕的同类型itemview的个数。

class RecycleBin {private View[] mActiveViews = new View[0];    //一级缓存,用来缓存ListView第一次layout过程的itemview,在第二次layout过程会被降级到mScrapViews中private ArrayList<View>[] mScrapViews; //二级缓存,缓存已经滑出ListView的itemview,可以被adapter作为convertview进行重用
}

mScrapViews的初始化

//AbsListView
public void setViewTypeCount(int viewTypeCount) {if (viewTypeCount < 1) {throw new IllegalArgumentException("Can't have a viewTypeCount < 1");}ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; //这里的viewTypeCount就是我们通过继承BaseAdapter重写其中getViewTypeCount方法指定的for (int i = 0; i < viewTypeCount; i++) {scrapViews[i] = new ArrayList<View>();}mViewTypeCount = viewTypeCount;mCurrentScrap = scrapViews[0];  //mScrapViews = scrapViews;        //将上面创建的scrapViews赋给mScrapViews
}

mScrapViews的缓存大小就是getViewTypeCount方法返回值指定的大小。

在加载多子布局的情况就需要重写ListView的getItemViewType和getViewTypeCount方法;

另外,无论setViewTypeCount()/getViewTypeCount()值为几,RecycleBin都只有一个,而不会单独另起一个。唯一区别的是,当值为1时,即只有一种viewtype时,RecycleBin使用的是ArrayList<View> mCurrentScrap来回收和存储滚出屏幕的views,当大于1时,RecycleBin用的是ArrayList<View>[] mScrapViews

layout过程

由于父ViewGroup的原因可能导致ListView layout多次。

ListView主要看onLayout方法,因为ListView的大小就是我们指定的大小,所以onMeasure方法就没什么看的了;又因为ListView的绘制都是交给子项来完成的,所以onDraw方法也没有了解的意义了。

第一次Layout

第一次Layout,这时还没有item子项的缓存,即RecycleBin中的mActiveViews数组大小为0,mScrapViews还未初始化,所以会通过Adapter的getView方法来获取子项的布局。

调用链

onLayout()->
layoutChildren()->
fillFromTop()->
fillDown()->     //在一个while循环中调用makeAndAddView方法获取子布局的view,直到占满整个屏幕
makeAndAddView()->
obtainView()->  mAdapter.getView()->
setupChild()        //将getView方法返回的view添加到ListView中

makeAndAddView()

这个方法很重要,它体现了ListView缓存的特点:

  1. 先从RecycleBin的ActiveView获取缓存
  2. 再从RecycleBin的ScrapView获取缓存
  3. 最后考虑从Adapter的getView方法加载子布局

因为getView方法中是通过inflater的inflate方法来加载子布局的,这样获取效率较低,所以ListView会优先从两个缓存中获取。

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {//这里通过getActiveView方法从RecycleBin的ActiveView中获取final View activeView = mRecycler.getActiveView中获取(position);if (activeView != null) {    //第一次还没有缓存,所以这里为nullsetupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}//从scrapview缓存获取或加载一个新的itemview到此位置final View child = obtainView(position, mIsScrap);setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;
}

obtainView()

View obtainView(int position, boolean[] isScrap) {isScrap[0] = false;View scrapView;//再从RecycleBin的scrapview中获取屏幕外item的缓存,因为第一次layout,所以没有缓存scrapView = mRecycler.getScrapView(position);View child;if (scrapView != null) {    //这里为nullchild = mAdapter.getView(position, scrapView, this);if (child != scrapView) {mRecycler.addScrapView(scrapView);if (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}} else {isScrap[0] = true;dispatchFinishTemporaryDetach(child);}} else {child = mAdapter.getView(position, null, this);      //通过关联的Adapter的getView方法获取item的viewif (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}}return child;
}

第二次Layout

第二次Layout的流程和第一次差不多。

onLayout()->
layoutChildren()->       //会将第一次layout添加到ListView中的view全部添加到RecycleBin.mActiveViews中,然后通过detachAllViewsFromParent()将所有的itemview从ViewGroup中先移除,避免后面重复添加。
fillSpecific()->
makeAndAddView()->
setupChild()        //通过attachViewToParent()将一个之前detach的itemView重新attach到ViewGroup上

这次makeAndAddView方法中进入了不同的分支:

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {//这里通过getActiveView方法从RecycleBin的ActiveView中获取final View activeView = mRecycler.getActiveView(position);if (activeView != null) {   //前面将所有itemview加入到了RecycleBin的mActiveView中,所以这里不为nullsetupChild(activeView, position, y, flow, childrenLeft, selected, true);    //其中会通过attachViewToParent()将一个之前detach的itemView重新attach到ViewGroup上return activeView;}}final View child = obtainView(position, mIsScrap);setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;
}

滑动事件

滑动时mActiveViews缓存了滑动后还在屏幕内的itemview,如上面layout过程所分析。下面onTouchEvent方法只会使用mScrapViews屏幕外itemview的缓存。

调用链

onTouchEvent()->
trackMotionScroll()->    //如果该子View已经移出屏幕了,就会调用RecycleBin.addScrapView()将这个View加入到mScrapViews缓存当中,并将count计数器加1,计数器用于记录有多少个子View被移出了屏幕。还会调用detachViewsFromParent()将移出屏幕的itemView全部detach掉
fillGap()->
fillUp()/fillDown()->
makeAndAddView()->
obtainView()

因为在第二次layout过程中通过getActiveView获取过ActiveView中的itemview,所以在makeAndAddView()中就获取不到它们了,这个原因在 getActiveView() 中:

View getActiveView(int position) {int index = position - mFirstActivePosition;final View[] activeViews = mActiveViews;if (index >=0 && index < activeViews.length) {final View match = activeViews[index];activeViews[index] = null;      //可以看到,这里会将找到的itemview缓存置空return match;}return null;
}

所以下面会到obtainView获取屏幕外item的缓存或从Adapter的getView方法中加载。

View obtainView(int position, boolean[] isScrap) {isScrap[0] = false;View scrapView;scrapView = mRecycler.getScrapView(position);View child;if (scrapView != null) {  //addScrapView()方法将移出屏幕的View加入到了mScrapViews缓存当中,所以这里不为nullchild = mAdapter.getView(position, scrapView, this);  //可以看到,第二个参数为获取到的scrapView,这就是我们熟悉的View类型的convertview!if (child != scrapView) {mRecycler.addScrapView(scrapView);if (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}} else {isScrap[0] = true;dispatchFinishTemporaryDetach(child);}} else {child = mAdapter.getView(position, null, this);if (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}}return child;
}

上面obtainView方法中 child = mAdapter.getView(position, scrapView, this); 这行代码就解释了我们为什么要在Adapter的getView方法对第二个convertview做判断了,这样可以复用相同类型的itemview,避免通过inflate方法加载以提高效率。

顺便看看上面 mRecycler.getScrapView(position) 怎样获取ScrapView缓存:

View getScrapView(int position) {final int whichScrap = mAdapter.getItemViewType(position); //通过Adapter.getItemViewType方法获取itemview的类型,它同时是该类型ArrayList在mScrapViews这个ArrayList数组中的索引if (whichScrap < 0) {return null;}if (mViewTypeCount == 1) {    //只有一种itemview的类型就在mCurrentScrap中获取缓冲return retrieveFromScrap(mCurrentScrap, position);} else if (whichScrap < mScrapViews.length) { //否则就在该类型的ArrayList中获取缓存return retrieveFromScrap(mScrapViews[whichScrap], position);}return null;
}private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {final int size = scrapViews.size();if (size > 0) {//从后往前遍历是为了找到最近使用的itemview缓存for (int i = size - 1; i >= 0; i--) {final View view = scrapViews.get(i);final AbsListView.LayoutParams params =(AbsListView.LayoutParams) view.getLayoutParams();if (mAdapterHasStableIds) {final long id = mAdapter.getItemId(position);if (id == params.itemId) {return scrapViews.remove(i);}} else if (params.scrappedFromPosition == position) {final View scrap = scrapViews.remove(i);clearScrapForRebind(scrap);return scrap;}}final View scrap = scrapViews.remove(size - 1);clearScrapForRebind(scrap);return scrap;} else {return null;}
}

参考资料

Android ListView工作原理完全解析,带你从源码的角度彻底理解

ListView缓存机制小结相关推荐

  1. 解决ListView 缓存机制带来的显示不正常问题

    ListView加载数据原理:系统绘制ListView时,首先会用getCount()函数得到要绘制的这个列表的长度,然后开始逐行绘制.然后调用getView()函数,在这个函数里面首先获得一个Vie ...

  2. ListView缓存机制踩过的坑

    ListView,GrildView使用时候经常会用到缓存机制,随意一搜,例子成千上万,但是讲解都是很一致,跟自己踩的坑很少有人讲解到. 测试 需求: GrildView 分三列显示,默认背景为白色, ...

  3. Android笔记(二十五) ListView的缓存机制与BaseAdapter

    之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容 ...

  4. 【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析--缓存机制

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d... 作者:黄宁源 一,背景 RecyclerView是谷歌官方出的一 ...

  5. ListView和GridView的缓存机制及measure过程

    目录 前言 1.View的Transient状态 2.RecycleBin 3.obtainView 4.getView的调用 5.GridView的onMeasure 6.ListView的onMe ...

  6. Android学习——ListView的缓存机制

    在使用ListView的时候,需要加载适配器和数据源,这篇文章主要介绍一下ListView的使用以及利用ListView的缓存机制来减少系统的初始化时间. ListView的使用 ListView和V ...

  7. delphi listview 添加数据 慢_ListView 的缓存机制

    一.前言 ListView 作为一个 Android 5.x 之前的一个用于显示数据列表的控件,或许在今天都已经被 RecyclerView 完全替代,但是其中的缓存机制仍然值得我们去了解,对后面学习 ...

  8. android listview 缓存,探究Android ListView 的缓存机制

    概述 ListView 是继承AbListView,AbListView是所有列表类控件的基类. ListView的数据加载 在ListView数据加载中最关键的一个函数就是makeAndAddVie ...

  9. Picasso之图片缓存机制二ListView篇

    前面已经个大家介绍Picasso图片缓存机制,大家不熟悉请看上一篇文章http://blog.csdn.net/qq_15950325/article/details/52809380,其实Picas ...

最新文章

  1. ubuntu报错解决:The following packages have unmet dependencies:
  2. 修改服务器名后,sql server的配置处理
  3. tensorflow lstm 预测_图卷积神经网络GCN与递归结构RNN相结合的时间序列预测
  4. 三分钟玩转jQuery.noConflict()
  5. YOLOv5自定义数据集训练
  6. 自动摘要php,修改DEDECMS文章自动摘要长度或者取掉文章摘要
  7. ECshop安装及报错解决方案总结
  8. python webdriver save_Python + Selenium +Chrome 批量下载网页代码修改【新手必学】
  9. Linux Vim基本操作(文件的打开和编辑)完全攻略(有图有真相)
  10. Debian/Ubuntu系统下,apt-get update 、apt-get upgrade 、apt-get dist-upgrade 命令之间的区别
  11. 基于 XGBoost 对 Santander 银行用户购买行为进行预测
  12. arcgis已试图对空几何执行该操作_ArcGIS中坐标转换和投影变换
  13. 颜色恒常性评价指标——角度误差【弧度角度】避坑
  14. 多层高速PCB设计学习笔记(五)四层板实战(下)之阻抗控制计算(SI9000)
  15. oracle入门学习
  16. 遇到一个macOS下malware中毒很深的网友,安装的恶意软件MyCouponsmart、SearchMine.AnySearch、Advanced Mac Cleaner等真多!
  17. bandizip修改压缩文件内容_如何修改压缩文件的编码?
  18. python小程序之猜水果游戏
  19. 教你如何降低诺基亚E71的辐射
  20. rasa填槽slot

热门文章

  1. apache 2.4.X使用htpasswd 出现apache authentication failure passwd mismatch错误
  2. 大数据项目实践过程笔记
  3. 【C语言面试复试汇总】
  4. 【机器学习】Kmeans聚类(含代码)
  5. VISTA系统常识技巧集锦
  6. Python发送邮件(demo)
  7. 企业选择外贸B2B平台需要考虑哪些因素
  8. 学校人事管理系统python实现
  9. 0x5003eaed指令引用的0x00000000内存。该内存不能为read
  10. Java基础语法的思维导图