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

    好了,有了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转成输入流,我想不用我继续往下说了


你可能感兴趣的:(ImageLoader源码解读)