日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android ListView异步加载图片乱序问题,原因分析及解决方案

發布時間:2025/6/15 Android 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android ListView异步加载图片乱序问题,原因分析及解决方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載請注明出處:http://blog.csdn.net/guolin_blog/article/details/45586553

在Android所有系統自帶的控件當中,ListView這個控件算是用法比較復雜的了,關鍵是用法復雜也就算了,它還經常會出現一些稀奇古怪的問題,讓人非常頭疼。比如說在ListView中加載圖片,如果是同步加載圖片倒還好,但是一旦使用異步加載圖片那么問題就來了,這個問題我相信很多Android開發者都曾經遇到過,就是異步加載圖片會出現錯位亂序的情況。遇到這個問題時,不少人在網上搜索找到了相應的解決方案,但是真正深入理解這個問題出現的原因并對癥解決的人恐怕還并不是很多。那么今天我們就來具體深入分析一下ListView異步加載圖片出現亂序問題的原因,以及怎么樣對癥下藥去解決它。

本篇文章的原理基礎建立在上一篇文章之上,如果你對ListView的工作原理還不夠了解的話,建議先去閱讀 Android ListView工作原理完全解析,帶你從源碼的角度徹底理解?。

問題重現

要想解決問題首先我們要把問題重現出來,這里只需要搭建一個最基本的ListView項目,然后在ListView中去異步請求圖片并顯示,問題就能夠得以重現了,那么我們就新建一個ListViewTest項目。

項目建好之后第一個要解決的是數據源的問題,由于ListView中需要從網絡上請求圖片,那么我就提前準備好了許多張圖片,將它們上傳到了我的CSDN相冊當中,然后新建一個Images類,將所有相冊中圖片的URL地址都配置進去就可以了,代碼如下所示:

[java] view plaincopy
  • /**?
  • ?*?原文地址:?http://blog.csdn.net/guolin_blog/article/details/45586553?
  • ?*?@author?guolin?
  • ?*/??
  • public?class?Images?{??
  • ??
  • ????public?final?static?String[]?imageUrls?=?new?String[]?{??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",????
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760704_5707.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760685_5091.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760685_4444.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760684_8827.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760683_3691.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760683_7315.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760663_7318.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760662_3454.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760662_5113.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760661_3305.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760661_7416.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760589_2946.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760589_1100.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760588_8297.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760587_2575.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760587_8906.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760550_2875.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760550_9517.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760549_7093.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760549_1352.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760548_2780.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760531_1776.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760531_1380.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760530_4944.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760530_5750.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760529_3289.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760500_7871.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760498_7007.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760478_3128.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760478_6766.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760477_1358.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760477_3540.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760476_1240.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760446_7993.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760446_3641.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",??
  • ????????"https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg",??
  • ????};??
  • }??
  • 設置好了圖片源之后,我們需要一個ListView來展示所有的圖片。打開或修改activity_main.xml中的代碼,如下所示: [html] view plaincopy
  • <LinearLayout?xmlns:android="http://schemas.android.com/apk/res/android"??
  • ????android:layout_width="match_parent"??
  • ????android:layout_height="match_parent"???
  • ????android:orientation="vertical">??
  • ??
  • ????<ListView??
  • ????????android:id="@+id/list_view"??
  • ????????android:layout_width="match_parent"??
  • ????????android:layout_height="match_parent"??
  • ????????>??
  • ????</ListView>??
  • ??
  • </LinearLayout>??
  • 很簡單,只是在LinearLayout中寫了一個ListView而已。接著我們要定義ListView中每一個子View的布局,新建一個image_item.xml布局,加入如下代碼:

    [html] view plaincopy
  • <?xml?version="1.0"?encoding="utf-8"?>??
  • <LinearLayout?xmlns:android="http://schemas.android.com/apk/res/android"??
  • ????android:layout_width="match_parent"??
  • ????android:layout_height="match_parent"?>??
  • ??
  • ????<ImageView??
  • ????????android:id="@+id/image"??
  • ????????android:layout_width="match_parent"??
  • ????????android:layout_height="120dp"??
  • ????????android:src="@drawable/empty_photo"???
  • ????????android:scaleType="fitXY"/>??
  • ??
  • </LinearLayout>??
  • 仍然很簡單,image_item.xml布局中只有一個ImageView控件,就是用它來顯示圖片的,控件在默認情況下會顯示一張empty_photo。這樣我們就把所有的布局文件都寫好了。

    接下來新建ImageAdapter做為ListView的適配器,代碼如下所示:

    [java] view plaincopy
  • /**??
  • ?*?原文地址:?http://blog.csdn.net/guolin_blog/article/details/45586553??
  • ?*?@author?guolin??
  • ?*/????
  • public?class?ImageAdapter?extends?ArrayAdapter<String>?{??
  • ??
  • ????/**?
  • ?????*?圖片緩存技術的核心類,用于緩存所有下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。?
  • ?????*/??
  • ????private?LruCache<String,?BitmapDrawable>?mMemoryCache;??
  • ??
  • ????public?ImageAdapter(Context?context,?int?resource,?String[]?objects)?{??
  • ????????super(context,?resource,?objects);??
  • ????????//?獲取應用程序最大可用內存??
  • ????????int?maxMemory?=?(int)?Runtime.getRuntime().maxMemory();??
  • ????????int?cacheSize?=?maxMemory?/?8;??
  • ????????mMemoryCache?=?new?LruCache<String,?BitmapDrawable>(cacheSize)?{??
  • ????????????@Override??
  • ????????????protected?int?sizeOf(String?key,?BitmapDrawable?drawable)?{??
  • ????????????????return?drawable.getBitmap().getByteCount();??
  • ????????????}??
  • ????????};??
  • ????}??
  • ??
  • ????@Override??
  • ????public?View?getView(int?position,?View?convertView,?ViewGroup?parent)?{??
  • ????????String?url?=?getItem(position);??
  • ????????View?view;??
  • ????????if?(convertView?==?null)?{??
  • ????????????view?=?LayoutInflater.from(getContext()).inflate(R.layout.image_item,?null);??
  • ????????}?else?{??
  • ????????????view?=?convertView;??
  • ????????}??
  • ????????ImageView?image?=?(ImageView)?view.findViewById(R.id.image);??
  • ????????BitmapDrawable?drawable?=?getBitmapFromMemoryCache(url);??
  • ????????if?(drawable?!=?null)?{??
  • ????????????image.setImageDrawable(drawable);??
  • ????????}?else?{??
  • ????????????BitmapWorkerTask?task?=?new?BitmapWorkerTask(image);??
  • ????????????task.execute(url);??
  • ????????}??
  • ????????return?view;??
  • ????}??
  • ??
  • ????/**?
  • ?????*?將一張圖片存儲到LruCache中。?
  • ?????*??
  • ?????*?@param?key?
  • ?????*????????????LruCache的鍵,這里傳入圖片的URL地址。?
  • ?????*?@param?drawable?
  • ?????*????????????LruCache的值,這里傳入從網絡上下載的BitmapDrawable對象。?
  • ?????*/??
  • ????public?void?addBitmapToMemoryCache(String?key,?BitmapDrawable?drawable)?{??
  • ????????if?(getBitmapFromMemoryCache(key)?==?null)?{??
  • ????????????mMemoryCache.put(key,?drawable);??
  • ????????}??
  • ????}??
  • ??
  • ????/**?
  • ?????*?從LruCache中獲取一張圖片,如果不存在就返回null。?
  • ?????*??
  • ?????*?@param?key?
  • ?????*????????????LruCache的鍵,這里傳入圖片的URL地址。?
  • ?????*?@return?對應傳入鍵的BitmapDrawable對象,或者null。?
  • ?????*/??
  • ????public?BitmapDrawable?getBitmapFromMemoryCache(String?key)?{??
  • ????????return?mMemoryCache.get(key);??
  • ????}??
  • ??
  • ????/**?
  • ?????*?異步下載圖片的任務。?
  • ?????*??
  • ?????*?@author?guolin?
  • ?????*/??
  • ????class?BitmapWorkerTask?extends?AsyncTask<String,?Void,?BitmapDrawable>?{??
  • ??
  • ????????private?ImageView?mImageView;??
  • ??
  • ????????public?BitmapWorkerTask(ImageView?imageView)?{??
  • ????????????mImageView?=?imageView;??
  • ????????}??
  • ??
  • ????????@Override??
  • ????????protected?BitmapDrawable?doInBackground(String...?params)?{??
  • ????????????String?imageUrl?=?params[0];??
  • ????????????//?在后臺開始下載圖片??
  • ????????????Bitmap?bitmap?=?downloadBitmap(imageUrl);??
  • ????????????BitmapDrawable?drawable?=?new?BitmapDrawable(getContext().getResources(),?bitmap);??
  • ????????????addBitmapToMemoryCache(imageUrl,?drawable);??
  • ????????????return?drawable;??
  • ????????}??
  • ??
  • ????????@Override??
  • ????????protected?void?onPostExecute(BitmapDrawable?drawable)?{??
  • ????????????if?(mImageView?!=?null?&&?drawable?!=?null)?{??
  • ????????????????mImageView.setImageDrawable(drawable);??
  • ????????????}??
  • ????????}??
  • ??
  • ????????/**?
  • ?????????*?建立HTTP請求,并獲取Bitmap對象。?
  • ?????????*??
  • ?????????*?@param?imageUrl?
  • ?????????*????????????圖片的URL地址?
  • ?????????*?@return?解析后的Bitmap對象?
  • ?????????*/??
  • ????????private?Bitmap?downloadBitmap(String?imageUrl)?{??
  • ????????????Bitmap?bitmap?=?null;??
  • ????????????HttpURLConnection?con?=?null;??
  • ????????????try?{??
  • ????????????????URL?url?=?new?URL(imageUrl);??
  • ????????????????con?=?(HttpURLConnection)?url.openConnection();??
  • ????????????????con.setConnectTimeout(5?*?1000);??
  • ????????????????con.setReadTimeout(10?*?1000);??
  • ????????????????bitmap?=?BitmapFactory.decodeStream(con.getInputStream());??
  • ????????????}?catch?(Exception?e)?{??
  • ????????????????e.printStackTrace();??
  • ????????????}?finally?{??
  • ????????????????if?(con?!=?null)?{??
  • ????????????????????con.disconnect();??
  • ????????????????}??
  • ????????????}??
  • ????????????return?bitmap;??
  • ????????}??
  • ??
  • ????}??
  • ??
  • }??
  • ImageAdapter中的代碼還算是比較簡單的,在getView()方法中首先根據當前的位置獲取到圖片的URL地址,然后使用inflate()方法加載image_item.xml這個布局,并獲取到ImageView控件的實例,接下來開啟了一個BitmapWorkerTask異步任務來從網絡上加載圖片,最終將加載好的圖片設置到ImageView上面。注意這里為了防止圖片占用過多的內存,我們還是使用了LruCache技術來進行內存控制,對這個技術不熟悉的朋友可以參考我之前的一篇文章 Android高效加載大圖、多圖解決方案,有效避免程序OOM?。

    最后,程序主界面的代碼就非常簡單了,修改MainActivity中的代碼,如下所示:

    [java] view plaincopy
  • /**???
  • ?*?原文地址:?http://blog.csdn.net/guolin_blog/article/details/45586553???
  • ?*?@author?guolin???
  • ?*/??????
  • public?class?MainActivity?extends?Activity?{??
  • ??????
  • ????private?ListView?listView;??
  • ??????
  • ????@Override??
  • ????protected?void?onCreate(Bundle?savedInstanceState)?{??
  • ????????super.onCreate(savedInstanceState);??
  • ????????setContentView(R.layout.activity_main);??
  • ????????listView?=?(ListView)?findViewById(R.id.list_view);??
  • ????????ImageAdapter?adapter?=?new?ImageAdapter(this,?0,?Images.imageThumbUrls);??
  • ????????listView.setAdapter(adapter);??
  • ????}??
  • ??
  • ??
  • }??
  • 這就是整個程序所有的代碼了,記得還需要在AndroidManifest.xml中添加INTERNET權限。

    那么目前程序的思路其實是很簡單的,我們在ListView的getView()方法中開啟異步請求,從網絡上獲取圖片,當圖片獲取成功就后就將圖片顯示到ImageView上面。看起來沒什么問題對嗎?那么現在我們就來運行一下程序看一看效果吧。


    恩?怎么會這個樣子,當滑動ListView的時候,圖片竟然會自動變來變去,而且圖片顯示的位置也不正確,簡直快亂成一鍋粥了!可是我們所有的邏輯都很簡單呀,怎么會導致出現這種圖片自動變來變去的情況?很遺憾,這是由于Listview內部的工作機制所導致的,如果你對Listview的工作機制不了解,那么就會很難理解這種現象,不過好在上篇文章中我已經講解過ListView的工作原理了,因此下面就讓我們一起分析一下這個問題出現的原因。

    原因分析

    上篇文章中已經提到了,ListView之所以能夠實現加載成百上千條數據都不會OOM,最主要在于它內部優秀的實現機制。雖然作為普通的使用者,我們大可不必關心ListView內部到底是怎么實現的,但是當你了解了它的內部原理之后,很多之前難以解釋的問題都變得有理有據了。

    ListView在借助RecycleBin機制的幫助下,實現了一個生產者和消費者的模式,不管有任意多條數據需要顯示,ListView中的子View其實來來回回就那么幾個,移出屏幕的子View會很快被移入屏幕的數據重新利用起來,原理示意圖如下所示:


    那么這里我們就可以思考一下了,目前數據源當中大概有60個圖片的URL地址,而根據ListView的工作原理,顯然不可能為每張圖片都單獨分配一個ImageView控件,ImageView控件的個數其實就比一屏能顯示的圖片數量稍微多一點而已,移出屏幕的ImageView控件會進入到RecycleBin當中,而新進入屏幕的元素則會從RecycleBin中獲取ImageView控件。

    那么,每當有新的元素進入界面時就會回調getView()方法,而在getView()方法中會開啟異步請求從網絡上獲取圖片,注意網絡操作都是比較耗時的,也就是說當我們快速滑動ListView的時候就很有可能出現這樣一種情況,某一個位置上的元素進入屏幕后開始從網絡上請求圖片,但是還沒等圖片下載完成,它就又被移出了屏幕。這種情況下會產生什么樣的現象呢?根據ListView的工作原理,被移出屏幕的控件將會很快被新進入屏幕的元素重新利用起來,而如果在這個時候剛好前面發起的圖片請求有了響應,就會將剛才位置上的圖片顯示到當前位置上,因為雖然它們位置不同,但都是共用的同一個ImageView實例,這樣就出現了圖片亂序的情況。

    但是還沒完,新進入屏幕的元素它也會發起一條網絡請求來獲取當前位置的圖片,等到圖片下載完的時候會設置到同樣的ImageView上面,因此就會出現先顯示一張圖片,然后又變成了另外一張圖片的情況,那么剛才我們看到的圖片會自動變來變去的情況也就得到了解釋。

    問題原因已經分析出來了,但是這個問題該怎么解決呢?說實話,ListView異步加載圖片的問題并沒有什么標準的解決方案,很多人都有自己的一套解決思路,這里我準備給大家講解三種比較經典的解決辦法,大家通過任何一種都可以解決這個問題,但是我們每多學習一種思路,水平就能夠更進一步的提高。

    解決方案一 ?使用findViewWithTag

    findViewWithTag算是一種比較簡單易懂的解決方案,其實早在 Android照片墻應用實現,再多的圖片也不怕崩潰?這篇文章當中,我就采用了findViewWithTag來避免圖片出現亂序的情況。那么這里我們先來看看怎么通過修改代碼把這個問題解決掉,然后再研究一下findViewWithTag的工作原理。

    使用findViewWithTag并不需要修改太多的代碼,只需要改動ImageAdapter這一個類就可以了,如下所示:

    [java] view plaincopy
  • /**??
  • ?*?原文地址:?http://blog.csdn.net/guolin_blog/article/details/45586553??
  • ?*?@author?guolin??
  • ?*/????
  • public?class?ImageAdapter?extends?ArrayAdapter<String>?{??
  • ??????
  • ????private?ListView?mListView;???
  • ??
  • ????......??
  • ??
  • ????@Override??
  • ????public?View?getView(int?position,?View?convertView,?ViewGroup?parent)?{??
  • ????????if?(mListView?==?null)?{????
  • ????????????mListView?=?(ListView)?parent;????
  • ????????}???
  • ????????String?url?=?getItem(position);??
  • ????????View?view;??
  • ????????if?(convertView?==?null)?{??
  • ????????????view?=?LayoutInflater.from(getContext()).inflate(R.layout.image_item,?null);??
  • ????????}?else?{??
  • ????????????view?=?convertView;??
  • ????????}??
  • ????????ImageView?image?=?(ImageView)?view.findViewById(R.id.image);??
  • ????????image.setImageResource(R.drawable.empty_photo);??
  • ????????image.setTag(url);??
  • ????????BitmapDrawable?drawable?=?getBitmapFromMemoryCache(url);??
  • ????????if?(drawable?!=?null)?{??
  • ????????????image.setImageDrawable(drawable);??
  • ????????}?else?{??
  • ????????????BitmapWorkerTask?task?=?new?BitmapWorkerTask();??
  • ????????????task.execute(url);??
  • ????????}??
  • ????????return?view;??
  • ????}??
  • ??
  • ????......??
  • ??
  • ????/**?
  • ?????*?異步下載圖片的任務。?
  • ?????*??
  • ?????*?@author?guolin?
  • ?????*/??
  • ????class?BitmapWorkerTask?extends?AsyncTask<String,?Void,?BitmapDrawable>?{??
  • ??
  • ????????String?imageUrl;???
  • ??
  • ????????@Override??
  • ????????protected?BitmapDrawable?doInBackground(String...?params)?{??
  • ????????????imageUrl?=?params[0];??
  • ????????????//?在后臺開始下載圖片??
  • ????????????Bitmap?bitmap?=?downloadBitmap(imageUrl);??
  • ????????????BitmapDrawable?drawable?=?new?BitmapDrawable(getContext().getResources(),?bitmap);??
  • ????????????addBitmapToMemoryCache(imageUrl,?drawable);??
  • ????????????return?drawable;??
  • ????????}??
  • ??
  • ????????@Override??
  • ????????protected?void?onPostExecute(BitmapDrawable?drawable)?{??
  • ????????????ImageView?imageView?=?(ImageView)?mListView.findViewWithTag(imageUrl);????
  • ????????????if?(imageView?!=?null?&&?drawable?!=?null)?{????
  • ????????????????imageView.setImageDrawable(drawable);????
  • ????????????}???
  • ????????}??
  • ??
  • ????????......??
  • ??
  • ????}??
  • ??
  • }??
  • 改動的地方就只有這么多,那么我們來分析一下。由于使用findViewWithTag必須要有ListView的實例才行,那么我們在Adapter中怎樣才能拿到ListView的實例呢?其實如果你仔細通讀了上一篇文章就能知道,getView()方法中傳入的第三個參數其實就是ListView的實例,那么這里我們定義一個全局變量mListView,然后在getView()方法中判斷它是否為空,如果為空就把parent這個參數賦值給它。

    另外在getView()方法中我們還做了一個操作,就是調用了ImageView的setTag()方法,并把當前位置圖片的URL地址作為參數傳了進去,這個是為后續的findViewWithTag()方法做準備。

    最后,我們修改了BitmapWorkerTask的構造函數,這里不再通過構造函數把ImageView的實例傳進去了,而是在onPostExecute()方法當中通過ListView的findVIewWithTag()方法來去獲取ImageView控件的實例。獲取到控件實例后判斷下是否為空,如果不為空就讓圖片顯示到控件上。

    這里我們可以嘗試分析一下findViewWithTag的工作原理,其實顧名思義,這個方法就是通過Tag的名字來獲取具備該Tag名的控件,我們先要調用控件的setTag()方法來給控件設置一個Tag,然后再調用ListView的findViewWithTag()方法使用相同的Tag名來找回控件。

    那么為什么用了findViewWithTag()方法之后,圖片就不會再出現亂序情況了呢?其實原因很簡單,由于ListView中的ImageView控件都是重用的,移出屏幕的控件很快會被進入屏幕的圖片重新利用起來,那么getView()方法就會再次得到執行,而在getView()方法中會為這個ImageView控件設置新的Tag,這樣老的Tag就會被覆蓋掉,于是這時再調用findVIewWithTag()方法并傳入老的Tag,就只能得到null了,而我們判斷只有ImageView不等于null的時候才會設置圖片,這樣圖片亂序的問題也就不存在了。

    這是第一種解決方案。

    解決方案二 ?使用弱引用關聯

    雖然這里我給這種解決方案起名叫弱引用關聯,但實際上弱引用只是輔助手段而已,最主要的還是關聯,這種解決方案的本質是要讓ImageView和BitmapWorkerTask之間建立一個雙向關聯,互相持有對方的引用,再通過適當的邏輯判斷來解決圖片亂序問題,然后為了防止出現內存泄漏的情況,雙向關聯要使用弱引用的方式建立。相比于第一種解決方案,第二種解決方案要明顯復雜不少,但在性能和效率方面都會有更好的表現。

    我們仍然只需要改動ImageAdapter中的代碼,但這次改動的地方比較多,所以我就把ImageAdapter中的全部代碼都貼出來了,如下所示:

    [java] view plaincopy
  • /**??
  • ?*?原文地址:?http://blog.csdn.net/guolin_blog/article/details/45586553??
  • ?*?@author?guolin??
  • ?*/????
  • public?class?ImageAdapter?extends?ArrayAdapter<String>?{??
  • ??????
  • ????private?ListView?mListView;???
  • ??????
  • ????private?Bitmap?mLoadingBitmap;??
  • ??
  • ????/**?
  • ?????*?圖片緩存技術的核心類,用于緩存所有下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。?
  • ?????*/??
  • ????private?LruCache<String,?BitmapDrawable>?mMemoryCache;??
  • ??
  • ????public?ImageAdapter(Context?context,?int?resource,?String[]?objects)?{??
  • ????????super(context,?resource,?objects);??
  • ????????mLoadingBitmap?=?BitmapFactory.decodeResource(context.getResources(),??
  • ????????????????R.drawable.empty_photo);??
  • ????????//?獲取應用程序最大可用內存??
  • ????????int?maxMemory?=?(int)?Runtime.getRuntime().maxMemory();??
  • ????????int?cacheSize?=?maxMemory?/?8;??
  • ????????mMemoryCache?=?new?LruCache<String,?BitmapDrawable>(cacheSize)?{??
  • ????????????@Override??
  • ????????????protected?int?sizeOf(String?key,?BitmapDrawable?drawable)?{??
  • ????????????????return?drawable.getBitmap().getByteCount();??
  • ????????????}??
  • ????????};??
  • ????}??
  • ??
  • ????@Override??
  • ????public?View?getView(int?position,?View?convertView,?ViewGroup?parent)?{??
  • ????????if?(mListView?==?null)?{????
  • ????????????mListView?=?(ListView)?parent;????
  • ????????}???
  • ????????String?url?=?getItem(position);??
  • ????????View?view;??
  • ????????if?(convertView?==?null)?{??
  • ????????????view?=?LayoutInflater.from(getContext()).inflate(R.layout.image_item,?null);??
  • ????????}?else?{??
  • ????????????view?=?convertView;??
  • ????????}??
  • ????????ImageView?image?=?(ImageView)?view.findViewById(R.id.image);??
  • ????????BitmapDrawable?drawable?=?getBitmapFromMemoryCache(url);??
  • ????????if?(drawable?!=?null)?{??
  • ????????????image.setImageDrawable(drawable);??
  • ????????}?else?if?(cancelPotentialWork(url,?image))?{??
  • ????????????BitmapWorkerTask?task?=?new?BitmapWorkerTask(image);??
  • ????????????AsyncDrawable?asyncDrawable?=?new?AsyncDrawable(getContext()??
  • ????????????????????.getResources(),?mLoadingBitmap,?task);??
  • ????????????image.setImageDrawable(asyncDrawable);??
  • ????????????task.execute(url);??
  • ????????}??
  • ????????return?view;??
  • ????}??
  • ??????
  • ????/**?
  • ?????*?自定義的一個Drawable,讓這個Drawable持有BitmapWorkerTask的弱引用。?
  • ?????*/??
  • ????class?AsyncDrawable?extends?BitmapDrawable?{??
  • ??
  • ????????private?WeakReference<BitmapWorkerTask>?bitmapWorkerTaskReference;??
  • ??
  • ????????public?AsyncDrawable(Resources?res,?Bitmap?bitmap,??
  • ????????????????BitmapWorkerTask?bitmapWorkerTask)?{??
  • ????????????super(res,?bitmap);??
  • ????????????bitmapWorkerTaskReference?=?new?WeakReference<BitmapWorkerTask>(??
  • ????????????????????bitmapWorkerTask);??
  • ????????}??
  • ??
  • ????????public?BitmapWorkerTask?getBitmapWorkerTask()?{??
  • ????????????return?bitmapWorkerTaskReference.get();??
  • ????????}??
  • ??
  • ????}??
  • ??????
  • ????/**?
  • ?????*?獲取傳入的ImageView它所對應的BitmapWorkerTask。?
  • ?????*/??
  • ????private?BitmapWorkerTask?getBitmapWorkerTask(ImageView?imageView)?{??
  • ????????if?(imageView?!=?null)?{??
  • ????????????Drawable?drawable?=?imageView.getDrawable();??
  • ????????????if?(drawable?instanceof?AsyncDrawable)?{??
  • ????????????????AsyncDrawable?asyncDrawable?=?(AsyncDrawable)?drawable;??
  • ????????????????return?asyncDrawable.getBitmapWorkerTask();??
  • ????????????}??
  • ????????}??
  • ????????return?null;??
  • ????}??
  • ??????
  • ????/**?
  • ?????*?取消掉后臺的潛在任務,當認為當前ImageView存在著一個另外圖片請求任務時?
  • ?????*?,則把它取消掉并返回true,否則返回false。?
  • ?????*/??
  • ????public?boolean?cancelPotentialWork(String?url,?ImageView?imageView)?{??
  • ????????BitmapWorkerTask?bitmapWorkerTask?=?getBitmapWorkerTask(imageView);??
  • ????????if?(bitmapWorkerTask?!=?null)?{??
  • ????????????String?imageUrl?=?bitmapWorkerTask.imageUrl;??
  • ????????????if?(imageUrl?==?null?||?!imageUrl.equals(url))?{??
  • ????????????????bitmapWorkerTask.cancel(true);??
  • ????????????}?else?{??
  • ????????????????return?false;??
  • ????????????}??
  • ????????}??
  • ????????return?true;??
  • ????}??
  • ??
  • ????/**?
  • ?????*?將一張圖片存儲到LruCache中。?
  • ?????*??
  • ?????*?@param?key?
  • ?????*????????????LruCache的鍵,這里傳入圖片的URL地址。?
  • ?????*?@param?drawable?
  • ?????*????????????LruCache的值,這里傳入從網絡上下載的BitmapDrawable對象。?
  • ?????*/??
  • ????public?void?addBitmapToMemoryCache(String?key,?BitmapDrawable?drawable)?{??
  • ????????if?(getBitmapFromMemoryCache(key)?==?null)?{??
  • ????????????mMemoryCache.put(key,?drawable);??
  • ????????}??
  • ????}??
  • ??
  • ????/**?
  • ?????*?從LruCache中獲取一張圖片,如果不存在就返回null。?
  • ?????*??
  • ?????*?@param?key?
  • ?????*????????????LruCache的鍵,這里傳入圖片的URL地址。?
  • ?????*?@return?對應傳入鍵的BitmapDrawable對象,或者null。?
  • ?????*/??
  • ????public?BitmapDrawable?getBitmapFromMemoryCache(String?key)?{??
  • ????????return?mMemoryCache.get(key);??
  • ????}??
  • ??
  • ????/**?
  • ?????*?異步下載圖片的任務。?
  • ?????*??
  • ?????*?@author?guolin?
  • ?????*/??
  • ????class?BitmapWorkerTask?extends?AsyncTask<String,?Void,?BitmapDrawable>?{??
  • ??
  • ????????String?imageUrl;???
  • ??????????
  • ????????private?WeakReference<ImageView>?imageViewReference;??
  • ??????????
  • ????????public?BitmapWorkerTask(ImageView?imageView)?{????
  • ????????????imageViewReference?=?new?WeakReference<ImageView>(imageView);??
  • ????????}????
  • ??
  • ????????@Override??
  • ????????protected?BitmapDrawable?doInBackground(String...?params)?{??
  • ????????????imageUrl?=?params[0];??
  • ????????????//?在后臺開始下載圖片??
  • ????????????Bitmap?bitmap?=?downloadBitmap(imageUrl);??
  • ????????????BitmapDrawable?drawable?=?new?BitmapDrawable(getContext().getResources(),?bitmap);??
  • ????????????addBitmapToMemoryCache(imageUrl,?drawable);??
  • ????????????return?drawable;??
  • ????????}??
  • ??
  • ????????@Override??
  • ????????protected?void?onPostExecute(BitmapDrawable?drawable)?{??
  • ????????????ImageView?imageView?=?getAttachedImageView();??
  • ????????????if?(imageView?!=?null?&&?drawable?!=?null)?{????
  • ????????????????imageView.setImageDrawable(drawable);????
  • ????????????}???
  • ????????}??
  • ??????????
  • ????????/**?
  • ?????????*?獲取當前BitmapWorkerTask所關聯的ImageView。?
  • ?????????*/??
  • ????????private?ImageView?getAttachedImageView()?{??
  • ????????????ImageView?imageView?=?imageViewReference.get();??
  • ????????????BitmapWorkerTask?bitmapWorkerTask?=?getBitmapWorkerTask(imageView);??
  • ????????????if?(this?==?bitmapWorkerTask)?{??
  • ????????????????return?imageView;??
  • ????????????}??
  • ????????????return?null;??
  • ????????}??
  • ??
  • ????????/**?
  • ?????????*?建立HTTP請求,并獲取Bitmap對象。?
  • ?????????*??
  • ?????????*?@param?imageUrl?
  • ?????????*????????????圖片的URL地址?
  • ?????????*?@return?解析后的Bitmap對象?
  • ?????????*/??
  • ????????private?Bitmap?downloadBitmap(String?imageUrl)?{??
  • ????????????Bitmap?bitmap?=?null;??
  • ????????????HttpURLConnection?con?=?null;??
  • ????????????try?{??
  • ????????????????URL?url?=?new?URL(imageUrl);??
  • ????????????????con?=?(HttpURLConnection)?url.openConnection();??
  • ????????????????con.setConnectTimeout(5?*?1000);??
  • ????????????????con.setReadTimeout(10?*?1000);??
  • ????????????????bitmap?=?BitmapFactory.decodeStream(con.getInputStream());??
  • ????????????}?catch?(Exception?e)?{??
  • ????????????????e.printStackTrace();??
  • ????????????}?finally?{??
  • ????????????????if?(con?!=?null)?{??
  • ????????????????????con.disconnect();??
  • ????????????????}??
  • ????????????}??
  • ????????????return?bitmap;??
  • ????????}??
  • ??
  • ????}??
  • ??
  • }??
  • 那么我們一點點開始解析。首先剛才說到的,ImageView和BitmapWorkerTask之間要建立一個雙向的弱引用關聯,上述代碼中已經建立好了。ImageView中可以獲取到它所對應的BitmapWorkerTask,而BitmapWorkerTask也可以獲取到它所對應的ImageView。

    下面來看一下這個雙向弱引用關聯是怎么建立的。BitmapWorkerTask指向ImageView的弱引用關聯比較簡單,就是在BitmapWorkerTask中加入一個構造函數,并在構造函數中要求傳入ImageView這個參數。不過我們不再直接持有ImageView的引用,而是使用WeakReference對ImageView進行了一層包裝,這樣就OK了。

    但是ImageView指向BitmapWorkerTask的弱引用關聯就沒這么容易了,因為我們很難將BitmapWorkerTask的一個弱引用直接設置到ImageView當中。這該怎么辦呢?這里使用了一個比較巧的方法,就是借助自定義Drawable的方式來實現。可以看到,我們自定義了一個AsyncDrawable類并讓它繼承自BitmapDrawable,然后重寫了AsyncDrawable的構造函數,在構造函數中要求把BitmapWorkerTask傳入,然后在這里給它包裝了一層弱引用。那么現在AsyncDrawable指向BitmapWorkerTask的關聯已經有了,但是ImageView指向BitmapWorkerTask的關聯還不存在,怎么辦呢?很簡單,讓ImageView和AsyncDrawable再關聯一下就可以了。可以看到,在getView()方法當中,我們調用了ImageView的setImageDrawable()方法把AsyncDrawable設置了進去,那么ImageView就可以通過getDrawable()方法獲取到和它關聯的AsyncDrawable,然后再借助AsyncDrawable就可以獲取到BitmapWorkerTask了。這樣ImageView指向BitmapWorkerTask的弱引用關聯也成功建立。

    現在雙向弱引用的關聯已經建立好了,接下來就是邏輯判斷的工作了。那么怎樣通過邏輯判斷來避免圖片出現亂序的情況呢?這里我們引入了兩個方法,一個是getBitmapWorkerTask()方法,這個方法可以根據傳入的ImageView來獲取到它對應的BitmapWorkerTask,內部的邏輯就是先獲取ImageView對應的AsyncDrawable,再獲取AsyncDrawable對應的BitmapWorkerTask。另一個是getAttachedImageView()方法,這個方法會獲取當前BitmapWorkerTask所關聯的ImageView,然后調用getBitmapWorkerTask()方法來獲取該ImageView所對應的BitmapWorkerTask,最后判斷,如果獲取到的BitmapWorkerTask等于this,也就是當前的BitmapWorkerTask,那么就將ImageView返回,否則就返回null。最后,在onPostExecute()方法當中,只需要使用getAttachedImageView()方法獲取到的ImageView來顯示圖片就可以了。

    那么為什么做了這個邏輯判斷之后,圖片亂序的問題就可以得到解決呢?其實最主要的奧秘就是在getAttachedImageView()方法當中,它會使用當前BitmapWorkerTask所關聯的ImageView來反向獲取這個ImageView所關聯的BitmapWorkerTask,然后用這兩個BitmapWorkerTask做對比,如果發現是同一個BitmapWorkerTask才會返回ImageView,否則就返回null。那么什么情況下這兩個BitmapWorkerTask才會不同呢?比如說某個圖片被移出了屏幕,它的ImageView被另外一個新進入屏幕的圖片重用了,那么就會給這個ImageView關聯一個新的BitmapWorkerTask,這種情況下,上一個BitmapWorkerTask和新的BitmapWorkerTask肯定就不相等了,這時getAttachedImageView()方法會返回null,而我們又判斷ImageView等于null的話是不會設置圖片的,因此就不會出現圖片亂序的情況了。

    除此之外還有另外一個方法非常值得大家注意,就是cancelPotentialWork()方法,這個方法可以大大提高整個ListView圖片加載的工作效率。這個方法接收兩個參數,一個圖片的url,一個ImageView。看一下它的內部邏輯,首先它也是調用了getBitmapWorkerTask()方法來獲取傳入的ImageView所對應的BitmapWorkerTask,接下來拿BitmapWorkerTask中的imageUrl和傳入的url做比較,如果兩個url不等的話就調用BitmapWorkerTask的cancel()方法,然后返回true,如果兩個url相等的話就返回false。

    那么這段邏輯是什么意思呢?其實并不復雜,兩個url做比對時,如果發現是相同的,說明請求的是同一張圖片,那么直接返回false,這樣就不會再去啟動BitmapWorkerTask來請求圖片,而如果兩個url不相同,說明這個ImageView被另外一張圖片重新利用了,這個時候就調用了BitmapWorkerTask的cancel()方法把之前的請求取消掉,然后重新啟動BitmapWorkerTask來去請求新圖片。有了這個操作保護之后,就可以把一些已經移出屏幕的無效的圖片請求過濾掉,從而整體提升ListView加載圖片的工作效率。

    這是第二種解決方案。

    解決方案三 ?使用NetworkImageView

    前面兩種解決方案都需要我們自己去做額外的邏輯處理,因為ImageView本身是不能自動解決這個問題的,但是如果我們使用NetworkImageView這個控件的話就非常簡單了,它自身就已經考慮到了這個問題,我們直接使用它就可以了,不用做任何額外的處理也不會出現圖片亂序的情況。

    NetworkImageView是Volley當中提供的控件,對于這個控件我之前專門寫過一篇博客來講解,還不熟悉這個控件的朋友可以先去閱讀 Android Volley完全解析(二),使用Volley加載網絡圖片?。

    下面我們看一下如何用NetworkImageView來解決這個問題,首先需要修改一下image_item.xml文件,因為我們已經不再使用ImageView控件了,代碼如下所示:

    [html] view plaincopy
  • <?xml?version="1.0"?encoding="utf-8"?>??
  • <LinearLayout?xmlns:android="http://schemas.android.com/apk/res/android"??
  • ????android:layout_width="match_parent"??
  • ????android:layout_height="match_parent"?>??
  • ??
  • ????<com.android.volley.toolbox.NetworkImageView??
  • ????????android:id="@+id/image"??
  • ????????android:layout_width="match_parent"??
  • ????????android:layout_height="120dp"??
  • ????????android:src="@drawable/empty_photo"???
  • ????????android:scaleType="fitXY"/>??
  • ??
  • </LinearLayout>??
  • 很簡單,只是把ImageView替換成了NetworkImageView。然后修改ImageAdapter中的代碼,如下所示: [java] view plaincopy
  • /**?
  • ?*?原文地址:?http://blog.csdn.net/guolin_blog/article/details/45586553?
  • ?*?@author?guolin?
  • ?*/??
  • public?class?ImageAdapter?extends?ArrayAdapter<String>?{??
  • ??????
  • ????ImageLoader?mImageLoader;??
  • ??
  • ????public?ImageAdapter(Context?context,?int?resource,?String[]?objects)?{??
  • ????????super(context,?resource,?objects);??
  • ????????RequestQueue?queue?=?Volley.newRequestQueue(context);??
  • ????????mImageLoader?=?new?ImageLoader(queue,?new?BitmapCache());??
  • ????}??
  • ??
  • ????@Override??
  • ????public?View?getView(int?position,?View?convertView,?ViewGroup?parent)?{??
  • ????????String?url?=?getItem(position);??
  • ????????View?view;??
  • ????????if?(convertView?==?null)?{??
  • ????????????view?=?LayoutInflater.from(getContext()).inflate(R.layout.image_item,?null);??
  • ????????}?else?{??
  • ????????????view?=?convertView;??
  • ????????}??
  • ????????NetworkImageView?image?=?(NetworkImageView)?view.findViewById(R.id.image);??
  • ????????image.setDefaultImageResId(R.drawable.empty_photo);??
  • ????????image.setErrorImageResId(R.drawable.empty_photo);??
  • ????????image.setImageUrl(url,?mImageLoader);??
  • ????????return?view;??
  • ????}??
  • ??
  • ????/**?
  • ?????*?使用LruCache來緩存圖片?
  • ?????*/??
  • ????public?class?BitmapCache?implements?ImageCache?{??
  • ??
  • ????????private?LruCache<String,?Bitmap>?mCache;??
  • ??
  • ????????public?BitmapCache()?{??
  • ????????????//?獲取應用程序最大可用內存??
  • ????????????int?maxMemory?=?(int)?Runtime.getRuntime().maxMemory();??
  • ????????????int?cacheSize?=?maxMemory?/?8;??
  • ????????????mCache?=?new?LruCache<String,?Bitmap>(cacheSize)?{??
  • ????????????????@Override??
  • ????????????????protected?int?sizeOf(String?key,?Bitmap?bitmap)?{??
  • ????????????????????return?bitmap.getRowBytes()?*?bitmap.getHeight();??
  • ????????????????}??
  • ????????????};??
  • ????????}??
  • ??
  • ????????@Override??
  • ????????public?Bitmap?getBitmap(String?url)?{??
  • ????????????return?mCache.get(url);??
  • ????????}??
  • ??
  • ????????@Override??
  • ????????public?void?putBitmap(String?url,?Bitmap?bitmap)?{??
  • ????????????mCache.put(url,?bitmap);??
  • ????????}??
  • ??
  • ????}??
  • ??
  • }??
  • 沒錯,就是這么簡單,一共60行左右的代碼搞定一切!我們不需要自己再去寫一個BitmapWorkerTask來處理圖片的下載和顯示,也不需要自己再去管理LruCache的邏輯,一切NetworkImageView都幫我們做好了。至于上面的代碼我就不再做解釋了,因為實在是太簡單了。

    那么當然了,雖然現在沒有做任何額外的邏輯處理,但是也根本不會出現圖片亂序的情況,因為NetworkImageView在內部都幫我們處理掉了。不過大家可能都很好奇,NetworkImageView到底是如何做到的呢?那么就讓我們來分析一下它的源碼吧。

    NetworkImageView中開始加載圖片的代碼是setImageUrl()方法,源碼分析就從這里開始吧,如下所示:

    [java] view plaincopy
  • /**?
  • ?*?Sets?URL?of?the?image?that?should?be?loaded?into?this?view.?Note?that?calling?this?will?
  • ?*?immediately?either?set?the?cached?image?(if?available)?or?the?default?image?specified?by?
  • ?*?{@link?NetworkImageView#setDefaultImageResId(int)}?on?the?view.?
  • ?*?
  • ?*?NOTE:?If?applicable,?{@link?NetworkImageView#setDefaultImageResId(int)}?and?
  • ?*?{@link?NetworkImageView#setErrorImageResId(int)}?should?be?called?prior?to?calling?
  • ?*?this?function.?
  • ?*?
  • ?*?@param?url?The?URL?that?should?be?loaded?into?this?ImageView.?
  • ?*?@param?imageLoader?ImageLoader?that?will?be?used?to?make?the?request.?
  • ?*/??
  • public?void?setImageUrl(String?url,?ImageLoader?imageLoader)?{??
  • ????mUrl?=?url;??
  • ????mImageLoader?=?imageLoader;??
  • ????//?The?URL?has?potentially?changed.?See?if?we?need?to?load?it.??
  • ????loadImageIfNecessary(false);??
  • }??
  • setImageUrl()方法中并沒有幾行代碼,讓人值得留意的是loadImageIfNecessary()這個方法,看上去具體加載圖片的邏輯就是在這里進行的,那么我們就跟進去瞧一瞧: [java] view plaincopy
  • /**?
  • ?*?Loads?the?image?for?the?view?if?it?isn't?already?loaded.?
  • ?*?@param?isInLayoutPass?True?if?this?was?invoked?from?a?layout?pass,?false?otherwise.?
  • ?*/??
  • private?void?loadImageIfNecessary(final?boolean?isInLayoutPass)?{??
  • ????int?width?=?getWidth();??
  • ????int?height?=?getHeight();??
  • ??
  • ????boolean?isFullyWrapContent?=?getLayoutParams()?!=?null??
  • ????????????&&?getLayoutParams().height?==?LayoutParams.WRAP_CONTENT??
  • ????????????&&?getLayoutParams().width?==?LayoutParams.WRAP_CONTENT;??
  • ????//?if?the?view's?bounds?aren't?known?yet,?and?this?is?not?a?wrap-content/wrap-content??
  • ????//?view,?hold?off?on?loading?the?image.??
  • ????if?(width?==?0?&&?height?==?0?&&?!isFullyWrapContent)?{??
  • ????????return;??
  • ????}??
  • ??
  • ????//?if?the?URL?to?be?loaded?in?this?view?is?empty,?cancel?any?old?requests?and?clear?the??
  • ????//?currently?loaded?image.??
  • ????if?(TextUtils.isEmpty(mUrl))?{??
  • ????????if?(mImageContainer?!=?null)?{??
  • ????????????mImageContainer.cancelRequest();??
  • ????????????mImageContainer?=?null;??
  • ????????}??
  • ????????setDefaultImageOrNull();??
  • ????????return;??
  • ????}??
  • ??
  • ????//?if?there?was?an?old?request?in?this?view,?check?if?it?needs?to?be?canceled.??
  • ????if?(mImageContainer?!=?null?&&?mImageContainer.getRequestUrl()?!=?null)?{??
  • ????????if?(mImageContainer.getRequestUrl().equals(mUrl))?{??
  • ????????????//?if?the?request?is?from?the?same?URL,?return.??
  • ????????????return;??
  • ????????}?else?{??
  • ????????????//?if?there?is?a?pre-existing?request,?cancel?it?if?it's?fetching?a?different?URL.??
  • ????????????mImageContainer.cancelRequest();??
  • ????????????setDefaultImageOrNull();??
  • ????????}??
  • ????}??
  • ??
  • ????//?The?pre-existing?content?of?this?view?didn't?match?the?current?URL.?Load?the?new?image??
  • ????//?from?the?network.??
  • ????ImageContainer?newContainer?=?mImageLoader.get(mUrl,??
  • ????????????new?ImageListener()?{??
  • ????????????????@Override??
  • ????????????????public?void?onErrorResponse(VolleyError?error)?{??
  • ????????????????????if?(mErrorImageId?!=?0)?{??
  • ????????????????????????setImageResource(mErrorImageId);??
  • ????????????????????}??
  • ????????????????}??
  • ??
  • ????????????????@Override??
  • ????????????????public?void?onResponse(final?ImageContainer?response,?boolean?isImmediate)?{??
  • ????????????????????//?If?this?was?an?immediate?response?that?was?delivered?inside?of?a?layout??
  • ????????????????????//?pass?do?not?set?the?image?immediately?as?it?will?trigger?a?requestLayout??
  • ????????????????????//?inside?of?a?layout.?Instead,?defer?setting?the?image?by?posting?back?to??
  • ????????????????????//?the?main?thread.??
  • ????????????????????if?(isImmediate?&&?isInLayoutPass)?{??
  • ????????????????????????post(new?Runnable()?{??
  • ????????????????????????????@Override??
  • ????????????????????????????public?void?run()?{??
  • ????????????????????????????????onResponse(response,?false);??
  • ????????????????????????????}??
  • ????????????????????????});??
  • ????????????????????????return;??
  • ????????????????????}??
  • ??
  • ????????????????????if?(response.getBitmap()?!=?null)?{??
  • ????????????????????????setImageBitmap(response.getBitmap());??
  • ????????????????????}?else?if?(mDefaultImageId?!=?0)?{??
  • ????????????????????????setImageResource(mDefaultImageId);??
  • ????????????????????}??
  • ????????????????}??
  • ????????????});??
  • ??
  • ????//?update?the?ImageContainer?to?be?the?new?bitmap?container.??
  • ????mImageContainer?=?newContainer;??
  • }??
  • 這里在第43行調用了ImageLoader的get()方法來去請求圖片,get()方法會返回一個ImageContainer對象,這個對象封裝了圖片請求地址、Bitmap等數據,每個NetworkImageView中都會對應一個ImageContainer。然后在第31行我們看到,這里從ImageContainer對象中獲取封裝的圖片請求地址,并拿來和當前的請求地址做對比,如果相同的話說明這是一條重復的請求,就直接return掉,如果不同的話就調用cancelRequest()方法將請求取消掉,然后將圖片設置為默認圖片并重新發起請求。

    那么解決圖片亂序最核心的邏輯就在這里了,其實NetworkImageView的解決思路還是比較簡單的,就是如果這個控件已經被移出了屏幕且被重新利用了,那么就把之前的請求取消掉,僅此而已。

    而我們都知道,在通常情況下,僅僅這么處理可能是解決不了問題的,因為Java的線程無法保證一定可以中斷,即使像第二種解決方案里使用的BitmapWorkerTask的cancel()方法,也不能保證一定可以把請求取消掉,所以還需要使用弱引用關聯的處理方式。但是在NetworkImageView當中就可以這么任性,僅僅調用cancelRequest()方法把請求取消掉就可以了,這主要是得益于Volley的出色設計。由于Volley在網絡方面的封裝非常優秀,它可以保證,只要是取消掉的請求,就絕對不會進行回調,既然不會回調,那么也就不會回到NetworkImageView當中,自然也就不會出現亂序的情況了。

    需要注意的是,Volley只是保證取消掉的請求不會進行回調而已,但并沒有說可以中斷任何請求。由此可見即使是Volley也無法做到中斷一個正在執行的線程,如果有一個線程正在執行,Volley只會保證在它執行完之后不會進行回調,但在調用者看來,就好像是這個請求就被取消掉了一樣。

    那么這里我們只分析與圖片亂序相關部分的源碼,如果你想了解關于Volley更多的源碼,可以參考我之前的一篇文章 Android Volley完全解析(四),帶你從源碼的角度理解Volley?。

    這是第三種解決方案。

    好了,關于ListView異步加載圖片亂序的問題今天我們就討論到這里,如果你把三種解決方案都理解清楚的話,那么對于這個問題研究的就算比較透徹了。下一篇文章仍然是ListView主題,我們將學習一下如何對ListView控件進行一些功能擴展,感興趣的朋友請繼續閱讀 Android ListView功能擴展,實現高性能的瀑布流布局?。

    第一時間獲得博客更新提醒,以及更多技術信息分享,歡迎關注我的微信公眾號,掃一掃下方二維碼或搜索微信號guolin_blog,即可關注。

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的Android ListView异步加载图片乱序问题,原因分析及解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 日韩精品在线免费观看 | 日韩精品一区二区在线观看 | 玖玖玖精品 | 国产一区二区三区视频免费在线观看 | 午夜欧美视频 | 欧美三级中文字幕 | 最近国语视频在线观看免费播放 | 国产成人精品亚洲日本在线观看 | 网站免费在线观看 | 亚洲欧美日韩另类在线 | 亚洲天堂男 | 国产男女猛烈无遮挡免费视频 | 成人午夜又粗又硬又大 | 好吊操免费视频 | 久久无码视频网站 | 国产女人在线 | 国产精品一区二区视频 | 欧美精品 在线观看 | 亚洲自啪| 超碰666 | 国产精品999 | 521a人成v香蕉网站 | 亚洲av无码久久精品色欲 | 韩国三级国产 | 国产成a人亚洲精v品无码 | 蜜桃一二三区 | 天天爽夜夜爽人人爽 | 亚洲精品中文字幕乱码无线 | 一区二区三区四区免费视频 | 视频免费1区二区三区 | 干欧美少妇 | 99热黄色| 狠狠干2019| 成人午夜淫片免费观看 | 久久99九九 | 日日干夜夜草 | 亚洲色图视频在线观看 | 国产成人无码精品久久二区三 | 久久高潮视频 | 一区二区在线 | 青草青草久热 | 午夜视频在线播放 | 深夜福利国产 | 国产男男gay体育生白袜 | 91免费精品视频 | 中文字幕在线播放不卡 | 国产精品久久国产 | 免费成人小视频 | 国产欧美视频一区二区三区 | 91桃色视频在线观看 | 欧美在线视频精品 | 九九九热| 少妇大叫太粗太大爽一区二区 | 色爱av综合| 精品九九 | 性生交大片免费看视频 | 亚洲丝袜一区 | 亚洲人无码成www久久 | 毛利兰被扒开腿做同人漫画 | 成人免费毛片嘿嘿连载视频 | 国产原创剧情av | 91麻豆精品国产 | h片在线免费 | 国产精品国产精品国产专区不片 | 精品一区二区三区免费毛片 | 国产粉嫩白浆 | 欧美精品二区三区 | 国产一级一级 | 污片网站在线观看 | 欧美三级在线播放 | 性高潮久久久久久 | 日本免费一区二区三区四区五六区 | 久久99精品久久久久久琪琪 | 污视频在线 | 骚虎av| 日本伦理片在线播放 | 亚洲免费中文 | 影音先锋 日韩 | 亚洲石原莉奈一区二区在线观看 | 国产传媒中文字幕 | 人妻精品一区二区三区 | 色综合av | 亚洲怡红院av| 中文字幕天堂在线 | 国产剧情av麻豆香蕉精品 | 51成人网| 欧美日韩免费看 | 在线播放国产精品 | 亚洲1234区| 成人亚洲精品 | 无码人妻精品一区二区三区不卡 | 国产特黄aaaaa毛片 | 国产精品178页| 狠狠操影视| 日日干天天射 | 高h文在线| 秋葵视频污 | 小敏的受孕日记h | heyzo北岛玲在线播放 |