在自定义Adapter时,我们常常会重写Adapter的getView方法,该方法的签名如下所示:
public abstract View getView (int position, View convertView, ViewGroup parent)
此处会传入一个convertView变量,它的值有可能是null,也有可能不是null,如果不为null,我们就可以复用该convertView,对convertView里面的一些控件赋值后可以将convertView作为getView的返回值返回,这么做的目的是减少LayoutInflater.inflate()的调用次数,从而提升了性能(LayoutInflater.inflate()比较消耗性能)。
本文将介绍ListView中的RecycleBin机制,让大家对ListView中的优化机制有个概括的了解,同时也说明convertView的来龙去脉。
首先,我们知道,Adapter是数据源,AdapterView是展示数据源的UI控件,Adapter是给AdapterView使用的,通过调用AdapterView的setAdapter方法就可以让一个AdapterView绑定Adapter对象,从而AdapterView会将Adapter中的数据展示出来。
AdapterView的子类有AbsListView和AbsSpinner等,其中AbsListView的子类又有ListView、GridView等,所以ListView继承自AdapterView。
如果Adapter中有10000条数据,将这个Adapter对象赋给ListView,如果ListView创建10000个子View,那么App肯定崩溃了,因为Android没有能力同时绘制这么多的子View。而且,即便能同时绘制这10000个子View也没什么意义,因为手机的屏幕大小是有限的,有可能ListView的高度只能最多显示10个子View。基于此,Android在设计ListView这个类的时候,引入了RecycleBin机制—–对子View进行回收利用,RecycleBin直译过来就是回收站的意思。
RecycleBin基本原理
下面先简要说一下RecycleBin中的工作原理
在某一时刻,我们看到ListView中有许多View呈现在UI上,这些View对我们来说是可见的,这些可见的View可以称作OnScreen的View,即在屏幕中能看到的View,也可以叫做ActiveView,因为它们是在UI上可操作的。
当触摸ListView并向上滑动时,ListView上部的一些OnScreen的View位置上移,并移除了ListView的屏幕范围,此时这些OnScreen的View就变得不可见了,不可见的View叫做OffScreen的View,即这些View已经不在屏幕可见范围内了,也可以叫做ScrapView,Scrap表示废弃的意思,ScrapView的意思是这些OffScreen的View不再处于可以交互的Active状态了。ListView会把那些ScrapView(即OffScreen的View)删除,这样就不用绘制这些本来就不可见的View了,同时,ListView会把这些删除的ScrapView放入到RecycleBin中存起来,就像把暂时无用的资源放到回收站一样。
当ListView的底部需要显示新的View的时候,会从RecycleBin中取出一个ScrapView,将其作为convertView参数传递给Adapter的getView方法,从而达到View复用的目的,这样就不必在Adapter的getView方法中执行LayoutInflater.inflate()方法了。
RecycleBin中有两个重要的View数组,分别是mActiveViews和mScrapViews。这两个数组中所存储的View都是用来复用的,只不过mActiveViews中存储的是OnScreen的View,这些View很有可能被直接复用;而mScrapViews中存储的是OffScreen的View,这些View主要是用来间接复用的。
上面对mActiveViews和mScrapViews的说明比较笼统,其实在细节上还牵扯到Adapter的数据源发生变化的情况,具体细节后面会讲解。
源码解析
AdapterView是继承自ViewGroup的,ViewGroup中有addView方法可以向ViewGroup中添加子View,但是AdapterView重写了addView方法,如下所示:
@Override public void addView(View child) { throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); } @Override public void addView(View child, int index) { throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView"); }
在AdapterView的addView方法中会抛出异常,也就是说AdapterView禁用了addView方法。
在具体讲解之前,我们还是先花一点时间简要说一下View的每一帧的显示流程,当然,ListView也肯定遵循此流程。一个View要想在界面上呈现出来,需要经过三个阶段:measure->layout->draw。
View是一帧一帧绘制的,每一帧绘制都经历了measure->layout->draw这三个阶段,绘制完一帧之后,如果UI需要更新,比如用户滚动了ListView,那么又会绘制下一帧,再次经历measure->layout->draw方法
我们上面说了,AdapterView把addView方法给禁用了,那么ListView怎么向其中添加child呢?奥秘就在layout中,在布局的时候,ListView会执行layoutChildren方法,该方法是ListView对View进行添加以及回收的关键方法,RecycleBin的很多方法都在layoutChildren方法中被调用。在layoutChildren方法中实现对子View的增删,经过layoutChildren方法之后,ListView中所有的子View都是在屏幕中可见的,也就是说layoutChildren方法为接下来的帧绘制把子View准备完善了,这就保证了在后面的draw方法的执行过程中能够正确绘制ListView。
ListView的layoutChildren方法代码比较多,我们只研究和View增删相关的关键代码,主要分以下三个阶段:
ListView的children->RecycleBin
ListView清空children
RecycleBin->ListView的children
在layout这个方法刚刚开始执行的时候,ListView中的children其实还是上一帧中需要绘制的子View的集合,在layout这个方法执行完成的时候,ListView中的children就变成了当前帧马上要进行绘制的子View的集合。
如果有面试官问RecycleBin的机制是什么,怎么进行工作的,我觉得可以用以下的回答:
RecycleBin中有两个重要的View数组,分别是mActiveViews和mScrapViews。这两个数组中所存储的View都是用来复用的,只不过mActiveViews中存储的是OnScreen的View,这些View很有可能被直接复用;而mScrapViews中存储的是OffScreen的View,这些View主要是用来间接复用的。
当listview在布局的时候,会调用layoutChildren,该方法是ListView对View进行添加以及回收,这个方法分为三个阶段,一个是把listview的children都放到recycleBin中,然后listview清空children,最好把recycleBin中的view转换为listview的子view并显示在界面上
原文:https://blog.csdn.net/iispring/article/details/50967445