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则给我我很大的触动。我一下意识到这个跟ImageViewlayout_heightwrap_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` `` ```之前确保高度是和实际内容一致的,不然即便是没有加载很多,也是多余预期的。