UniversalImageLoader
簡單來說就是用於載入圖片的一個開源專案,在其專案介紹中是這麼寫的
- 支援多執行緒圖片載入
- 提供豐富的細節配置,比如執行緒池大小,HTPP請求項,記憶體和磁碟快取,圖片顯示時的引數配置等等;
- 提供雙快取
- 支援載入過程的監聽;
- 提供圖片的個性化顯示配置介面;
其他類似的專案也有很多,但這個作為github上著名的開源專案被廣泛使用。第三方的包雖然好用省力,可以有效避免重複造輪子,但是卻隱藏了一些開發上的細節,如果不關注其內部實現,那麼將不利於掌握核心技術,當然也談不上更好的使用它,計劃分析專案的整合使用和低層實現。
原始碼地址:https://github.com/nostra13/Android-Universal-Image-Loader
簡單分析
- 載入圖片之前,先要做初始化配置,這個類似很多遊戲引擎使用前要做一下初始化;
- 其實只做了一件事,例項化一個全域性的ImageLoader物件,同時傳入圖片載入快取的配置;
- ImageLoaderConfiguration封裝了基本的配置資訊,比如載入圖片事用的執行緒池大小,執行緒的優先順序,記憶體快取大小,是否支援同一圖片的多尺寸快取(預設是支援的,可以手動關閉),還有快取的命名規則等等.
這基本也就是幾行程式碼,下面這張圖裡有例項化和初始化的過程。
關於這個例項化,是執行緒安全,忽略第二層判斷,如果A,B執行緒同時執行if(instance==null),A,B都滿足條件進入,此時,其中一個換鎖,另一個等待,還需要再次判斷instance==null(這是必要的,否則可能使得,再次例項化)這樣一個單例就正常初始化了。
配置完後,就可以開始使用了,通過ImageLoader的displayImage()繫結一個圖片和ImageView,該方法有四個過載版本,傳的引數比較多,這也印證了該專案提供每個圖片單獨的顯示配置這一說法。
其中引數最全的是:
displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener);
另外三個其實就是減少其中幾個引數用預設的值而已。
- String uri, 圖片連結沒什麼疑問
- ImageView imageView, 圖片載體控制元件,也沒什麼好說
比較重要的是後面兩個,DisplayImageOptions options,圖片的引數配置物件
options = new DisplayImageOptions.Builder() .showStubImage(R.drawable.stub_image) .showImageForEmptyUri(R.drawable.image_for_empty_url) .cacheInMemory() .cacheOnDisc() .build();
來看看都有什麼資訊可以配置的,
- 第一個是圖片載入過程中顯示的圖片
- 第二個是圖片載入失敗時用的的圖片
- 第三個允許記憶體快取
- 第四個允許磁碟快取
除此之外還有兩個
- imageScaleType(ImageScaleType imageScaleType)圖片縮放型別
- displayer(BitmapDisplayer displayer)bitmap顯示控制層,可以在顯示圖片前對Bitmap簡單處理一下,這兩個不是一定要設定,應為他們都有預設值。
最後一個引數ImageLoadingListener listener當然是監聽過程的回撥介面
Imageloader
圖片載入的Imageloader實現
通過ImageLoader例項物件,呼叫public void displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener)發放將開始載入圖片
具體過程可以分為幾個階段
合法性檢查
主要是初始化檢查和引數檢查,可能會
- 丟擲異常或是下載不受干擾可以繼續;有傳入的圖片地址為空
- imageview為空
- 圖片配置例項為空
- 過程監聽介面為空四種情況。
if (configuration == null) { throw new RuntimeException(ERROR_NOT_INIT); } if (imageView == null) { Log.w(TAG, ERROR_WRONG_ARGUMENTS); return; } if (listener == null) { listener = emptyListener; } if (options == null) { options = configuration.defaultDisplayImageOptions; } if (uri == null || uri.length() == 0) { cacheKeyForImageView.remove(imageView); listener.onLoadingStarted(); if (options.isShowImageForEmptyUri()) { imageView.setImageResource(options.getImageForEmptyUri()); } else { imageView.setImageBitmap(null); } listener.onLoadingComplete(null); return; }
- 如果沒有初始化ImageLoader是比較嚴重的,將直接丟擲執行時異常
- 控制元件ImageView為空時不影響,可以直接退出,不再下載
- 圖片配置和監聽介面為空則將啟用預設值
- 至於圖片url的話,由於初始化時已經例項化了預設值的情形,所以將顯示本地設定的預設圖片,同時將控制元件移出HashMap。
載入準備
- 這裡的準備操作一個是獲取圖片的尺寸引數
- 然後根據這個引數和url生成標記ImageView的key
- 最後以key-value的形式存入HashMap中備用
targetSize = getImageSizeScaleTo(imageView); String memoryCacheKey = MemoryCacheKeyUtil.generateKey(uri, targetSize); cacheKeyForImageView.put(imageView, memoryCacheKey);
載入操作
載入分為呼叫記憶體快取和本地快取/網路下載,根據上一步載入準備中得到的key獲取bitmap,這個過程比較發雜
先看看從記憶體快取獲取圖片
if (bmp != null && !bmp.isRecycled()) { if (configuration.loggingEnabled) Log.i(TAG, String.format(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey)); listener.onLoadingStarted(); Bitmap displayedBitmap = options.getDisplayer().display(bmp, imageView); imageView.setImageBitmap(displayedBitmap); listener.onLoadingComplete(bmp); }
- 根據得到的bitmap,如果不為空且未被標記為回收狀態,那麼就可以使用這個快取的bitmap
- 呼叫監聽介面的onLoadingStarted()處理一些載入前的操作,然後對bitmap做一些顯示前的操作
- 這個就用到傳入進來的圖片顯示的option配置,如果沒傳這個值,那啟用預設值,應該是不對bitmap操作
- 這個預設的option使用SimpleBitmapDisplayer 例項
- 接下來呼叫監聽介面的onLoadingComlete()
- 到此顯示記憶體快取圖片的操作就結束了
檢視其原始碼,果然是直接將bitmap設定給ImageView然後對外返回原來的Bitmap,對於這種情況後面的在此設定bitmap給imageview其實有些累贅,重複操作了。
public final class SimpleBitmapDisplayer implements BitmapDisplayer { @Override public Bitmap display(Bitmap bitmap, ImageView imageView) { imageView.setImageBitmap(bitmap); return bitmap; } }
下面介紹第二種情況,就是從磁碟快取/網新下載圖片。
- 先呼叫監聽介面的onLoadingStarted()
- 接著顯示一個下載過程中的圖片或則乾脆在下載時不顯示任何圖片
- 接著檢查一下執行緒池是否初始化並正常工作中checkExecutors()
檢視原始碼可以發現,專案用與下載的的task其實是通過ExecutorService來管理
private void checkExecutors() { if (imageLoadingExecutor == null || imageLoadingExecutor.isShutdown()) { imageLoadingExecutor = Executors.newFixedThreadPool(configuration.threadPoolSize, configuration.displayImageThreadFactory); } if (cachedImageLoadingExecutor == null || cachedImageLoadingExecutor.isShutdown()) { cachedImageLoadingExecutor = Executors.newFixedThreadPool(configuration.threadPoolSize, configuration.displayImageThreadFactory); } }
- 為了下載圖片這裡把必要的圖片資訊做了一個封裝傳給工作執行緒
- ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, options, listener, getLockForUri(uri));
- 現在一起看一下他的工作執行緒是怎麼寫的:LoadAndDisplayImageTask displayImageTask = new LoadAndDisplayImageTask(configuration, imageLoadingInfo, new Handler());
- 這裡的最後一個引數是我們熟悉的Handler 例項,可以預測這個task類應該是Runnale的是子類,在run() 中根據情況向handler傳送處理操作請求
原始碼詳細註釋
http://blog.csdn.net/wwj_748/article/details/10079311
參考文章
https://github.com/nostra13/Android-Universal-Image-Loader
http://blog.csdn.net/wwj_748/article/details/10079311
http://www.cnblogs.com/avenwu/