從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中的包設計、緩沖、下載、多任務機制。
文章列表