好了,有了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上 } }
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()); } }
加载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返回 }
这里我有必要详细介绍一下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/"); } }