我們在開發網路應用的時候,時常會涉及到圖片下載的情況,圖片下載是一個耗時的過程。由於非同步下載的體驗好,因此非同步載入網狀圖片成了我們首選的方式。
之前翻閱了網上的一些資料,發現已經有人分享了這方面的經驗,小弟在學習之餘,在他們的基礎上,也做了一些最佳化。下面我們就來看看這非同步載入的實現過程吧。
非同步載入說白了就是開後台線程來下載圖片,等待下載完成後就在UI上顯示出來。那麼我們要為每個圖片都開一個線程嗎?如果要載入一百張圖片呢?如果這樣做就顯得太浪費,這時我們就想到了線程池了!線程池可以通過反覆調度現有的線程來最大化的利用資源,我們可以通過Executors.newFixedThreadPool();來擷取這樣的線程池,然後用它來管理下載線程。
當然,我們很幸運,Android為我們提供了更加簡單的方式!
我們知道Android為我們提供了一個非常好用的非同步任務——AsyncTask,通過查看AsyncTask的源碼,我們看到了下面這行代碼:
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
其中,CORE_POOL_SIZE就限定了啟動並執行線程的數量。
這說明了AsyncTask內部的實現就是通過線程池來進行調度的,利用它的這一個特點,再結合上面的思路,就可以用AsyncTask來為我們實現非同步載入的功能了。那麼,具體實現請看代碼,為了方便擴充和使用,我將非同步下載的功能封裝到了自訂的組件RemoteImageView中,代碼如下:
public class RemoteImageView extends ImageView{ public static HashMap<String,Bitmap> imageCache = new HashMap<String, Bitmap>();//1.作為緩衝,有其他更好的實現方式 private static final int MAX_FAIL_TIME = 5; private int mFails = 0; private String mUrl; public RemoteImageView(Context context, AttributeSet attrs) { super(context, attrs); } public void setDefaultImage(int resId){ this.setImageResource(resId); } public void setImageUrl(String url){ if(mUrl != null && mUrl.equals(url)){ mFails++; }else{ mFails = 0; mUrl = url; } if(mFails >= MAX_FAIL_TIME) return; mUrl = url; if(isCached(url)) return; startDownload(url); } public boolean isCached(String url){ if(imageCache.containsKey(url)){ this.setImageBitmap(imageCache.get(url)); return true; } return false; } private void startDownload(String url){ try{ new DownloadTask().execute(url); }catch (RejectedExecutionException e) { //2.捕獲RejectedExecutionException同時載入的圖片過多而導致程式崩潰 } } private void reDownload(String url){ setImageUrl(url); } class DownloadTask extends AsyncTask<String, Void, String>{ private String imageUrl; @Override protected String doInBackground(String... params) { imageUrl = params[0]; InputStream is = null; Bitmap bmp = null; try { URL url = new URL(imageUrl); is = url.openStream(); bmp = BitmapFactory.decodeStream(is); if(bmp != null){ imageCache.put(imageUrl, bmp); }else{ reDownload(imageUrl); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(is != null){ try { is.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return imageUrl; } @Override protected void onPostExecute(String result) { Bitmap bmp = null; if(imageCache.containsKey(result)){ bmp = imageCache.get(result); RemoteImageView.this.setImageBitmap(bmp); }else{ reDownload(imageUrl); } super.onPostExecute(result); } }}
這裡我主要對注釋的兩處作解釋:
1、由於圖片資源是比較消耗流量的,所以下載到本地後我們需要對其進行緩衝,緩衝的方式有多種,其中比較收歡迎的有:(1)、通過SoftReference進行臨時儲存,由於SoftReference會針對記憶體進行最佳化,所以處於記憶體最佳化,這是一種很好的方式。(2)、通過檔案進行儲存的外部儲存空間。
2、由於線程池對線程的個數有限制,當載入圖片數量過多時,會拋出RejectedExecutionException
這樣我們就能使用RemoteImageView實現圖片的非同步載入了,如有什麼錯誤,歡迎指正~~~~
下面是一個demo工程的源碼:http://download.csdn.net/download/chenshaoyang0011/4428075