標籤:
從UNIVERSAL IMAGE LOADER. PART 3(四個DisplayImage重載方法詳解)中,我們學習了Android-Universal-Image-Loader(以下簡稱UIL)中四個DisplayImage重載方法的使用,如果你還沒有學習,最好先返回去看看,不然可能不理解這篇文章。在這篇文章中我們將主要探討Android-Universal-Image-Loader的主要流程和這些流程相關的類的分析。
我們先瞭解一下UIL載入圖片的流程(可以通過查看ImageLoader.displayImage(…)方法分析得出),如
從中,我們可以看出,UIL載入圖片的一般流程是先判斷記憶體中是否有對應的Bitmap,再判斷磁碟(disk)中是否有,如果沒有就從網路中載入。最後根據原先在UIL中的配置判斷是否需要緩衝Bitmap到記憶體或磁碟中。Bitmap載入完後,就對它進行解析,然後顯示到特定的ImageView中。
有了對UIL對圖片載入和處理流程的初步認識之後,我們就可以著手分析它的原始碼了。先從ImageLoader.displayImage(...)入手,畢竟一切都因它而始。
1 public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, 2 ImageLoadingListener listener, ImageLoadingProgressListener progressListener) { 3 //檢查UIL的配置是否被初始化 4 checkConfiguration(); 5 if (imageAware == null) { 6 throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS); 7 } 8 if (listener == null) { 9 listener = emptyListener;10 }11 if (options == null) {12 options = configuration.defaultDisplayImageOptions;13 }14 15 if (TextUtils.isEmpty(uri)) {16 engine.cancelDisplayTaskFor(imageAware);17 listener.onLoadingStarted(uri, imageAware.getWrappedView());18 if (options.shouldShowImageForEmptyUri()) {19 imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));20 } else {21 imageAware.setImageDrawable(null);22 }23 listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);24 return;25 }26 //計算Bitmap的大小,以便後面解析圖片時用27 ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());28 String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);29 engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);30 31 listener.onLoadingStarted(uri, imageAware.getWrappedView());32 //Bitmap是否緩衝在記憶體?33 Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);34 if (bmp != null && !bmp.isRecycled()) {35 L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);36 37 if (options.shouldPostProcess()) {38 ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,39 options, listener, progressListener, engine.getLockForUri(uri));40 //處理並顯示圖片41 ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,42 defineHandler(options));43 if (options.isSyncLoading()) {44 displayTask.run();45 } else {46 engine.submit(displayTask);47 }48 } else {49 //顯示圖片50 options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);51 listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);52 }53 } else {54 if (options.shouldShowImageOnLoading()) {55 imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));56 } else if (options.isResetViewBeforeLoading()) {57 imageAware.setImageDrawable(null);58 }59 60 ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,61 options, listener, progressListener, engine.getLockForUri(uri));62 //啟動一個線程,載入並顯示圖片63 LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,64 defineHandler(options));65 if (options.isSyncLoading()) {66 displayTask.run();67 } else {68 engine.submit(displayTask);69 }70 }71 }
代碼有點多,但是有很多代碼是進行異常判斷處理和函數的回調,為了先把握整體的流程,我們先放棄細節方面的追蹤。基本上重要的處理流程我都有用注釋標出。不過,從這段代碼中我們也可以看出這段代碼的結構非常清晰。對圖片的整個的載入流程都有對應的監聽介面(ImageLoadingListener.onLoadingStarted,ImageLoadingListener.onLoadingComplete,ImageLoadingListener這個類就是用來監聽圖片的載入過程的),也就是說整個的圖片載入過程程式員都可以進行相應的處理。我們先關注一片從無到有的載入過程,畢竟這部分是大家最為關心的。看到第63行中的LoadAndDisplayImageTask,跟進LoadAndDisplayImageTask.run()方法中。在這個run()方法中,除了 bmp = tryLoadBitmap();這一句是對圖片進行載入,其他的函數都是對Bitmap進行處理或者顯示。我們繼續進入看看。
1 private Bitmap tryLoadBitmap() throws TaskCancelledException { 2 Bitmap bitmap = null; 3 try { 4 //嘗試從磁碟緩衝中讀取Bitmap 5 File imageFile = configuration.diskCache.get(uri); 6 if (imageFile != null && imageFile.exists()) { 7 L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey); 8 loadedFrom = LoadedFrom.DISC_CACHE; 9 10 checkTaskNotActual();11 bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));12 }13 //沒有緩衝在磁碟,從網路中下載圖片14 if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {15 L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);16 loadedFrom = LoadedFrom.NETWORK;17 18 String imageUriForDecoding = uri;19 if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {20 imageFile = configuration.diskCache.get(uri);21 if (imageFile != null) {22 imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());23 }24 }25 26 checkTaskNotActual();27 bitmap = decodeImage(imageUriForDecoding);28 29 if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {30 fireFailEvent(FailType.DECODING_ERROR, null);31 }32 }33 } catch (IllegalStateException e) {34 fireFailEvent(FailType.NETWORK_DENIED, null);35 } catch (TaskCancelledException e) {36 throw e;37 } catch (IOException e) {38 L.e(e);39 fireFailEvent(FailType.IO_ERROR, e);40 } catch (OutOfMemoryError e) {41 L.e(e);42 fireFailEvent(FailType.OUT_OF_MEMORY, e);43 } catch (Throwable e) {44 L.e(e);45 fireFailEvent(FailType.UNKNOWN, e);46 }47 return bitmap;48 }
從3~12行是嘗試從磁碟緩衝中載入Bitmap。第19行判斷磁碟中是否有緩衝,就開始進行網路下載(tryCacheImageOnDisk())。在tryCacheImageOnDisk()函數中有個tryCacheImageOnDisk()的 loaded = downloadImage()這行進行圖片下載。
private boolean downloadImage() throws IOException { InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader()); return configuration.diskCache.save(uri, is, this); }
這個函數做的事情很簡單,就是擷取一個實現Image Downloader的downloader(當然這裡,作者根據網路情況將downloader分為慢速(slowNetworkDownloader)、正常速度(downloader)、網路拒絕(networkDeniedDownloader)情況下的download,在這裡我們不展開,你只要知道他們是imageDownloader介面的實現者就行,後面的文章會探討這個問題),然後利用Disk Cache將Bitmap寫入磁碟緩衝中。返回到之前我們進入downloadImage()函數中的tryLoadBitmap(),在將圖片緩衝到磁碟中。是否緩衝到磁碟跟配置有關)後,緊接著調用 bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));解析圖片。進入decodeImage()函數中,我們發現UIL調用Image Decoder進行圖片的解析。
1 private Bitmap decodeImage(String imageUri) throws IOException {2 ViewScaleType viewScaleType = imageAware.getScaleType();3 ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,4 getDownloader(), options);5 return decoder.decode(decodingInfo);6 }
decode()函數最終是調用BaseImageDecoder.decode()方法進行解析的,這個利用之前獲得的inputStream,直接從它身上讀取資料,然後進行解析,並對整個下載任務的網路介面進行重設。
1 public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException { 2 Bitmap decodedBitmap; 3 ImageFileInfo imageInfo; 4 5 InputStream imageStream = getImageStream(decodingInfo); 6 try { 7 imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo); 8 imageStream = resetStream(imageStream, decodingInfo); 9 Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);10 decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);11 } finally {12 IoUtils.closeSilently(imageStream);13 }14 15 if (decodedBitmap == null) {16 L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());17 } else {18 decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,19 imageInfo.exif.flipHorizontal);20 }21 return decodedBitmap;22 }
接下來,有瞭解析好的Bitmap對象後,剩下的就是在Image View對象中顯示它了。我們回到文章一開始介紹到的ImageLoader.displayImage(...)函數中(相關的代碼在文章的開頭處可以看到)。
為了方便,我還是將ImageLoader.displayImage(...)中涉及的代碼貼在下面。
1 DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);2 runTask(displayBitmapTask, syncLoading, handler, engine);
我們進去DisplayBitmapTask.run()函數中看看。除去前面幾行的ImageLoadingListener.ImageLoadingListener()代碼,相關代碼其實就一行 displayer.display(bitmap, imageAware, loadedFrom),它其實就是調用BitmapDisplayer這個對象將Bitmap對象顯示到ImageView上。根據實現BitmapDisplayer介面的不同對象,還有SimpleBitmapDisplayer、FadeInBitmapDisplayer、RoundedBitmapDisplayer、RoundedVignetteBitmapDisplayer這5種對象。
最後,讓我們用任務流圖概況以上的處理流程中對應介面。
在接下去的文章中,我們會介紹UIL中的包設計、緩衝、下載、多任務機制。
從程式碼分析Android-Universal-Image-Loader的圖片載入、顯示流程