为了开年面试的准备,开始看一些之前用过的开源库,今天就主要围绕Android-Universal-Image-Loader
github的源码带着大家浏览一遍。为了更好地理解Android-Universal-Image-Loader
代码部分,这里给大家画张类的结构图:
大家看到上面图,其实也不知道咋回事。下面就顺着代码去看看是怎么个流程:
ImageLoader.getInstance().displayImage(IMAGE_URLS[position], holder.image, options, animateFirstListener);
上面的代码是大家经常用的使用方法,那咱们去瞧瞧displayImage
方法是如何实现的,几个重载的displayImage
最终都会调用该displayImage
方法:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
//省略空判断的代码
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
//内存中bitmap存在
if (bmp != null && !bmp.isRecycled()) {
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
//如果是需要进行process操作,默认不会process操作的
if (options.shouldPostProcess()) {
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
//默认不是cync操作
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
} else {
//直接从内存中获取bitmap进行显示了
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
//内存中不存在bitmap的操作
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable(null);
}
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
//内存中没有bitmap走该task
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
//同步的话,就是立即执行
displayTask.run();
} else {
//默认走这里的,这里不会同步执行task,而是放在线程池中等待执行的
engine.submit(displayTask);
}
}
}
那咱们知道了内存中没有bitmap
的时候,会创建了一个LoadAndDisplayImageTask
。那咱们去瞧瞧:
从图上可以看出,它是一个子线程的操作。那咱们就直接去看
run
方法吧:
@Override
public void run() {
//省略部分代码
Bitmap bmp;
try {
checkTaskNotActual();
//此处又从memory中读取了
bmp = configuration.memoryCache.get(memoryCacheKey);
//内存中没有数据
if (bmp == null || bmp.isRecycled()) {
//从disk活网络加载bitmap
bmp = tryLoadBitmap();
//省略代码
if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
//获取完了后放到内存中
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {
loadedFrom = LoadedFrom.MEMORY_CACHE;
L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
}
//省略代码
} catch (TaskCancelledException e) {
fireCancelEvent();
return;
} finally {
loadFromUriLock.unlock();
}
//要显示的task
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
上面代码也说明了,如果memory中获取不到bitmap
会走tryLoadBitmap
方法,那咱们也去看看:
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
//从disk中获取bitmap,注意了这里只是获取是否存在该bitmap的文件
File imageFile = configuration.diskCache.get(uri);
//如果disk中有我们想要的bitmap文件
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;
checkTaskNotActual();
//进行了decodeImage操作
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
//网络操作的分支
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;
//默认的Scheme是网络类型的
String imageUriForDecoding = uri;
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
//如果图片的文件存在,此时会将uri格式改成FILE类型
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding);
//省略代码
}
} catch (IllegalStateException e) {
fireFailEvent(FailType.NETWORK_DENIED, null);
} catch (TaskCancelledException e) {
throw e;
} catch (IOException e) {
L.e(e);
fireFailEvent(FailType.IO_ERROR, e);
} catch (OutOfMemoryError e) {
L.e(e);
fireFailEvent(FailType.OUT_OF_MEMORY, e);
} catch (Throwable e) {
L.e(e);
fireFailEvent(FailType.UNKNOWN, e);
}
return bitmap;
}
可以看出上面最后都进调用了decodeImage
方法,那咱们去瞧瞧:
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
getDownloader(), options);
return decoder.decode(decodingInfo);
}
这里就顺藤摸瓜呗,看看decoder
的decode
方法了,这里的decoder
对象其实是一个BaseImageDecoder
对象,那咱们进去看看:
@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);
//输入流转成bitmap
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;
}
上面做了两件事:获取输入流、输入流转成bitmap,咱们要着重看下这里是怎么获取输入流的:
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
decodingInfo.getDownloader()
实际上默认是一个BaseImageDownloader
对象,那咱们瞧瞧getStream
方法:
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
//走网络的
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
//disk的cache就是走这里获取输入流
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
那咱们也进去看看这两种inputStream
都是怎么获取的吧:
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
//获取connection对象
HttpURLConnection conn = createConnection(imageUri, extra);
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 {
//这里就是咋们熟悉不过的HttpURLConnection请求代码了
String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
return conn;
}
也顺便看下file类型的输入流获取吧:
protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
String filePath = Scheme.FILE.crop(imageUri);
if (isVideoFileUri(imageUri)) {
return getVideoThumbnailStream(filePath);
} else {
//图片格式走这里,可以看到输入流对象直接是通过filePah直接new出来的
BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
return new ContentLengthInputStream(imageStream, (int) new File(filePath).length());
}
}
其实咋们漏了一个点,就是什么时候存储bitmap到disk中的呢?咋们还得回去看下tryLoadBitmap
方法中有这么一段:
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
其实这里的tryCacheImageOnDisk
方法就是进行保存到disk中的:
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
//存储到disk之前先判断有没有下载到图片
loaded = downloadImage();
if (loaded) {
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
if (width > 0 || height > 0) {
L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
resizeAndSaveImage(width, height); // TODO : process boolean result
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
private boolean downloadImage() throws IOException {
//注意了这里的getDownloader不是上面说的BaseImageDownloader了,这里是要分情况的
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {
return configuration.diskCache.save(uri, is, this);
} finally {
IoUtils.closeSilently(is);
}
}
}
private ImageDownloader getDownloader() {
ImageDownloader d;
//根据网络情况去获取不同的ImageDownloader
if (engine.isNetworkDenied()) {
d = networkDeniedDownloader;
} else if (engine.isSlowNetwork()) {
d = slowNetworkDownloader;
} else {
//这里是BaseImageDownloader
d = downloader;
}
return d;
}
咋们知道这么回事就行了,有兴趣的去看看他们是如何获取inputstream
。接着看tryCacheImageOnDisk
方法中用调用了resizeAndSaveImage
方法:
private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
// Decode image file, compress and re-save it
boolean saved = false;
//获取目标file文件
File targetFile = configuration.diskCache.get(uri);
if (targetFile != null && targetFile.exists()) {
ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
.imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
getDownloader(), specialOptions);
//decode操作,将Scheme为FILE类型的inputstream转化成bitmap对象
Bitmap bmp = decoder.decode(decodingInfo);
if (bmp != null && configuration.processorForDiskCache != null) {
L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
bmp = configuration.processorForDiskCache.process(bmp);
if (bmp == null) {
L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
}
}
if (bmp != null) {
//这里才是真正保存bitmap到disk中的操作
saved = configuration.diskCache.save(uri, bmp);
bmp.recycle();
}
}
return saved;
}
上面代码在保存到disk中,先是判断有没有下载到,如果有下载先进行保存inputstream
中的内容,在进行保存的时候,通过copy的方式来保存到OutputStream
中,这里在copy的时候,如果当前保存少于CONTINUE_LOADING_PERCENTAGE=75
的时候,视为失败。这里追踪代码到LruDiskCache
的save
方法,其中save
方法传入的是InputStream
对象。最后通过targetFile
文件进行decode
操作获取到bitmap
对象。最后又进行保存bitmap
对象到LruDiskCache
中。
这里我提个疑问哈,为什么在保存到disk中,为什么先去save传了一个inputstream
,紧接着又去save传了一个bitmap
至此,代码分析得差不多了,如果有什么疑问,我们可以共同讨论,或者加群(184793647
)