转载请标明出处,本文出自: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 >= 10) copyOptions10(srcOptions, destOptions);
if (Build.VERSION.SDK_INT >= 11) copyOptions11(srcOptions, destOptions);
}
//get方法略去
…………
@TargetApi(10)
private void copyOptions10(Options srcOptions, Options destOptions) {
destOptions.inPreferQualityOverSpeed = srcOptions.inPreferQualityOverSpeed;
}
@TargetApi(11)
private void copyOptions11(Options srcOptions, Options destOptions) {
destOptions.inBitmap = srcOptions.inBitmap;
destOptions.inMutable = srcOptions.inMutable;
}
}
我们在获得了图片解码器的基类 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 = 0;
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 中的做法也是值得参考的,因为它把这一块逻辑剥离,避免耦合。