【Android】開源專案UniversalImageLoader及開源框架ImageLoader

Leo.cheng發表於2014-03-12

UniversalImageLoader

  簡單來說就是用於載入圖片的一個開源專案,在其專案介紹中是這麼寫的

  1. 支援多執行緒圖片載入
  2. 提供豐富的細節配置,比如執行緒池大小,HTPP請求項,記憶體和磁碟快取,圖片顯示時的引數配置等等;
  3. 提供雙快取
  4. 支援載入過程的監聽;
  5. 提供圖片的個性化顯示配置介面;

  其他類似的專案也有很多,但這個作為github上著名的開源專案被廣泛使用。第三方的包雖然好用省力,可以有效避免重複造輪子,但是卻隱藏了一些開發上的細節,如果不關注其內部實現,那麼將不利於掌握核心技術,當然也談不上更好的使用它,計劃分析專案的整合使用和低層實現。

  原始碼地址:https://github.com/nostra13/Android-Universal-Image-Loader

簡單分析

  1. 載入圖片之前,先要做初始化配置,這個類似很多遊戲引擎使用前要做一下初始化;
  2. 其實只做了一件事,例項化一個全域性的ImageLoader物件,同時傳入圖片載入快取的配置
  3. ImageLoaderConfiguration封裝了基本的配置資訊,比如載入圖片事用的執行緒池大小,執行緒的優先順序,記憶體快取大小,是否支援同一圖片的多尺寸快取(預設是支援的,可以手動關閉),還有快取的命名規則等等.

  這基本也就是幾行程式碼,下面這張圖裡有例項化和初始化的過程。

  UniversalImageLoader

     關於這個例項化,是執行緒安全,忽略第二層判斷,如果A,B執行緒同時執行if(instance==null),A,B都滿足條件進入,此時,其中一個換鎖,另一個等待,還需要再次判斷instance==null(這是必要的,否則可能使得,再次例項化)這樣一個單例就正常初始化了。

  配置完後,就可以開始使用了,通過ImageLoaderdisplayImage()繫結一個圖片和ImageView,該方法有四個過載版本,傳的引數比較多,這也印證了該專案提供每個圖片單獨的顯示配置這一說法。

  其中引數最全的是:

displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener);

  另外三個其實就是減少其中幾個引數用預設的值而已。

  1. String uri, 圖片連結沒什麼疑問
  2. ImageView imageView, 圖片載體控制元件,也沒什麼好說

  比較重要的是後面兩個,DisplayImageOptions options,圖片的引數配置物件

options = new DisplayImageOptions.Builder() 
.showStubImage(R.drawable.stub_image) 
.showImageForEmptyUri(R.drawable.image_for_empty_url) 
.cacheInMemory() 
.cacheOnDisc() 
.build();

  來看看都有什麼資訊可以配置的,

  1. 第一個是圖片載入過程中顯示的圖片
  2. 第二個是圖片載入失敗時用的的圖片
  3. 第三個允許記憶體快取
  4. 第四個允許磁碟快取

  除此之外還有兩個

  1. imageScaleType(ImageScaleType imageScaleType)圖片縮放型別
  2. displayer(BitmapDisplayer displayer)bitmap顯示控制層,可以在顯示圖片前對Bitmap簡單處理一下,這兩個不是一定要設定,應為他們都有預設值。

  最後一個引數ImageLoadingListener listener當然是監聽過程的回撥介面


Imageloader

  圖片載入的Imageloader實現

  

  通過ImageLoader例項物件,呼叫public void displayImage(String uri, ImageView imageView, DisplayImageOptions options, ImageLoadingListener listener)發放將開始載入圖片

  具體過程可以分為幾個階段

合法性檢查

  主要是初始化檢查和引數檢查,可能會

  1. 丟擲異常或是下載不受干擾可以繼續;有傳入的圖片地址為空
  2. imageview為空
  3. 圖片配置例項為空
  4. 過程監聽介面為空四種情況。
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; 
} 
View Code
  1. 如果沒有初始化ImageLoader是比較嚴重的,將直接丟擲執行時異常
  2. 控制元件ImageView為空時不影響,可以直接退出,不再下載
  3. 圖片配置和監聽介面為空則將啟用預設值
  4. 至於圖片url的話,由於初始化時已經例項化了預設值的情形,所以將顯示本地設定的預設圖片,同時將控制元件移出HashMap。

載入準備

  1. 這裡的準備操作一個是獲取圖片的尺寸引數
  2. 然後根據這個引數和url生成標記ImageView的key
  3. 最後以key-value的形式存入HashMap中備用
targetSize = getImageSizeScaleTo(imageView); 
String memoryCacheKey = MemoryCacheKeyUtil.generateKey(uri, targetSize); 
cacheKeyForImageView.put(imageView, memoryCacheKey); 
View Code

載入操作

  載入分為呼叫記憶體快取本地快取/網路下載,根據上一步載入準備中得到的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); 
}
View Code
  1. 根據得到的bitmap,如果不為空且未被標記為回收狀態,那麼就可以使用這個快取的bitmap
  2. 呼叫監聽介面的onLoadingStarted()處理一些載入前的操作,然後對bitmap做一些顯示前的操作
  3. 這個就用到傳入進來的圖片顯示的option配置,如果沒傳這個值,那啟用預設值,應該是不對bitmap操作
  4. 這個預設的option使用SimpleBitmapDisplayer 例項
  5. 接下來呼叫監聽介面的onLoadingComlete()
  6. 到此顯示記憶體快取圖片的操作就結束了

  檢視其原始碼,果然是直接將bitmap設定給ImageView然後對外返回原來的Bitmap,對於這種情況後面的在此設定bitmap給imageview其實有些累贅,重複操作了。

public final class SimpleBitmapDisplayer implements BitmapDisplayer 
{ 
    @Override 
    public Bitmap display(Bitmap bitmap, ImageView imageView) 
    { 
        imageView.setImageBitmap(bitmap); 
        return bitmap; 
    } 
}
View Code

  下面介紹第二種情況,就是從磁碟快取/網新下載圖片

  1. 先呼叫監聽介面的onLoadingStarted()
  2. 接著顯示一個下載過程中的圖片或則乾脆在下載時不顯示任何圖片
  3. 接著檢查一下執行緒池是否初始化並正常工作中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); 
    } 
} 
View Code
  1. 為了下載圖片這裡把必要的圖片資訊做了一個封裝傳給工作執行緒
  2. ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, options, listener, getLockForUri(uri));
  3. 現在一起看一下他的工作執行緒是怎麼寫的:LoadAndDisplayImageTask displayImageTask = new LoadAndDisplayImageTask(configuration, imageLoadingInfo, new Handler());
  4. 這裡的最後一個引數是我們熟悉的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/

相關文章