您的位置:首页 > 其它

UniversalImageLoader源码解读02-图片处理和显示

2016-05-11 17:09 197 查看
好了,有了ImageAware,我们知道是谁包装了我们的ImageView, 图片在哪里显示,现在我们把目光转向Bitmap的处理。

BitmapProcessor, 这个接口非常简单,只有一个方法Bitmap process(Bitmap bitmap); 可惜的是,框架并没有给我们提供默认实现,我们需要自己实现。

比如我们想在图片的右下角加个水印,或者将图片变成灰度图,或者做个高斯模糊,都可以实现这个接口,然后在DisplayImageOptions中使用。

BitmapDisplayer , 这个接口也非常简单, 也只有一个方法 void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom); 值得高兴的是,框架为我们提供了好几个默认实现,这里我只讲最简单的一个SimpleBitmapDisplayer,也是最常用的,功能很简单,只是单纯的 设置了一下图片而已

public final class SimpleBitmapDisplayer implements BitmapDisplayer {
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);//把图片设置到ImageAware包装的ImageView上
}
}


LoadedFrom 这个类封装了图片的来源,是个枚举

public enum LoadedFrom {
NETWORK, DISC_CACHE, MEMORY_CACHE // 网络,磁盘缓存,内存缓存
}


ImageDecoder这个接口也非常简单, 也只有一个方法 Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException; 并且为我们提供了一个默认实现

BaseImageDecoder , 这里比较重要的是ImageDecodingInfo,里面包含一些和图片缓存,下载等相关的信息,这里介绍几个重要属性,其他的自己看

private final String imageKey;//图片的唯一标识,全局唯一,作为内存缓存的key
private final String imageUri;//图片的Uri,可能是http:// file://等
private final ImageDownloader downloader;// 图片加载器,后面会详细介绍,这里你只要知道这个类的职责是从图片源(磁盘,网络) 去下载图片即可


ImageDownloader 接口非常简单,只有一个方法 InputStream getStream(String imageUri, Object extra) throws IOException; 和一个Scheme,Scheme的作用是定义图片的加载Uri,这里有必要说一下,以免有些人不知道如何使用ImageLoader去加载资源文件夹中的图片,请看定义

public enum Scheme {
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");

private String scheme;
private String uriPrefix;

Scheme(String scheme) {
this.scheme = scheme;
uriPrefix = scheme + "://";
}

public static Scheme ofUri(String uri) {
if (uri != null) {
for (Scheme s : values()) {
if (s.belongsTo(uri)) {
return s;
}
}
}
return UNKNOWN;
}

private boolean belongsTo(String uri) {
return uri.toLowerCase(Locale.US).startsWith(uriPrefix);
}

/** Appends scheme to incoming path */
public String wrap(String path) {
return uriPrefix + path;
}

/** Removed scheme part ("scheme://") from incoming URI */
public String crop(String uri) {
if (!belongsTo(uri)) {
throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme));
}
return uri.substring(uriPrefix.length());
}
}


例子:加载网络图片Uri:http://www.xxxxxx.com/abc.jpg 或 https://www.xxxxxx.com/abc.jpg
加载sdcard图片Uri : file:///sdcard/abc/def/aaaaa.jpg 注意file:// 后有一个根文件夹的盘符 ' / ',连一起是///

加载drawable文件夹下载资源文件 drawable://333125 注意一下这个数字是什么呢?是资源id ,也是R.drawable.icon 的值,使用时候你可以拼串

content asset下的资源同理,这里主要强调drawable,很多人不知道怎么用

接下来的内容非常重要,图片能否读取到,完全依赖于BaseImageDecoder, 这是一个重量级的类,占有举足轻重的地位

当然内容有很多,但是我们主要关注Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException; 这个方法,因为这个是接口定义的,子类必须实现的方法

@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;

InputStream imageStream = getImageStream(decodingInfo);//从ImageDownLoader中获取一个输入流
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);//从流中解出bitmap
} 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;//把从流中解出的bitmap返回
}


其他的细节自己看吧,很简单,注释就是代码的主干逻辑,一阵折腾,就是为了从流中拿到bitmap

这里我有必要详细介绍一下BaseImageDownloader ,因为这个类作为开发者会经常用到,比如,存在服务器的图片并不是一个图片的二进制流,而是一个base64串,这个时候你必须实现自己的ImageDownLoader,这个BaseImageDownloader就是框架给我们提供的最好的例子,我们可以参考它来写我们自己的加载逻辑

public class BaseImageDownloader implements ImageDownloader {
/** {@value} */
public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
/** {@value} */
public static final int DEFAULT_HTTP_READ_TIMEOUT = 20 * 1000; // milliseconds

/** {@value} */
protected static final int BUFFER_SIZE = 32 * 1024; // 32 Kb
/** {@value} */
protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";

protected static final int MAX_REDIRECT_COUNT = 5;

protected static final String CONTENT_CONTACTS_URI_PREFIX = "content://com.android.contacts/";

private static final String ERROR_UNSUPPORTED_SCHEME = "UIL doesn't support scheme(protocol) by default [%s]. " + "You should implement this support yourself (BaseImageDownloader.getStreamFromOtherSource(...))";

protected final Context context;
protected final int connectTimeout;
protected final int readTimeout;

public BaseImageDownloader(Context context) {
this.context = context.getApplicationContext();
this.connectTimeout = DEFAULT_HTTP_CONNECT_TIMEOUT;
this.readTimeout = DEFAULT_HTTP_READ_TIMEOUT;
}

public BaseImageDownloader(Context context, int connectTimeout, int readTimeout) {
this.context = context.getApplicationContext();
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
}

/**
* 这个是主干方法,就是根据传入的imageUri来判断应该去哪里加载图片
*/
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);//从网络获取图片输入流,注释见下
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);//从资源drawable文件夹获取图片输入流,注释见下
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);

//创建http连接
int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}

//拿到输入流
InputStream imageStream;
try {
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
//返回输入流
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}

protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
return conn;
}

protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
String filePath = Scheme.FILE.crop(imageUri);
return new ContentLengthInputStream(new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE),
(int) new File(filePath).length());
}

protected InputStream getStreamFromContent(String imageUri, Object extra) throws FileNotFoundException {
ContentResolver res = context.getContentResolver();

Uri uri = Uri.parse(imageUri);
if (isVideoUri(uri)) { // video thumbnail
Long origId = Long.valueOf(uri.getLastPathSegment());
Bitmap bitmap = MediaStore.Video.Thumbnails
.getThumbnail(res, origId, MediaStore.Images.Thumbnails.MINI_KIND, null);
if (bitmap != null) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 0, bos);
return new ByteArrayInputStream(bos.toByteArray());
}
} else if (imageUri.startsWith(CONTENT_CONTACTS_URI_PREFIX)) { // contacts photo
return ContactsContract.Contacts.openContactPhotoInputStream(res, uri);
}

return res.openInputStream(uri);
}

protected InputStream getStreamFromAssets(String imageUri, Object extra) throws IOException {
String filePath = Scheme.ASSETS.crop(imageUri);
return context.getAssets().open(filePath);
}

/**
* 我想,当你看到这里的时候,你会明白我为什么会说加载drawable要用drawable://787978了
*/
protected InputStream getStreamFromDrawable(String imageUri, Object extra) {
String drawableIdString = Scheme.DRAWABLE.crop(imageUri);
int drawableId = Integer.parseInt(drawableIdString);//parseInt()拿到资源id
return context.getResources().openRawResource(drawableId);//拿到输入流
}

protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException {
throw new UnsupportedOperationException(String.format(ERROR_UNSUPPORTED_SCHEME, imageUri));
}

private boolean isVideoUri(Uri uri) {
String mimeType = context.getContentResolver().getType(uri);

if (mimeType == null) {
return false;
}

return mimeType.startsWith("video/");
}
}


看到这里,怎么把base64转成输入流,我想不用我继续往下说了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: