介紹
ImageLoader是android使用中出現(xiàn)比較早,使用最多的一個開源圖片加載庫了柏蘑,隨著glide,picasso等圖片加載的庫出現(xiàn)届搁,ImageLoader使用變得越來越少。
特點(diǎn)
- 支持網(wǎng)絡(luò),本地加載圖片
- 多線程下載圖片
- 支持圖片下載進(jìn)度的監(jiān)聽
- 支持Listview滾動時暫停下載
- 支持內(nèi)存和磁盤的圖片緩存
- 支持自定義配置選項(xiàng)(線程執(zhí)行器愕鼓,下載器,編碼器,緩存望门,顯示圖片等)
本文也是采用調(diào)用流程為線的方式來分析源碼,下面我們先來看下ImageLoader整體流程圖和核心類的名稱和作用锰霜。
預(yù)習(xí)
流程圖:
重要的類:
-
ImageLoaderConfiguration
配置信息類筹误,使用builder設(shè)計(jì)模式
-
ImageLoaderEngine
圖片加載引擎,主要負(fù)責(zé)將LoadAndDisplayImageTaskProcessAndDisplayImageTask任務(wù)分發(fā)給線程池去執(zhí)行 Executor taskExecutor; // 用于從源獲取圖片的線程池 Executor taskExecutorForCachedImages; // 從緩存池中獲取圖片的線程池 Executor taskDistributor; // 分發(fā)任務(wù)的線程池癣缅,把任務(wù)分發(fā)到上面兩個線程池中
-
ImageAware
圖片顯示的對象厨剪,一般為ImageView的封裝
-
ImageDownloader
圖片下載器哄酝,主要是從本地,網(wǎng)絡(luò)等獲取圖片流
-
MemoryCache
內(nèi)存緩存祷膳,使用Lru算法實(shí)現(xiàn)對內(nèi)存緩存的管理陶衅,當(dāng)圖片占用內(nèi)存或者圖片數(shù)量大于設(shè)置的閾值,回收最老的直晨,最少使用的万哪。
-
DiskCache
本地緩存,可以自定義配置抡秆,實(shí)現(xiàn)對本地緩存的控制
-
ImageDecoder
圖片解碼器奕巍,將輸入流轉(zhuǎn)化成Bitmap
-
BitmapProcessor
圖片處理器,圖片的預(yù)處理和后期處理都使用這個類儒士,圖片的處理包括圖片寬高等等
-
BitmapDisplayer
bitmap顯示器的止,負(fù)責(zé)將處理過后的bitmap顯示
-
LoadAndDisplayImageTask
加載并顯示圖片任務(wù)
-
ProcessAndDisplayImageTask
處理并顯示圖片任務(wù)
-
DisplayBitmapTask
顯示圖片任務(wù)
源碼解析
本文不打算對ImageLoaderConfiguration配置類進(jìn)行講解,其實(shí)就是進(jìn)行配置着撩,還有使用了Builder設(shè)置模式诅福,有興趣的可以去看源碼。
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);
}
//如果沒有加載監(jiān)聽拖叙,設(shè)置默認(rèn)的
if (listener == null) {
listener = defaultListener;
}
//設(shè)置顯示圖片的選項(xiàng)(緩存氓润,加載中顯示,圖片處理薯鳍,是否同步咖气,編碼)
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
//請求地址為空
if (TextUtils.isEmpty(uri)) {
//取消顯示任務(wù)
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;
}
//圖片寬高為空挖滤,設(shè)置為imageview寬高
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
//通過uri+targetsize生成緩存 key
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
//將imageAware和緩存key關(guān)聯(lián)
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崩溪,封裝處理顯示任務(wù)
//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));
//是否同步調(diào)用
if (options.isSyncLoading()) {
//直接執(zhí)行run(當(dāng)前線程)
displayTask.run();
} else {
//將任務(wù)交給taskExecutorForCachedImages處理緩存的線程池來執(zhí)行
engine.submit(displayTask);
}
} else {
//不支持后期處理bitmap
//顯示bitmap,加載完畢
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
//沒有緩存或者已經(jīng)被回收
//是否有顯示加載中圖片
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
//加載圖片前重置
imageAware.setImageDrawable(null);
}
//封裝ImageLoadingInfo斩松,封裝加載顯示任務(wù)
//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()) {
//同步執(zhí)行run
displayTask.run();
} else {
//任務(wù)交給taskDistributor進(jìn)行派發(fā)(根據(jù)是否有本地緩存)
engine.submit(displayTask);
}
}
}
displayImage方法中主要就是:
-
生成 cacheKey,并和當(dāng)前ImageAware保存到map中
作用是惧盹,在線程中判斷當(dāng)前ImageAware是否被重用了
有緩存乳幸,異步,生成ProcessAndDisplayImageTask钧椰,且交給緩存線程池taskExecutorForCachedImages
沒有緩存粹断,異步,生成LoadAndDisplayImageTask演侯,交給分發(fā)線程池taskDistributor來根據(jù)是否有本地緩存來分發(fā)
關(guān)于同步我們就不講解了姿染,我們下面從兩個方面講解:有內(nèi)存緩存異步,沒有內(nèi)存緩存異步。
2.有內(nèi)存緩存異步
2.1 ProcessAndDisplayImageTask.run()
public void run() {
//后期處理圖片
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
Bitmap processedBitmap = processor.process(bitmap);
//創(chuàng)建DisplayBitmapTask(顯示任務(wù))
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE);
//執(zhí)行顯示任務(wù)
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
對來自內(nèi)存緩存中的圖片進(jìn)行后期處理悬赏,創(chuàng)建DisplayBitmapTask狡汉,交給LoadAndDisplayImageTask的runTask方法去執(zhí)行。
2.2 LoadAndDisplayImageTask.runTask
static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
if (sync) {
//同步執(zhí)行闽颇,當(dāng)前線程顯示
r.run();
} else if (handler == null) {
//異步執(zhí)行盾戴,且在執(zhí)行線程中顯示
engine.fireCallback(r);
} else {
//handler執(zhí)行(主線程顯示)
handler.post(r);
}
}
runTask方法就是判斷顯示任務(wù)在那個線程中執(zhí)行。
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中當(dāng)前iamgeAware元素
engine.cancelDisplayTaskFor(imageAware);
//調(diào)用加載完成監(jiān)聽
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
如果ImageAware被回收或者被重用兵多,那么直接回調(diào)onLoadingCancelled尖啡,負(fù)責(zé)顯示圖片,移除cacheKeysForImageAwares中當(dāng)前ImageAware的數(shù)據(jù)剩膘,回調(diào)onLoadingComplete衅斩。
2.4 isViewWasReused()
private boolean isViewWasReused() {
//獲取ImageAware最新的cacheKey
String currentCacheKey = engine.getLoadingUriForView(imageAware);
//如果當(dāng)前任務(wù)的cacheKey和最新的cacheKey不一致,說明ImageAware被重用了怠褐。
return !memoryCacheKey.equals(currentCacheKey);
}
如果當(dāng)前任務(wù)的cacheKey和最新的cacheKey不一致畏梆,說明ImageAware被重用了。
有內(nèi)存緩存的情況已經(jīng)分析結(jié)束奈懒。
3.沒有內(nèi)存緩存異步
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);
}
}
});
}
分發(fā)線程池taskDistributor根據(jù)是否有本地緩存來進(jìn)行處理:
- 有本地緩存磷杏,將任務(wù)交給緩存線程池
- 沒有本地緩存溜畅,將任務(wù)交給taskExecutor
3.2 LoadAndDisplayImageTask.run()
public void run() {
//如果線程被打斷,進(jìn)入休眠
//如果線程沒有被打斷或者被喚醒,且imageAware被GC回收极祸,或者imageAware被重用了慈格,那么返回true
if (waitIfPaused()) return;
//延時加載且imageAware被GC回收,或者imageAware被重用了贿肩,那么返回true
if (delayIfNeed()) return;
//根據(jù)當(dāng)前uri,獲取鎖
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
//加鎖
loadFromUriLock.lock();
Bitmap bmp;
try {
//如果ImageAware被回收了或者被重用了峦椰,直接拋出任務(wù)取消異常TaskCancelledException
checkTaskNotActual();
//獲取內(nèi)存緩存(有可能圖片被其他線程加載過)
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
//沒有內(nèi)存緩存
//嘗試從本地緩存或者本地或者網(wǎng)絡(luò)等等源加載圖片
bmp = tryLoadBitmap();
//失敗的onLoadingFailed監(jiān)聽已經(jīng)被回調(diào)龄寞,這邊直接返回
if (bmp == null) return; // listener callback already was fired
//處理圖片前汰规,檢查ImageAware被回收了,或者被重用了物邑,或者線程被打斷了
checkTaskNotActual();
checkTaskInterrupted();
//內(nèi)存緩存前的預(yù)處理
if (options.shouldPreProcess()) {
bmp = options.getPreProcessor().process(bmp);
}
//添加到內(nèi)存緩存中
if (bmp != null && options.isCacheInMemory()) {
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {
//有內(nèi)存緩存
loadedFrom = LoadedFrom.MEMORY_CACHE;
}
//內(nèi)存緩存后的處理
if (bmp != null && options.shouldPostProcess()) {
bmp = options.getPostProcessor().process(bmp);
}
////處理圖片后溜哮,檢查ImageAware被回收了,或者被重用了色解,或者線程被打斷了
checkTaskNotActual();
checkTaskInterrupted();
} catch (TaskCancelledException e) {
//異步且線程不是被打斷的茂嗓,執(zhí)行onLoadingCancelled
fireCancelEvent();
return;
} finally {
//釋放鎖
loadFromUriLock.unlock();
}
//創(chuàng)建DisplayBitmapTask任務(wù)
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
//執(zhí)行
runTask(displayBitmapTask, syncLoading, handler, engine);
}
代碼注釋很清晰的,來個簡單的流程總結(jié):
- 1.imageAware被GC回收科阎,或者imageAware被重用了述吸,直接返回true
- 2.加鎖
- 3.獲取內(nèi)存緩存
有緩存:
- 4.后期的處理
- 5.釋放鎖
- 6.創(chuàng)建DisplayBitmapTask任務(wù),并執(zhí)行
沒有緩存
- 4.從本地緩存中獲取,如果沒有從網(wǎng)絡(luò)蝌矛,sd卡等中獲取圖片
- 5.預(yù)處理
- 6.添加到內(nèi)存緩存中
- 7.后期的處理
- 8.釋放鎖
- 9.創(chuàng)建DisplayBitmapTask任務(wù)道批,并執(zhí)行
下面分析核心的方法:
3.2.1 waitIfPaused() && delayIfNeed()
3.2.1.1 waitIfPaused()
/**
* 如果線程被打斷(比如:滾動listview),線程進(jìn)入wait,并釋放鎖
* 如果線程沒有被打斷或者被喚醒,你們返回isTaskNotActual()
* isTaskNotActual --- >如果imageAware被GC回收入撒,或者imageAware被重用了隆豹,那么返回true
*
* @return
*/
private boolean waitIfPaused() {
//獲取是否暫停狀態(tài)
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();
}
- 如果當(dāng)前的狀態(tài)為暫停(pasue = true),那么進(jìn)入休眠茅逮,等待被喚醒
- 如果沒有暫土模或者被喚醒,那么返回isTaskNotActual()結(jié)果
上面代碼有兩個點(diǎn)需要注意:1.何時暫停献雅,何時被喚醒碉考。 2.isTaskNotActual()執(zhí)行
1.何時暫停,何時被喚醒
我們都知道當(dāng)ListView滑動的時候挺身,ImageLoader會暫停圖片加載豆励,停止滑動時,繼續(xù)加載圖片瞒渠,這就是暫停和喚醒的時機(jī):
PauseOnScrollListener.onScrollStateChanged()
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
//恢復(fù)加載(喚醒)
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) {
//喚醒所有任務(wù)
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是否被重用,來顯示其他圖片
* 以開始執(zhí)行時窍箍,存放在cacheKeysForImageAwares中的cacheKey為最新的key
* 如果當(dāng)前的cachekey不相同串纺,則停止舍棄當(dāng)前的加載任務(wù)
*
* @return true--重用
*/
private boolean isViewReused() {
//獲取最新的cachekey
String currentCacheKey = engine.getLoadingUriForView(imageAware);
//如果和當(dāng)前cachekey不一致,說明imageAware被重用了
boolean imageAwareWasReused = !memoryCacheKey.equals(currentCacheKey);
if (imageAwareWasReused) {
return true;
}
return false;
}
waitIfPaused()總結(jié):
- 如果暫停加載椰棘,進(jìn)入休眠等待被喚醒
- 如果沒有暫停纺棺,或者被喚醒,則ImageAware被重用或者imageAware被GC回收邪狞,返回true,直接結(jié)束
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總結(jié):
- 如果沒有延時加載祷蝌,返回false
- 如果延時加載,進(jìn)入睡眠
- 線程被打斷結(jié)束睡眠 帆卓,返回true
- 正常結(jié)束睡眠巨朦,則ImageAware被重用或者imageAware被GC回收,返回true,直接結(jié)束
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總結(jié):
- 如果ImageAware被GC回收或者ImageAware被重用,拋出TaskCancelledException異常
- 拋出異常的處理將在后面進(jìn)行講解
//如果線程被打斷吁津,拋出TaskCancelledException
private void checkTaskInterrupted() throws TaskCancelledException {
if (isTaskInterrupted()) {
throw new TaskCancelledException();
}
}
//如果線程被打斷棚蓄,返回true
private boolean isTaskInterrupted() {
if (Thread.interrupted()) {
return true;
}
return false;
}
checkTaskInterrupted總結(jié):
- 如果線程被打斷,拋出TaskCancelledException異常
- 拋出異常的處理將在后面進(jìn)行講解
3.2.3 TaskCancelledException 異常處理
private void fireCancelEvent() {
//同步或者線程被打斷,直接返回
if (syncLoading || isTaskInterrupted()) return;
Runnable r = new Runnable() {
@Override
public void run() {
listener.onLoadingCancelled(uri, imageAware.getWrappedView());
}
};
//執(zhí)行onLoadingCancelled
runTask(r, false, handler, engine);
}
如果是同步或者線程被打斷梭依,直接返回挣柬,否則在指定線程中回調(diào)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;
//如果開啟本地緩存則調(diào)用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();
//如果上面已經(jīng)獲取了圖片,則這里的uri為本地uri
//如果沒有睛挚,下面將從network,asset,content,file,drawable中獲取圖片
//內(nèi)存保存的是根據(jù)ImageView大小邪蛔、scaletype、方向處理過得圖片
bitmap = decodeImage(imageUriForDecoding);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
//如果是異步且線程沒有被打斷扎狱,imageaware沒有被回收和重用
// 那么獲取圖片失敗侧到,顯示失敗后的圖片,并且調(diào)用onLoadingFailed
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
} catch (IllegalStateException e) {
//如果是異步且線程沒有被打斷淤击,imageaware沒有被回收和重用
// 那么獲取圖片失敗匠抗,顯示失敗后的圖片,并且調(diào)用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) {
//將原圖轉(zhuǎn)化為設(shè)定的最大本地緩存圖片大卸喔獭(默認(rèn)為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:
//網(wǎng)絡(luò)圖片通過HttpURLConnection實(shí)現(xiàn)的
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總結(jié):
- 根據(jù)uri協(xié)議的不同楣责,從不同的源加載圖片
tryCacheImageOnDisk總結(jié):
- 獲取圖片竣灌,保存到本地緩存
2.decodeImage
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
//targetSize -- imageview大小或者內(nèi)存緩存的最大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 {
//確定圖片尺寸 和 旋轉(zhuǎn)角度 ,生成ImageFileInfo
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
//重置流游標(biāo)
imageStream = resetStream(imageStream, decodingInfo);
//BitmapFactory.options
//準(zhǔn)備decode的opions
//decodingOptions.inSampleSize = scale; 對bitmap進(jìn)行壓縮
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
} else {
//對Bitmap進(jìn)行縮放秆麸,翻轉(zhuǎn)和旋轉(zhuǎn)等操作
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
getImageStream():
//調(diào)用BaseImageDownloader.getStream()
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
defineImageSizeAndRotation()
//獲取圖片的size 和 旋轉(zhuǎn)角度(EXIF信息)
protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo) {
//BitmapFactory.options
Options options = new Options();
//不加載bitmap數(shù)據(jù),只返回bitmap信息
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(imageStream, null, options);
//EXIF信息初嘹,是可交換圖像文件的縮寫,是專門為數(shù)碼相機(jī)的照片設(shè)定的沮趣,可以記錄數(shù)碼照片的屬性信息和拍攝數(shù)據(jù)屯烦。
ExifInfo exif;
String imageUri = decodingInfo.getImageUri();
//考慮 exif 且 文件能夠定義 exif(JPEG文件)
//附加:EXIF可以附加于JPEG、TIFF兔毒、RIFF等文件之中
if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
//根據(jù)文件exif信息漫贞,獲取旋轉(zhuǎn)角度,保存到ExifInfo中
exif = defineExifOrientation(imageUri);
} else {
//創(chuàng)建對象(角度為0育叁,即不旋轉(zhuǎn))
exif = new ExifInfo();
}
//將 圖片的寬高,旋轉(zhuǎn)角度 芍殖,exif 構(gòu)建ImageFileInfo對象
return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
}
resetStream()
//重置游標(biāo)
protected InputStream resetStream(InputStream imageStream, ImageDecodingInfo decodingInfo) throws IOException {
if (imageStream.markSupported()) {
try {
//重置游標(biāo)
imageStream.reset();
return imageStream;
} catch (IOException ignored) {
}
}
IoUtils.closeSilently(imageStream);
return getImageStream(decodingInfo);
}
prepareDecodingOptions():
準(zhǔn)備decode的opions,根據(jù)ImageScaleType,imagesize計(jì)算decodingOptions.inSampleSize 的值豪嗽。
considerExactScaleAndOrientatiton()
//對Bitmap進(jìn)行縮放,翻轉(zhuǎn)和旋轉(zhuǎn)等操作
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
//翻轉(zhuǎn)bitmap
if (flipHorizontal) {
m.postScale(-1, 1);
if (loggingEnabled) L.d(LOG_FLIP_IMAGE, decodingInfo.getImageKey());
}
// Rotate bitmap if need
//旋轉(zhuǎn)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總結(jié):
- 獲取bitmap,根據(jù)ImageView大小、scaletype龟梦、方向隐锭,旋轉(zhuǎn)角度處理圖片,最后返回
3.fireFailEvent
/**
* 如果是異步且線程沒有被打斷,imageaware沒有被回收和重用
那么獲取圖片失敗计贰,顯示失敗后的圖片钦睡,并且調(diào)用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() {
//設(shè)置失敗后的圖片
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 執(zhí)行DisplayBitmapTask
同上,2.2-2.3
上一個我自己畫的流程圖(請忽略試用版水印幾個大字):
imageloader框架的源碼解析就講到這里躁倒,有興趣的朋友可以去研究下內(nèi)存緩存荞怒,本地緩存的實(shí)現(xiàn)等等。