介绍
ImageLoader是android使用中出现比较早,使用最多的一个开源图片加载库了,随着glide,picasso等图片加载的库出现,ImageLoader使用变得越来越少。
特点
- 支持网络,本地加载图片
- 多线程下载图片
- 支持图片下载进度的监听
- 支持Listview滚动时暂停下载
- 支持内存和磁盘的图片缓存
- 支持自定义配置选项(线程执行器,下载器,编码器,缓存,显示图片等)
本文也是采用调用流程为线的方式来分析源码,下面我们先来看下ImageLoader整体流程图和核心类的名称和作用。
预习
流程图:
重要的类:
-
ImageLoaderConfiguration
配置信息类,使用builder设计模式
-
ImageLoaderEngine
图片加载引擎,主要负责将LoadAndDisplayImageTaskProcessAndDisplayImageTask任务分发给线程池去执行 Executor taskExecutor; // 用于从源获取图片的线程池 Executor taskExecutorForCachedImages; // 从缓存池中获取图片的线程池 Executor taskDistributor; // 分发任务的线程池,把任务分发到上面两个线程池中
-
ImageAware
图片显示的对象,一般为ImageView的封装
-
ImageDownloader
图片下载器,主要是从本地,网络等获取图片流
-
MemoryCache
内存缓存,使用Lru算法实现对内存缓存的管理,当图片占用内存或者图片数量大于设置的阈值,回收最老的,最少使用的。
-
DiskCache
本地缓存,可以自定义配置,实现对本地缓存的控制
-
ImageDecoder
图片解码器,将输入流转化成Bitmap
-
BitmapProcessor
图片处理器,图片的预处理和后期处理都使用这个类,图片的处理包括图片宽高等等
-
BitmapDisplayer
bitmap显示器,负责将处理过后的bitmap显示
-
LoadAndDisplayImageTask
加载并显示图片任务
-
ProcessAndDisplayImageTask
处理并显示图片任务
-
DisplayBitmapTask
显示图片任务
源码解析
本文不打算对ImageLoaderConfiguration配置类进行讲解,其实就是进行配置,还有使用了Builder设置模式,有兴趣的可以去看源码。
1.ImageLoader.displayImage
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
//检查配置
checkConfiguration();
//如果没有显示对象就抛出异常
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
//如果没有加载监听,设置默认的
if (listener == null) {
listener = defaultListener;
}
//设置显示图片的选项(缓存,加载中显示,图片处理,是否同步,编码)
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
//请求地址为空
if (TextUtils.isEmpty(uri)) {
//取消显示任务
engine.cancelDisplayTaskFor(imageAware);
//加载开始
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//空地址情况下,是否有显示的图片
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
imageAware.setImageDrawable(null);
}
//加载完成
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
return;
}
//图片宽高为空,设置为imageview宽高
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
//通过uri+targetsize生成缓存 key
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
//将imageAware和缓存key关联
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
//开始加载
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//获取缓存
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
//有缓存且可用
//是否支持后期处理bitmap
if (options.shouldPostProcess()) {
//封装ImageLoadingInfo,封装处理显示任务
//defineHandler()如果同步,返回null
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
//是否同步调用
if (options.isSyncLoading()) {
//直接执行run(当前线程)
displayTask.run();
} else {
//将任务交给taskExecutorForCachedImages处理缓存的线程池来执行
engine.submit(displayTask);
}
} else {
//不支持后期处理bitmap
//显示bitmap,加载完毕
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
//没有缓存或者已经被回收
//是否有显示加载中图片
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
//加载图片前重置
imageAware.setImageDrawable(null);
}
//封装ImageLoadingInfo,封装加载显示任务
//defineHandler()如果同步,返回null
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
//是否同步
if (options.isSyncLoading()) {
//同步执行run
displayTask.run();
} else {
//任务交给taskDistributor进行派发(根据是否有本地缓存)
engine.submit(displayTask);
}
}
}
displayImage方法中主要就是:
-
生成 cacheKey,并和当前ImageAware保存到map中
作用是,在线程中判断当前ImageAware是否被重用了
有缓存,异步,生成ProcessAndDisplayImageTask,且交给缓存线程池taskExecutorForCachedImages
没有缓存,异步,生成LoadAndDisplayImageTask,交给分发线程池taskDistributor来根据是否有本地缓存来分发
关于同步我们就不讲解了,我们下面从两个方面讲解:有内存缓存异步,没有内存缓存异步。
2.有内存缓存异步
2.1 ProcessAndDisplayImageTask.run()
public void run() {
//后期处理图片
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
Bitmap processedBitmap = processor.process(bitmap);
//创建DisplayBitmapTask(显示任务)
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE);
//执行显示任务
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
对来自内存缓存中的图片进行后期处理,创建DisplayBitmapTask,交给LoadAndDisplayImageTask的runTask方法去执行。
2.2 LoadAndDisplayImageTask.runTask
static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
if (sync) {
//同步执行,当前线程显示
r.run();
} else if (handler == null) {
//异步执行,且在执行线程中显示
engine.fireCallback(r);
} else {
//handler执行(主线程显示)
handler.post(r);
}
}
runTask方法就是判断显示任务在那个线程中执行。
2.3 DisplayBitmapTask.run
public void run() {
//imageAware被回收
if (imageAware.isCollected()) {
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else if (isViewWasReused()) {
//imageAware被重用
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else {
//显示图片
displayer.display(bitmap, imageAware, loadedFrom);
//移除cacheKeysForImageAwares中当前iamgeAware元素
engine.cancelDisplayTaskFor(imageAware);
//调用加载完成监听
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
如果ImageAware被回收或者被重用,那么直接回调onLoadingCancelled,负责显示图片,移除cacheKeysForImageAwares中当前ImageAware的数据,回调onLoadingComplete。
2.4 isViewWasReused()
private boolean isViewWasReused() {
//获取ImageAware最新的cacheKey
String currentCacheKey = engine.getLoadingUriForView(imageAware);
//如果当前任务的cacheKey和最新的cacheKey不一致,说明ImageAware被重用了。
return !memoryCacheKey.equals(currentCacheKey);
}
如果当前任务的cacheKey和最新的cacheKey不一致,说明ImageAware被重用了。
有内存缓存的情况已经分析结束。
3.没有内存缓存异步
3.1 ImageLoaderEngine.submit()
void submit(final LoadAndDisplayImageTask task) {
taskDistributor.execute(new Runnable() {
@Override
public void run() {
//获取本地缓存
File image = configuration.diskCache.get(task.getLoadingUri());
boolean isImageCachedOnDisk = image != null && image.exists();
initExecutorsIfNeed();
//是否有本地缓存
if (isImageCachedOnDisk) {
//如果有本地缓存,交给处理缓存线程池
taskExecutorForCachedImages.execute(task);
} else {
//如果没有本地缓存,交给taskExecutor
taskExecutor.execute(task);
}
}
});
}
分发线程池taskDistributor根据是否有本地缓存来进行处理:
- 有本地缓存,将任务交给缓存线程池
- 没有本地缓存,将任务交给taskExecutor
3.2 LoadAndDisplayImageTask.run()
public void run() {
//如果线程被打断,进入休眠
//如果线程没有被打断或者被唤醒,且imageAware被GC回收,或者imageAware被重用了,那么返回true
if (waitIfPaused()) return;
//延时加载且imageAware被GC回收,或者imageAware被重用了,那么返回true
if (delayIfNeed()) return;
//根据当前uri,获取锁
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
//加锁
loadFromUriLock.lock();
Bitmap bmp;
try {
//如果ImageAware被回收了或者被重用了,直接抛出任务取消异常TaskCancelledException
checkTaskNotActual();
//获取内存缓存(有可能图片被其他线程加载过)
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
//没有内存缓存
//尝试从本地缓存或者本地或者网络等等源加载图片
bmp = tryLoadBitmap();
//失败的onLoadingFailed监听已经被回调,这边直接返回
if (bmp == null) return; // listener callback already was fired
//处理图片前,检查ImageAware被回收了,或者被重用了,或者线程被打断了
checkTaskNotActual();
checkTaskInterrupted();
//内存缓存前的预处理
if (options.shouldPreProcess()) {
bmp = options.getPreProcessor().process(bmp);
}
//添加到内存缓存中
if (bmp != null && options.isCacheInMemory()) {
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {
//有内存缓存
loadedFrom = LoadedFrom.MEMORY_CACHE;
}
//内存缓存后的处理
if (bmp != null && options.shouldPostProcess()) {
bmp = options.getPostProcessor().process(bmp);
}
////处理图片后,检查ImageAware被回收了,或者被重用了,或者线程被打断了
checkTaskNotActual();
checkTaskInterrupted();
} catch (TaskCancelledException e) {
//异步且线程不是被打断的,执行onLoadingCancelled
fireCancelEvent();
return;
} finally {
//释放锁
loadFromUriLock.unlock();
}
//创建DisplayBitmapTask任务
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
//执行
runTask(displayBitmapTask, syncLoading, handler, engine);
}
代码注释很清晰的,来个简单的流程总结:
- 1.imageAware被GC回收,或者imageAware被重用了,直接返回true
- 2.加锁
- 3.获取内存缓存
有缓存:
- 4.后期的处理
- 5.释放锁
- 6.创建DisplayBitmapTask任务,并执行
没有缓存
- 4.从本地缓存中获取,如果没有从网络,sd卡等中获取图片
- 5.预处理
- 6.添加到内存缓存中
- 7.后期的处理
- 8.释放锁
- 9.创建DisplayBitmapTask任务,并执行
下面分析核心的方法:
3.2.1 waitIfPaused() && delayIfNeed()
3.2.1.1 waitIfPaused()
/**
* 如果线程被打断(比如:滚动listview),线程进入wait,并释放锁
* 如果线程没有被打断或者被唤醒,你们返回isTaskNotActual()
* isTaskNotActual --- >如果imageAware被GC回收,或者imageAware被重用了,那么返回true
*
* @return
*/
private boolean waitIfPaused() {
//获取是否暂停状态
AtomicBoolean pause = engine.getPause();
if (pause.get()) {
synchronized (engine.getPauseLock()) {
if (pause.get()) {
L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
try {
//休眠,等待被唤醒
engine.getPauseLock().wait();
} catch (InterruptedException e) {
L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
return true;
}
L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
}
}
}
return isTaskNotActual();
}
- 如果当前的状态为暂停(pasue = true),那么进入休眠,等待被唤醒
- 如果没有暂停或者被唤醒,那么返回isTaskNotActual()结果
上面代码有两个点需要注意:1.何时暂停,何时被唤醒。 2.isTaskNotActual()执行
1.何时暂停,何时被唤醒
我们都知道当ListView滑动的时候,ImageLoader会暂停图片加载,停止滑动时,继续加载图片,这就是暂停和唤醒的时机:
PauseOnScrollListener.onScrollStateChanged()
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
//恢复加载(唤醒)
imageLoader.resume();
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
if (pauseOnScroll) {
//滑动,暂停加载(休眠)
imageLoader.pause();
}
break;
case OnScrollListener.SCROLL_STATE_FLING:
if (pauseOnFling) {
//滚动,暂停加载(休眠)
imageLoader.pause();
}
break;
}
...
}
ImageLoaderEngine.java
private final AtomicBoolean paused = new AtomicBoolean(false);
void pause() {
paused.set(true);
}
void resume() {
paused.set(false);
synchronized (pauseLock) {
//唤醒所有任务
pauseLock.notifyAll();
}
}
2.isTaskNotActual()
/**
* 如果imageAware被GC回收,或者imageAware被重用了,那么返回true
* @return
*/
private boolean isTaskNotActual() {
return isViewCollected() || isViewReused();
}
/**
* imageAware是否被GC回收
*
* @return 回收 --true
*/
private boolean isViewCollected() {
if (imageAware.isCollected()) {
return true;
}
return false;
}
/**
* ImageAware是否被重用,来显示其他图片
* 以开始执行时,存放在cacheKeysForImageAwares中的cacheKey为最新的key
* 如果当前的cachekey不相同,则停止舍弃当前的加载任务
*
* @return true--重用
*/
private boolean isViewReused() {
//获取最新的cachekey
String currentCacheKey = engine.getLoadingUriForView(imageAware);
//如果和当前cachekey不一致,说明imageAware被重用了
boolean imageAwareWasReused = !memoryCacheKey.equals(currentCacheKey);
if (imageAwareWasReused) {
return true;
}
return false;
}
waitIfPaused()总结:
- 如果暂停加载,进入休眠等待被唤醒
- 如果没有暂停,或者被唤醒,则ImageAware被重用或者imageAware被GC回收,返回true,直接结束
3.2.1.2 delayIfNeed()
private boolean delayIfNeed() {
//延时加载
if (options.shouldDelayBeforeLoading()) {
try {
//睡眠
Thread.sleep(options.getDelayBeforeLoading());
} catch (InterruptedException e) {
return true;
}
return isTaskNotActual();
}
return false;
}
delayIfNeed总结:
- 如果没有延时加载,返回false
- 如果延时加载,进入睡眠
- 线程被打断结束睡眠 ,返回true
- 正常结束睡眠,则ImageAware被重用或者imageAware被GC回收,返回true,直接结束
3.2.2 checkTaskNotActual() && checkTaskInterrupted()
//如果ImageAware被GC回收,抛出TaskCancelledException
private void checkViewCollected() throws TaskCancelledException {
if (isViewCollected()) {
throw new TaskCancelledException();
}
}
//如果ImageAware被重用,抛出TaskCancelledException
private void checkViewReused() throws TaskCancelledException {
if (isViewReused()) {
throw new TaskCancelledException();
}
}
checkTaskNotActual总结:
- 如果ImageAware被GC回收或者ImageAware被重用,抛出TaskCancelledException异常
- 抛出异常的处理将在后面进行讲解
//如果线程被打断,抛出TaskCancelledException
private void checkTaskInterrupted() throws TaskCancelledException {
if (isTaskInterrupted()) {
throw new TaskCancelledException();
}
}
//如果线程被打断,返回true
private boolean isTaskInterrupted() {
if (Thread.interrupted()) {
return true;
}
return false;
}
checkTaskInterrupted总结:
- 如果线程被打断,抛出TaskCancelledException异常
- 抛出异常的处理将在后面进行讲解
3.2.3 TaskCancelledException 异常处理
private void fireCancelEvent() {
//同步或者线程被打断,直接返回
if (syncLoading || isTaskInterrupted()) return;
Runnable r = new Runnable() {
@Override
public void run() {
listener.onLoadingCancelled(uri, imageAware.getWrappedView());
}
};
//执行onLoadingCancelled
runTask(r, false, handler, engine);
}
如果是同步或者线程被打断,直接返回,否则在指定线程中回调onLoadingCancelled
3.2.4 tryLoadBitmap()
获取图片的核心方法:
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
//获取本地缓存
File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
//有本地缓存
loadedFrom = LoadedFrom.DISC_CACHE;
//检查
checkTaskNotActual();
//解码(file-->inputstream--->bitmap)
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
//没有本地缓存
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
loadedFrom = LoadedFrom.NETWORK;
String imageUriForDecoding = uri;
//如果开启本地缓存则调用tryCacheImageOnDisk从network,asset,content,file,drawable中获取图片,且缓存到本地缓存中
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
//获取到下载的文件
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
//检查
checkTaskNotActual();
//如果上面已经获取了图片,则这里的uri为本地uri
//如果没有,下面将从network,asset,content,file,drawable中获取图片
//内存保存的是根据ImageView大小、scaletype、方向处理过得图片
bitmap = decodeImage(imageUriForDecoding);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
//如果是异步且线程没有被打断,imageaware没有被回收和重用
// 那么获取图片失败,显示失败后的图片,并且调用onLoadingFailed
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
} catch (IllegalStateException e) {
//如果是异步且线程没有被打断,imageaware没有被回收和重用
// 那么获取图片失败,显示失败后的图片,并且调用onLoadingFailed
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;
}
tryLoadBitmap流程:
- 1.获取本地缓存
- 2.如果有本地缓存,直接解码取出
- 3.如果开启了本地缓存,则下载图片,并将图片保存到本地缓存中
- 4.解码图片,返回
分析几个核心方法:
1.tryCacheImageOnDisk
/**
* 从network,asset,content,file,drawable中获取图片,且缓存到本地缓存中
* @return 获取图片,保存本地成功返回true
* @throws TaskCancelledException
*/
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
boolean loaded;
try {
//获取图片,并保存本地缓存
loaded = downloadImage();
if (loaded) {
//将原图转化为设定的最大本地缓存图片大小(默认为0,所以不要改边)
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) {
loaded = false;
}
return loaded;
}
private boolean downloadImage() throws IOException {
//下载图片
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
if (is == null) {
return false;
} else {
try {
//保存到磁盘中
return configuration.diskCache.save(uri, is, this);
} finally {
IoUtils.closeSilently(is);
}
}
}
BaseImageDownloader.getStream()
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
//网络图片通过HttpURLConnection实现的
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);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
getStream总结:
- 根据uri协议的不同,从不同的源加载图片
tryCacheImageOnDisk总结:
- 获取图片,保存到本地缓存
2.decodeImage
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
//targetSize -- imageview大小或者内存缓存的最大size
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
getDownloader(), options);
return decoder.decode(decodingInfo);
}
BaseImageDecoder.decode()
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
//获取图片流
InputStream imageStream = getImageStream(decodingInfo);
if (imageStream == null) {
return null;
}
try {
//确定图片尺寸 和 旋转角度 ,生成ImageFileInfo
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
//重置流游标
imageStream = resetStream(imageStream, decodingInfo);
//BitmapFactory.options
//准备decode的opions
//decodingOptions.inSampleSize = scale; 对bitmap进行压缩
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
} else {
//对Bitmap进行缩放,翻转和旋转等操作
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
getImageStream():
//调用BaseImageDownloader.getStream()
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
defineImageSizeAndRotation()
//获取图片的size 和 旋转角度(EXIF信息)
protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo) {
//BitmapFactory.options
Options options = new Options();
//不加载bitmap数据,只返回bitmap信息
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(imageStream, null, options);
//EXIF信息,是可交换图像文件的缩写,是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。
ExifInfo exif;
String imageUri = decodingInfo.getImageUri();
//考虑 exif 且 文件能够定义 exif(JPEG文件)
//附加:EXIF可以附加于JPEG、TIFF、RIFF等文件之中
if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
//根据文件exif信息,获取旋转角度,保存到ExifInfo中
exif = defineExifOrientation(imageUri);
} else {
//创建对象(角度为0,即不旋转)
exif = new ExifInfo();
}
//将 图片的宽高,旋转角度 ,exif 构建ImageFileInfo对象
return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
}
resetStream()
//重置游标
protected InputStream resetStream(InputStream imageStream, ImageDecodingInfo decodingInfo) throws IOException {
if (imageStream.markSupported()) {
try {
//重置游标
imageStream.reset();
return imageStream;
} catch (IOException ignored) {
}
}
IoUtils.closeSilently(imageStream);
return getImageStream(decodingInfo);
}
prepareDecodingOptions():
准备decode的opions,根据ImageScaleType,imagesize计算decodingOptions.inSampleSize 的值。
considerExactScaleAndOrientatiton()
//对Bitmap进行缩放,翻转和旋转等操作
protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo,
int rotation, boolean flipHorizontal) {
Matrix m = new Matrix();
// Scale to exact size if need
//缩放到指定size
ImageScaleType scaleType = decodingInfo.getImageScaleType();
if (scaleType == ImageScaleType.EXACTLY || scaleType == ImageScaleType.EXACTLY_STRETCHED) {
ImageSize srcSize = new ImageSize(subsampledBitmap.getWidth(), subsampledBitmap.getHeight(), rotation);
float scale = ImageSizeUtils.computeImageScale(srcSize, decodingInfo.getTargetSize(), decodingInfo
.getViewScaleType(), scaleType == ImageScaleType.EXACTLY_STRETCHED);
if (Float.compare(scale, 1f) != 0) {
m.setScale(scale, scale);
if (loggingEnabled) {
L.d(LOG_SCALE_IMAGE, srcSize, srcSize.scale(scale), scale, decodingInfo.getImageKey());
}
}
}
// Flip bitmap if need
//翻转bitmap
if (flipHorizontal) {
m.postScale(-1, 1);
if (loggingEnabled) L.d(LOG_FLIP_IMAGE, decodingInfo.getImageKey());
}
// Rotate bitmap if need
//旋转bitmap
if (rotation != 0) {
m.postRotate(rotation);
if (loggingEnabled) L.d(LOG_ROTATE_IMAGE, rotation, decodingInfo.getImageKey());
}
Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
.getHeight(), m, true);
if (finalBitmap != subsampledBitmap) {
subsampledBitmap.recycle();
}
return finalBitmap;
}
decodeImage总结:
- 获取bitmap,根据ImageView大小、scaletype、方向,旋转角度处理图片,最后返回
3.fireFailEvent
/**
* 如果是异步且线程没有被打断,imageaware没有被回收和重用
那么获取图片失败,显示失败后的图片,并且调用onLoadingFailed
* @param failType
* @param failCause
*/
private void fireFailEvent(final FailType failType, final Throwable failCause) {
if (syncLoading || isTaskInterrupted() || isTaskNotActual()) return;
Runnable r = new Runnable() {
@Override
public void run() {
//设置失败后的图片
if (options.shouldShowImageOnFail()) {
imageAware.setImageDrawable(options.getImageOnFail(configuration.resources));
}
listener.onLoadingFailed(uri, imageAware.getWrappedView(), new FailReason(failType, failCause));
}
};
runTask(r, false, handler, engine);
}
3.2.5 执行DisplayBitmapTask
同上,2.2-2.3
上一个我自己画的流程图(请忽略试用版水印几个大字):
imageloader框架的源码解析就讲到这里,有兴趣的朋友可以去研究下内存缓存,本地缓存的实现等等。