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