[1]ListView asks adapter “give me a view” (getView) for each item of the list.(通过getView来获取每个item)
[2]A new View is returned and displayed(获取到后返回显示)
那么如果我们有大量的数据需要显示的时候,每个Item都去重复执行getView中的创建新的View的动作吗?这样做会耗费大量的资源去执行重复的事情,实际上Android为我们提供了一套重复利用的机制叫做“Recycler”:
原理简单描述下就是这样:
在一个完整的ListView第一次出现时,每个Item都是Null的,getView的时候会跑到需要inflate一个Item的代码段,假设整个view只能最多显示10个item,那么当滑动到第11个Item的时候,第一个item会放入“recycler”,如果第11个Item和放入“Recycler”的item的view一致,那么就会使用"Recycler"里面的Item来显示,从而不用再重复inflate一次,这样大大节省了创建View的工作,在需要显示大量数据时显得尤为重要。
工作原理的示意图如下:
学习自http://android.amberfog.com/?p=296:
Demo:
这是一个getView的方法,其他细节的Code就不显示了
- static class ViewHolder
- {
- public ImageView localImageView = null;
- public TextView localTextView1 = null;
- public TextView localTextView2 = null;
- public TextView localTextView3 = null;
- }
- public View getView(int paramInt, View paramView, ViewGroup paramViewGroup)
- {
- logger.i("This is position:" + paramInt);
- WrapperSonglist localUserShareSonglistEntity = (WrapperSonglist) getItem(paramInt);
- if(localUserShareSonglistEntity != null)
- {
- ViewHolder holder = null;
- if(paramView == null)
- {
- this.logger.d("convertView == null,Then inflate and findViewById");
- paramView = this.mInflater.inflate(R.layout.listitem04, paramViewGroup, false);
- holder = new ViewHolder();
- holder.localImageView = (ImageView) paramView.findViewById(R.id.listitem04ImageView);
- holder.localTextView1 = (TextView) paramView.findViewById(R.id.listitem04TextView01);
- holder.localTextView2 = (TextView) paramView.findViewById(R.id.listitem04TextView02);
- holder.localTextView3 = (TextView) paramView.findViewById(R.id.listitem04TextView03);
- paramView.setTag(holder);
- }
- else
- {
- //Used ViewHolder to improve performance
- this.logger.d("convertView != null,Then findViewById(get Holder)");
- holder = (ViewHolder) paramView.getTag();
- }
- if(paramView != null)
- {
- this.logger.d("convertView != null,Then SetValue");
- String mstr = localUserShareSonglistEntity.getSonglistImage();
- int id = localUserShareSonglistEntity.getSonglistId();
- holder.localTextView1.setText("[id]:"+id+",bitmap:url:"+mstr);
- String name = localUserShareSonglistEntity.getSonglistName();
- holder.localTextView2.setText("[Name]:"+name);
- String url = localUserShareSonglistEntity.getSonglistUrl();
- holder.localTextView3.setText("[Url]:"+url);
- }
- }
- return paramView;
- }
当我们第一次启动到Listview的时候如下,只显示了5个Item,那么getView被调用5次:
打印的Log如下:可以看到从0-4都是null,我们需要做inflate的动作,
- 01-12 17:58:22.144: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:0
- 01-12 17:58:22.154: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.174: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:1
- 01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.184: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:2
- 01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.194: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:3
- 01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.204: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:4
- 01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.214: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<strong>
- </strong>
当我们小心往下滑动一个位置,刚出现第6个Item,此时第1个还没有消失时,
这个时候只打印了一个获取第6个Item的Log,如下:可以看到第6个Item还是为null,需要inflate出来新的
- 01-12 18:02:37.623: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:5
- 01-12 18:02:37.623: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 18:02:37.633: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<strong>
- </strong>
如果此时再往下滑动列表,第1个item此时会放入Recycler,第7个Item此时不再是null,而是利用了第1个item的View,如下:
从下面的Log,可以看到从第7个开始就不是null了
01-12 18:52:36.243: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:6
01-12 18:52:36.243: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:36.243: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:36.693: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:7
01-12 18:52:36.693: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:36.693: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:37.024: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:8
01-12 18:52:37.024: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:37.034: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:37.604: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:9
01-12 18:52:37.604: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:37.604: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
等所有的item,一共10个都显示之后,不管上下滑动都再也不是NULL了,说明这个时候都是使用Recycle里面的view,而不会再重新inflate了,显然这样节省很多重复的操作
【1】重复多轮调用getView的解决方案
上面的例子我们可以看到,每滑动一次到需要显示的Item的时候就会调用一次getView,理论上是10个Item,均显示一次的话是要调用getView() 10次的,那么为什么有时候很奇怪,10个item显示一次也许会调用getView 20次,甚至40-50次呢?我想肯定很多人都遇到过这个问题
查了很久,其实我也没有找到root cause,只是知道我们在XML文件里面定义listView的时候需要设置height为fill_parent或者是指定的高度值(这个方法明显不靠谱,设置了高度,那么怎么进行不同屏幕的适配)
所以推荐使用下面的,例如:
<ListView
android:id="@+id/activity04_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
另附:
ANDROID LISTVIEW GETVIEW() 反复调用
最近做项目发现一个界面当有ListView是,getView和getCount中的log被疯狂调用。一个5个Item的ListView,getView竟然会被反复调用7组。尤其是当ItemView中需要加载图片时,很容易造成GC过多,很容易出现ANR。
原因就在于measure过程,ListView一般都会有好多个Item,而且也会同时显示若干组Item,这些Item的父元素都是这个ListView。
更具Google的解释,View在Draw的时候分成两个阶段:measure和layout,在measure阶段时主要就是为了计算两个参数:height和width。而且要注意的是,这是个递归的过程,从顶向下,DecorView开始依次调用自己子元素的measure。计算完成这两个参数后就开始layout,最后再是draw的调用。
对于ListView,当然每一个Item都会被调用measure方法,而在这个过程中getView和getCount会被调用,而且看用户的需求,可能会有很多次调用。
而为什么会有很多组次调用呢?
问题就在于在layout中的决定ListView或者它的父元素的height和width属性的定义了。fillparent会好一点,计算方法会比较简单,只要跟父元素的大小相似就行,但是即使是fillparent,也不能给View当饭吃,还是要计算出来具体的dip,所以measure还是会被调用,只是可能比wrapcontent的少一点。至于自适应的它会一直考量它的宽和高,根据内容(也就是它的子Item)计算宽高。可能这个measure过程会反复执行,如果父元素也是wrapcontent,这个过程会更加漫长。
所以,解决方法就是尽量避免自适应,除非是万不得已,固定大小或者填充的效果会比较好一些。
当我们在使用listview的时候。有时候自定义adapter的时候,是不是会发现在getview里打印日志的时候,重复调用很多次?有时候4次。有的严重甚至到10次,当我们在listview中移动的时候。每移动一列都会调用很多次,这样大大影响到效率!其实这和listview本身在android上的机制有关。下面我开始来介绍一下吧:
在布局,我们只有一个listview的时候。那好。我们把高设置成wrap_content的时候。在listview里加载几行看看。日志在getview里打印一下。是不是重复调用了?那这个办法就好弄了。把高设置成fill_parent就成了。这个时候发现日志还是重复调用?那就要看一下Listview的上一级而已的高是不是也是设置也fill_parent的,如果不是。请改动吧。如果是。。。那我还真没碰到重复调用的!因为测试好几次了!
如果我们在而已里不只一个Listview。一个复杂好看的布局可能有很多。listview在布局的某个地方。这个时候有时候运气不好。你会发现你调用了很多次getview。我测试的时候。最高230次。。。可想而知。这个速度是相当慢。而且每移动一次就是调用这么多次!对于这样的情况,在修改布局的时候,要考虑以下两点:1.首先考虑需求布局和性能哪个更重要一点。2.考虑listview周边哪个布局控件影响到了它!
如果在性能上没有太大影响,而需求要求必需是那样的布局。那就以布局为主。看看有没有别的方法来优化一下listview,当然前提是布局一点都不能调整。如果能调整,布局没有太大变动。而listview又能很好的优化。那就当然优化了!当我们优化的时候。首先要看一下有没有影响到Listview重绘的控件,比如。如果它上面和下面都有控件。而且高都是wrap_content,那么你就要设置成fill_parent或者固定高。这样listview在高上就不会重绘,这是最主要的一点。那左右是不是也有控件(一般一个手机页面用到list的时候不会有这么多控件)?有,那我们就也要调整,那就同高一样的设置。一定要让listview是一个固定在那个地方不动的。不然,你就等着让他重复去调用吧!
其实说了这么多。最主要的还是在我们进行布局的时候。要巧妙的运用每个控件的属性,以及了解控件每个的原理。这样在我们进行UI设计的时候,才能很好的去结合!
原文地址: http://my.eoe.cn/745790/archive/1216.html