轉載請标明出處,本文出自:chaossss的部落格
Android-Universal-ImageLoader Github 位址
在上一篇博文中我給大家剖析了 Android-Universal-ImageLoader 中緩存功能的設計和實作,希望大家可以在裡面學到一丢丢東西哈。今天呢,我将接着向下講解,介紹 AUI 核心類中的圖檔解碼與顯示功能,如果大家沒有看過上一篇博文的話,可以戳我進去看哈,廢話不多說,下面進入正題:
圖檔解碼
在考慮具體實作之前,我們不妨先想想一個解碼器的職責是什麼?我相信答案大家都能脫口而出:解碼。沒錯,解碼器的核心功能在于解碼,換言之,我們在設計 Decoder 類的時候,就應該遵循單一職責原則:
public interface ImageDecoder {
Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
}
小夥伴們看到 decode() 方法裡的 ImageDecodingInfo 參數可能會很困惑,這是幹什麼的啊?我們不是隻要解碼圖檔麼。大家别急,我們先在腦海裡模拟一整個圖檔解碼的流程吧:
要解碼圖檔,我們得獲得圖檔的“來源”,圖檔可能來自網絡(那我們就得調用下載下傳子產品去下載下傳),圖檔可能來自本地的檔案(那我們就得通過 IO 流去讀取)。在獲得圖檔的來源之後,我們就要開始解碼了,我們進行解碼肯定不隻有一種方案,有時候可能能顯示出來就行了,有時候則要圖檔高清不失真地顯示出來,又或者我們需要縮放/放大圖檔,又甚至是需要定制圖檔的長和寬,這就意味着我們需要一個解碼輔助類存儲這些解碼所需的資訊,以便于解碼操作的完成。
是以大家現在應該能領會 ImageDecodingInfo 的作用了吧?我們一起來看看它的源碼吧:
public class ImageDecodingInfo {
private final String imageKey;
private final String imageUri;
private final String originalImageUri;
private final ImageSize targetSize;
private final ImageScaleType imageScaleType;
private final ViewScaleType viewScaleType;
private final ImageDownloader downloader;
private final Object extraForDownloader;
private final boolean considerExifParams;
private final Options decodingOptions;
public ImageDecodingInfo(String imageKey, String imageUri, String originalImageUri, ImageSize targetSize, ViewScaleType viewScaleType,
ImageDownloader downloader, DisplayImageOptions displayOptions) {
this.imageKey = imageKey;
this.imageUri = imageUri;
this.originalImageUri = originalImageUri;
this.targetSize = targetSize;
this.imageScaleType = displayOptions.getImageScaleType();
this.viewScaleType = viewScaleType;
this.downloader = downloader;
this.extraForDownloader = displayOptions.getExtraForDownloader();
considerExifParams = displayOptions.isConsiderExifParams();
decodingOptions = new Options();
copyOptions(displayOptions.getDecodingOptions(), decodingOptions);
}
private void copyOptions(Options srcOptions, Options destOptions) {
destOptions.inDensity = srcOptions.inDensity;
destOptions.inDither = srcOptions.inDither;
destOptions.inInputShareable = srcOptions.inInputShareable;
destOptions.inJustDecodeBounds = srcOptions.inJustDecodeBounds;
destOptions.inPreferredConfig = srcOptions.inPreferredConfig;
destOptions.inPurgeable = srcOptions.inPurgeable;
destOptions.inSampleSize = srcOptions.inSampleSize;
destOptions.inScaled = srcOptions.inScaled;
destOptions.inScreenDensity = srcOptions.inScreenDensity;
destOptions.inTargetDensity = srcOptions.inTargetDensity;
destOptions.inTempStorage = srcOptions.inTempStorage;
if (Build.VERSION.SDK_INT >= ) copyOptions10(srcOptions, destOptions);
if (Build.VERSION.SDK_INT >= ) copyOptions11(srcOptions, destOptions);
}
//get方法略去
…………
@TargetApi()
private void copyOptions10(Options srcOptions, Options destOptions) {
destOptions.inPreferQualityOverSpeed = srcOptions.inPreferQualityOverSpeed;
}
@TargetApi()
private void copyOptions11(Options srcOptions, Options destOptions) {
destOptions.inBitmap = srcOptions.inBitmap;
destOptions.inMutable = srcOptions.inMutable;
}
}
BaseImageDecoder
我們在獲得了圖檔解碼器的基類 ImageDecoder 後,就得完成我們的具體實作了。那麼我們現在就得想想,基于 ImageDecoder 的抽象:decode() 方法,我們會衍生出哪些 ImageDecoder 的實作細節。為了完成圖檔解碼操作,我們會按照下面的步驟實作:
獲得圖檔的“來源” —> 獲得圖檔輸入流并判斷圖檔的實際大小、是否需要被裁減以及是否需要旋轉 —> 獲得圖檔 —> 根據圖檔資訊以及解碼設定處理圖檔 —> 得到符合要求的圖檔
既然實作思路已經定下來了,那具體實作肯定也不難拉:
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
InputStream imageStream = getImageStream(decodingInfo);
if (imageStream == null) {
L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
return null;
}
try {
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
imageStream = resetStream(imageStream, decodingInfo);
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
代碼我就不全放出來了,大家可以根據剛剛的思路順着這裡的實作進入相應的方法看就行了。而 ExifInfo、ImageFileInfo 兩個類隻是解碼過程需要的輔助類,提供處理圖檔需要的一些資訊,例如:圖檔檔案的大小、圖檔是否需要裁減、旋轉:
protected static class ExifInfo {
public final int rotation;
public final boolean flipHorizontal;
protected ExifInfo() {
this.rotation = ;
this.flipHorizontal = false;
}
protected ExifInfo(int rotation, boolean flipHorizontal) {
this.rotation = rotation;
this.flipHorizontal = flipHorizontal;
}
}
protected static class ImageFileInfo {
public final ImageSize imageSize;
public final ExifInfo exif;
protected ImageFileInfo(ImageSize imageSize, ExifInfo exif) {
this.imageSize = imageSize;
this.exif = exif;
}
}
圖檔顯示
事實上大家會發現,圖檔顯示和圖檔解碼兩個功能在實作上會很相似:抽象單一(圖檔顯示/圖檔解碼),具體實作隻要根據相應的需求依據抽象實作細節就可以了。
public interface BitmapDisplayer {
void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
}
我們顯示圖檔的時候可能需要顯示圓形/矩形/圓角圖檔,或者設定相應的圖檔動畫等等等等……實際上我們隻需要依據相應的圖檔形狀、動畫設定/實作相應的 Drawable 和 Animation 就可以了。我覺得這裡沒有啥好說的……但在這裡引入了一個接口 ImageAware,我倒覺得值得我們注意一下:
public interface ImageAware {
int getWidth();
int getHeight();
ViewScaleType getScaleType();
View getWrappedView();
boolean isCollected();
int getId();
boolean setImageDrawable(Drawable drawable);
boolean setImageBitmap(Bitmap bitmap);
}
通過這個接口我們在圖檔顯示類内獲得顯示圖檔的 View 的資訊,還可以修改 View 顯示的圖檔,圖檔的形狀,以及顯示圖檔的 ID。可能有人會說,然而這并沒有什麼卵用……但你仔細想想,真的是這樣麼?如果沒有這個接口,我們要怎麼在 Display 類裡面直接讓圖檔顯示為圓形,抑或是添加相應的動畫呢?要知道這些效果的最終實作都是應用到 View 上面的。
當然了,顯示圓形圖檔和動畫肯定能實作,例如自定義 View,或者是把 View 作為參數傳入 Display,在 Display 類裡對每一個顯示圖檔的 View 進行處理或者是獲得 View 的屬性。AUI 中的做法也是值得參考的,因為它把這一塊邏輯剝離,避免耦合。