ListView缓存机制简述

listview的缓存机制学习自郭霖大神的博客:

https://blog.csdn.net/guolin_blog/article/details/45586553

首先要知道listview是个什么东西,listview和gridview都继承自AbsListview,而AbsListview则是一个Viewgroup。

Listview缓存中要用到一个类RecycleBin,这是AbsListView的内部类,所以GridView缓存用的也是这个。Recyclebin中有两个view数组,mActiveViews,mCurrentScrap,还有一个view的二维数组mScrapViews,这是因为RecyclerBin支持根据不同的viewtype获取不同的缓存数组,一般用不到,我们现在只关注mActiveViews和mCurrentScrap。Recyclebin中


首先了解几个方法:

makeAndAddView:尝试从recyclebin的mActiveViews中取出对应位置的view并调用setupChild(view,.....),取不到的话则使用obtainView获取一个View并调用setupChilld。

obtainView:尝试从recycleBin的mCurrentScraps中取出一个view,有的话调用adapter的getView并传入中这个缓存view作为convertView,没有话传入空值(这个大家应该很熟悉了吧)。

setupChild:根据传入的最后一个boolean参数的不同对参数中的view有两种处理方式(都是Viewgroup的方法),分别是addViewInLayout和attachViewToParent,分别代表向viewgroup中加入一个新元素和复用一个以前被detach的元素。其实两个方法最终都是调用addInArray将元素加入children数组中,区别在于add方法在加入前有一个动画的判断执行。

RecycleBin的fillActiveVuew(int,int):将从序号a开始的b个子元素放入mActiveViews。



1.listview初始化过程:

onLayout中会调用layoutChildren,layoutChildren中首先调用recyclerbin的fillActiveView,然后调用detachAllViewsFromParent将所有子元素清除(将所有子元素mParent=null并将数组中所有引用=null),然后判断子元素数是否是0

(1)等于0:调用fillFromTop,fillFromTop调用fillDown,fillDown中会一直通过makeAndAddView创建View直到总高度超出屏幕。

(2)不等于0:调用fillSpec,和fillDown差不多,也是通过makeAndAddView获取view。


现在走一遍过程(view会调用两次onLayout,原因不明):首先layoutChildren中调用fillActiveViews,因为这时候并没有子元素所以这个方法执行没有意义。接下来清除所有子元素,依然没有意义。然后因为子元素数=0所以调用fillDown创建铺满一屏幕多的view(fillDown通过makeAddAddView创建view,在makeAndAddView中由于mActiveViews为空所以调用obtainView,obtainView中由于mCurrentScraps为空调用adapter的getView创建view,注意这时候传入getView的convertView为空。)另外,这时makeAndAddView中创建view后setupChild走的是addViewInLayout(见上)。

接下来第二遍,调用fillActiveViews,将所有子元素放入mAtiveViews,然后清除所有子元素,因为子元素数!=0所以调用fillSpec,依然是makeAndAddView,这时候就可以从mActiveViews中直接获取到对应位置的View了,然后setupChild走的是attachViewToParent。(注意取出后对应View直接在mActiveViews中删除,全取出后mActiveViews又空了)


总结一下,因为listview是一个viewgroup,所以这一系列操作实际上就是为了确定它的子元素children数组,然后再进行下一步的绘制。首先第一轮创建一屏幕多的view(addViewInLayout),第二轮先缓存所有子元素然后清除子元素数组(避免重复加入),然后再从缓存中取出并重新进入子元素数组(attachViewToParent)。


2.滑动列表:

看到一篇博客说视图每次更新的时候都会走一遍measure,layout,draw,比如滑动列表的时候。这让我很懵逼,每一帧都要重复这些操作?于是我打印了一下,发现滑动列表的时候并不会触发这些。。。。。

列表滑动处理在重写的onTouchListener中,主要是对MOVE事件的处理。首先会对滑出屏幕的view进行计数,为什么要计数而不是滑出一个的时候就处理呢?刚开始我也有这个疑问,但是我想了想,大多数列表的item高度都一样,但是也有不一样的啊,如果说一个列表显示5个item,第五个的height是好几个屏幕那么大,那么由上面的过程我们已知这个item已经完全创建出来了,即使前四个全部滑出也不需要处理。所以这里先进行一个计数,直到最后一个item滑出屏幕,这时候就调用方法将所有滑出的Item放入mCurrentScraps并加载新的item。

至于创建新的item,用的还是makeAndAddView,因为mActiveViews为空所以走obtainView,这时候就可以从mCurrentviews里取缓存交给adapter的getView了。


3.手动更新:

当列表数据更新的时候可以调用adapter的notifyDataSetChanged,这个过程实际上是一个观察者模式的应用。Adapter作为被观察者维护着一个观察者列表,Listview在setAdapter时创建一个自己的内部类对象并且注册到adapter的观察者注册表里,adapter调用notifyDataSetChanged时候就遍历注册表回调观察者的onChanged方法,其实现就是调用requestLayout。重新测量布局,全部刷新。

其他:mCurrentScrps中是无序的,取的时候直接返回并删除数组尾部元素。

总结一下,mActiveViews用于第一屏的缓存,mCurrentScraps用于滑出的缓存然后在新加载的时候交给getView。


猜你喜欢

转载自blog.csdn.net/zhang___yong/article/details/79902095
今日推荐