GridView异步加载中一次加载完所有数据问题的解决以及其原因分析
今天在开发一个相册应用的时候遇到一个很奇怪的问题,用于显示照片的GridView在显示的时候,初次加载,getView就被调用了1000次,而我的所有图片也只有1000张,也就是说在还没有滚动的情况下GridView就已经把所有的数据显示完了(当然超出屏幕的是看不见的),但是GridView本身是只显示视野范围内的数据项的啊。如果这样GridView的子view复用还有什么意义,GridView一直都是按需加载的啊。
下面是getView中打印出来的position值
显示安卓系统中图片是肯定需要异步加载的,如果一次就异步的方式去加载1000张照片,所消耗的系统资源可想而知,实际情况是我的应用直接就黑屏了。而即便没有开启异步加载如果第一次getView就调用了1000次,那么说明一次就生成了1000个子View,这样虽然应用不会死,也会出现上图中的渲染警告:Skipped 77 frames! The application may be doing too much work on its main thread.
出现这个问题让我很无奈,因为我根本就不知道我到底哪里错了,我都是按照正常方式来使用GridView的。
经过无数次debug,终于找出了问题的所在。
为了复线出这种情况,先讲讲我是如何使用的。之所以把这两部分代码提出来,是因为用替换法我发现问题就出在这里。
用于显示照片的GridView的adapter中getView 是这样实现的:
@Override public View getView(final int position, View convertView, ViewGroup parent) { if (DEBUG) Log.i(TAG, "position = " + position); ViewHolder holder = null; if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.item_image, null); holder = new ViewHolder(); holder.imageView = (ImageView) convertView .findViewById(R.id.imageView); convertView.setTag(holder); } holder = (ViewHolder) convertView.getTag(); mLoader.DisplayImage(mImageList.get(position), holder.imageView); return convertView; }
其中mLoader.DisplayImage(mImageList.get(position), holder.imageView);是开启一个加载图片的线程,也就是异步加载。
R.layout.item_image代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@null" /> </LinearLayout>
如上使用,则会出现刚刚提到的怪异情况。
但是我发现在ImageView
中,android:layout_height
设置一个高度就不会出现这样的问题。或者是给ImageView
设置一个padding也不会出现这样的问题,这些高度值我试了不同的数值,发现值越小,getView调用的次数越多,当为1px的时候差不多就接近1000次了,其实这个很好理解,因为值越小 每个item的高度越小,可见范围内就能显示越多的item。但是这个数值接近1000则给我我很大的触动。我一下意识到这个跟ImageView
的layout_height
为wrap_content
有关。
因为ImageView的图片资源是异步加载,所以在getView返回return
convertView的时候
ImageView其实是没有任何内容的,而
wrap_content
也就意味着其实际高度为0,因此不管你的ImageView在异步数据完成之后有多大,GridView都认为自己的高度足以显示完所有的item(因为在返回
convertView
的时候高度为0)。
找到了问题之后我们对症下药,完美解决。
但是还是有必要总结一下出现这种情况的时机,因为一般情况很少见,我看网上也很少有人提到这个问题,唯一看到有人对此提问还是在stackoverflow上,而且没有人回答正确 http://stackoverflow.com/questions/11152992/forbid-gridview-to-load-all-views-at-once。
1.item的xml中的控件不管有多少层,必须是可能有一个高度为0的情况出现,比如ImageView或者是LinearLayout
高度为wrap_content
,如果是有类似于TextView的控件,则绝不会出出现这种bug,因为TextView是有一个默认最小高度的。
2.通常情况下发生在异步加载的时候,因为即便ImageView或者是LinearLayout
高度为wrap_content,如果不是异步加载,他们的内容都会立马赋值,所以就会产生一个实际高度。
3.最普遍的是内容只是单个ImageView的情况,因为
wrap_content
的ImageView
在没有设置图片资源之前,高度是为0的。而且真正需要异步加载的往往也只有ImageView
。
最后给点建议:
虽然上面说item中有TextView绝不会出现无尽的加载完所有数据的异常情况,但是我们还是希望
TextView
(或者其他)能够在返回
之前确保高度是和实际内容一致的,不然即便是没有加载很多,也是多余预期的。convertView