文章出處

UNIVERSAL IMAGE LOADER. PART 3(四個DisplayImage重載方法詳解)中,我們學習了Android-Universal-Image-Loader(以下簡稱UIL)中四個DisplayImage重載方法的使用,如果你還沒有學習,最好先返回去看看,不然可能不理解這篇文章。在這篇文章中我們將主要探討Android-Universal-Image-Loader的主要流程和這些流程相關的類的分析。

我們先了解一下UIL加載圖片的流程(可以通過查看ImageLoader.displayImage(…)方法分析得出),如下圖

image

從上圖中,我們可以看出,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中的包設計、緩沖、下載、多任務機制。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()