前言
ImageLoader
是
android
使用中出現比較早(PS:即的剛接觸安卓項目的時候就用的是這個圖檔加載圖,算算已經快5年了),使用最多的一個開源圖檔加載庫了。随着
glide
,
fresco
和
picasso
等圖檔加載的庫出現,
ImageLoader
使用變得越來越少。最近在看其他圖檔加載庫的源碼,順便補補之前錯過的一些事情。
代碼倉庫位址:Android-Universal-Image-Loader
文章目錄
- 前言
- ImageLoader
- Config配置
- 視圖
- 加載回調
- 圖檔展示
- 位圖處理
- 記憶體緩存
- 圖檔解碼器
- 磁盤緩存
- 網絡下載下傳
- OkHttp網絡下載下傳
- ImageLoader
- 圖檔加載引擎
- 處理和展示圖檔任務
- 加載和展示圖檔任務
- run(記憶體緩存)
- 加載圖檔
- 緩存圖檔到磁盤
- 其他
ImageLoader
這個是
ImageLoader
的架構,
ImageLader
圖檔加載庫的主要組成部分都包括在其中。
下邊這幅圖對應的是,組成上面架構的每個部分的對應的類實作:
-
:為ImageLoader
下載下傳和展示圖檔的單例;ImageView
-
: 圖檔展示的配置項(加載中、空url、加載失敗預設圖等);DisplayImageOptions
-
:ImageLoaderConfiguration
的配置項;ImageLoader
-
:表示圖像感覺視圖,該視圖提供了圖像處理和顯示所需的所有屬性和行為;ImageAware
-
:監聽圖檔加載進度,開始、失敗、成功、取消;ImageLoadingListener
-
:執行圖檔下載下傳和展現任務;ImageLoaderEngine
-
:展現BitmapDisplayer
在Bitmap
上的時候可以修改這個ImageView
或添加展示的動畫效果;Bitmap
-
:可以處理原始的BitmapProcessor
;Bitmap
-
:MemoryCache
記憶體緩存接口;Bitmap
-
:磁盤緩存;DiskCache
-
:根據ImageDecoder
資訊得到圖檔并根據參數将其轉換為 Bitmap。ImageDecodingInfo
-
:通過ImageDownloader
擷取圖檔;URI
-
:展示圖檔并進行回調;DisplayBitmapTask
-
:處理圖檔和展現圖檔的任務,用于加載記憶體緩存中的圖檔;ProcessAndDisplayImageTask
-
:處理加載和顯示圖像的任務,用于從Internet或檔案系統加載圖像為LoadAndDisplayImagTask
;Bitmap
Config配置
初始化配置參數,參數
configuration
為
ImageLoader
的配置資訊,包括圖檔最大尺寸、任務線程池、磁盤緩存、下載下傳器、解碼器等等。
ImageLoaderConfiguration{
final Resources resources;//上下文環境中的resource
final int maxImageWidthForMemoryCache;//記憶體緩存最大寬度
final int maxImageHeightForMemoryCache;//記憶體緩存最大高度
final int maxImageWidthForDiskCache;//磁盤緩存最大寬度
final int maxImageHeightForDiskCache;//磁盤緩存最大高度
//在将圖像儲存到磁盤緩存之前先對其進行調整大小/壓縮處理
final BitmapProcessor processorForDiskCache;
final Executor taskExecutor;//自定義圖檔加載和展現的線程池
final Executor taskExecutorForCachedImages;//自定義展現在磁盤上的圖檔的線程池
final boolean customExecutor;//是否自定義下載下傳的線程池
final boolean customExecutorForCachedImages;//是否自定義緩存圖檔的線程池
//預設核心線程數和線程池容量為3
final int threadPoolSize;
//預設的線程優先級低兩級
final int threadPriority;
//LIFO,FIFO;預設為先進先出FIFO
final QueueProcessingType tasksProcessingType;
//記憶體緩存,預設為MemoryClass的八分之一,3.0之後為LargeMemoryClass的八分之一
//如果開啟denyCacheImageMultipleSizesInMemory,那麼緩存為FuzzyKeyMemoryCache執行個體,隻判斷圖檔位址不判斷大小,如果相同那麼重新整理緩存
final MemoryCache memoryCache;
//LruDiskCache,大小預設存儲為Long.MAX_VALUE,預設最大數量為Long.MAX_VALUE;
final DiskCache diskCache;
//通過URI從網絡或檔案系統或應用程式資源中檢索圖像,預設為HttpURLConnection進行網絡下載下傳
//提供了imageDownloader方法可以自定義,比如使用HttpClient或者OkHttp
final ImageDownloader downloader;
//将圖像解碼為Bitmap,将其縮放到所需大小
final ImageDecoder decoder;
//包含圖像顯示選項(預設圖設定以及其他預設選項)
final DisplayImageOptions defaultDisplayImageOptions;
//網絡禁止下載下傳器,一般不直接應用
final ImageDownloader networkDeniedDownloader;
//在慢速網絡上處理下載下傳
final ImageDownloader slowNetworkDownloader;
}
還有一個對于某個
ImageView
進行展示設定的
DisplayImageOptions
,配置圖檔顯示的配置項。比如加載前、加載中、加載失敗應該顯示的占位圖檔,圖檔是否需要在磁盤緩存,是否需要在記憶體緩存等。
視圖
講視圖主要是想讓
ImageView
與
ImageLoader
聯系在一起來,
ImageLoader
通過
ImageAware
接口實作圖檔在視圖上的展現。
public interface ImageAware {
int getWidth();
int getHeight();
ViewScaleType getScaleType();
View getWrappedView();
boolean isCollected();
int getId();
boolean setImageDrawable(Drawable drawable);
boolean setImageBitmap(Bitmap bitmap);
}
- ImageAware->ViewAware
- ImageAware->ViewAware->ImageViewAware
- ImageAware->NonViewAware
其中
ViewAware
是抽象類,是以
ImageAware
隻有
ImageViewAware
和
NonViewAware
兩個實作類。
NonViewAware
提供處理原始圖像所需的資訊,但不顯示圖像。當使用者隻需要加載和解碼圖像的時候可以使用它。
加載回調
主要進行圖檔加載過程中的事件監聽。
public interface ImageLoadingListener {
//開始加載
void onLoadingStarted(String imageUri, View view);
//加載失敗
void onLoadingFailed(String imageUri, View view, FailReason failReason);
//加載完成
void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);
//取消加載
void onLoadingCancelled(String imageUri, View view);
}
圖檔展示
在
ImageAware
中顯示
bitmap
對象的接口。可在實作中對
bitmap
做一些額外處理,比如加圓角、動畫效果。
預設的
BitmapDisplay
是
SimpleBitmapDisplayer
僅僅實作了加載圖檔的功能,
ImageLoader
還提供了
CircleBitmapDisplayer
、
FadeInBitmapDisplayer
和
RoundedBitmapDisplayer
等其他的實作。
public interface BitmapDisplayer {
void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
}
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
}
位圖處理
圖檔處理接口。可用于對圖檔預處理(
Pre-process
)和後處理(
Post-process
),這兩個處理器的配置都是在
DisplayImageOptions
進行設定。其中預處理是在圖檔擷取完緩存之前處理,後端處理是指在展示前的處理。
public interface BitmapProcessor {
Bitmap process(Bitmap bitmap);
}
記憶體緩存
記憶體緩存的是
Bitmap
,預設的緩存容器是
LruMemoryCache
。記憶體緩存的
Bitmap
都是通過資料流解碼生成的。
public interface MemoryCache {
boolean put(String key, Bitmap value);
Bitmap get(String key);
Bitmap remove(String key);
Collection<String> keys();
void clear();
}
public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
if (memoryCacheSize == 0) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
if (hasHoneycomb() && isLargeHeap(context)) {
memoryClass = getLargeMemoryClass(am);
}
memoryCacheSize = 1024 * 1024 * memoryClass / 8;
}
return new LruMemoryCache(memoryCacheSize);
}
LruMemoryCache
是區分size的,如果
ImageLoaderConfiguration
設定
denyCacheImageMultipleSizesInMemory
那麼緩存為
FuzzyKeyMemoryCache
執行個體,隻判斷圖檔位址不判斷大小,如果相同那麼重新整理緩存。
FuzzyKeyMemoryCache
隻是重寫了
MemoryCache
的
put
方法。
圖檔解碼器
根據
ImageDecodingInfo
資訊得到圖檔并根據參數将其轉換為
Bitmap
。
public interface ImageDecoder {
Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
}
public static ImageDecoder createImageDecoder(boolean loggingEnabled) {
return new BaseImageDecoder(loggingEnabled);
}
BaseImageDecoder
是
ImageLoaderConfiguration
預設的解碼器。
public class BaseImageDecoder implements ImageDecoder {
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
//通過ImageDecodingInfo中的資訊擷取資料流,圖檔下載下傳器部分會講怎麼擷取資料流
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);
//生成控制Bitmap進行采樣的Option
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
//将輸入流解碼為位圖
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
//對Bitmmap進行縮放和旋轉
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
/****部分代碼省略***/
}
在解碼的過程中我們之是以要重置遊标,是因為我們在讀頭資訊的時候已經讀出了部分資料,是以這裡要重置遊标得到完整的圖檔資料。
磁盤緩存
本地圖檔緩存,可向本地磁盤緩存儲存圖檔或從本地磁盤讀取圖檔。
LruDiskCache
是
ImageLoaderConfiguration
預設的磁盤緩存容器。這次緩存的圖檔檔案都是通過
InputStream
儲存在磁盤上的,實作是通過調用
save
方法。
public interface DiskCache {
File getDirectory();
File get(String imageUri);
boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;
boolean save(String imageUri, Bitmap bitmap) throws IOException;
boolean remove(String imageUri);
void close();
void clear();
}
public static DiskCache createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator,
long diskCacheSize, int diskCacheFileCount) {
File reserveCacheDir = createReserveDiskCacheDir(context);
if (diskCacheSize > 0 || diskCacheFileCount > 0) {
///Android/data/[app_package_name]/cache/uil-images
File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);
try {
//緩存目錄,緩存大小,緩存數量,緩存檔案名生成器都不能為空
return new LruDiskCache(individualCacheDir, reserveCacheDir, diskCacheFileNameGenerator, diskCacheSize,
diskCacheFileCount);
} catch (IOException e) {
L.e(e);
// continue and create unlimited cache
}
}
File cacheDir = StorageUtils.getCacheDirectory(context);
//UnlimitedDiskCache大小沒有限制
return new UnlimitedDiskCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator);
}
網絡下載下傳
擷取
Uri
對應的
Stream
,
extra
為輔助的下載下傳器,可以通過
DisplayImageOptions
得到
extraForDownloader
。下載下傳主要有
http
、
https
、
file
、
content
、
assets
和
drawable
。
public interface ImageDownloader {
InputStream getStream(String imageUri, Object extra) throws IOException;
/** Represents supported schemes(protocols) of URI. Provides convenient methods for work with schemes and URIs. */
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 + "://";
}
/***部分代碼省略***/
}
}
BaseImageDownloader
為預設的下載下傳器:内部通過下載下傳資源的類型的不同有着不同的實作。
public class BaseImageDownloader implements ImageDownloader {
@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);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
/***部分代碼省略***/
}
看一個從網絡請求中擷取
Stream
的實作(
ImageLoader
預設的網絡下載下傳):
public class BaseImageDownloader implements ImageDownloader {
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
//根據imageUri,建立HttpURLConnection對象
HttpURLConnection conn = createConnection(imageUri, extra);
int redirectCount = 0;
//最多重定向請求5次
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;
}
//如果responseCode不是200那麼關閉請求抛出IO異常
if (!shouldBeProcessed(conn)) {
IoUtils.closeSilently(imageStream);
throw new IOException("Image request failed with response code " + conn.getResponseCode());
}
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
}
OkHttp網絡下載下傳
隻需要在進行
ImageLoader
配置的時候調用
ImageLoaderConfiguration.Builder
的
imageDownloader
方法進行設定。
public class OkHttpImageDownloader extends BaseImageDownloader {
private OkHttpClient client;
public OkHttpImageDownloader(Context context, OkHttpClient client) {
super(context);
this.client = client;
}
@Override
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
Request request = new Request.Builder().url(imageUri).build();
ResponseBody responseBody = client.newCall(request).execute().body();
InputStream inputStream = responseBody.byteStream();
int contentLength = (int) responseBody.contentLength();
return new ContentLengthInputStream(inputStream, contentLength);
}
}
ImageLoader
講完了組成的
ImageLoader
的一整套圖檔加載流程的沒個部分:網絡下載下傳、磁盤緩存、資料解碼、記憶體緩存、位圖處理、圖檔展示和業務回調。下面我們看看
ImageLoader
是怎麼将這些部分是怎麼串在一起的。
使用雙重校驗鎖(DCL:double-checked locking)實作單例操作 Java版的7種單例模式。
public class ImageLoader {
private ImageLoaderConfiguration configuration;//圖檔加載配置資訊
private ImageLoaderEngine engine;//圖檔加載引擎
private ImageLoadingListener defaultListener = new SimpleImageLoadingListener();//預設的回調監聽
private volatile static ImageLoader instance;//單例
/** Returns singleton class instance */
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
//初始化方法
public synchronized void init(ImageLoaderConfiguration configuration) {
if (configuration == null) {
throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
}
if (this.configuration == null) {
L.d(LOG_INIT_CONFIG);
engine = new ImageLoaderEngine(configuration);
this.configuration = configuration;
} else {
L.w(WARNING_RE_INIT_CONFIG);
}
}
/***其他代碼省略***/
}
上面代碼是
ImageLoader
的構造初始化方法,接下分析它加載圖檔時候的調用:
public class ImageLoader {
public void displayImage(String uri, ImageView imageView) {
displayImage(uri, new ImageViewAware(imageView), null, null, null);
}
//最終加載圖檔的方法
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);//取消對于目前imageAware的展示任務
listener.onLoadingStarted(uri, imageAware.getWrappedView());//回調展示開始
//展示配置中有處理為空的url的預設圖
if (options.shouldShowImageForEmptyUri()) {
//給imageAware設定這個預設圖
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
imageAware.setImageDrawable(null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);//回調展示結束
return;
}
//擷取目前需要下載下傳的圖檔的size
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
//擷取記憶體緩存的key(url_width_height)
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
//添加到執行引擎cacheKeysForImageAwares的容器中
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());//回調展示開始
//從記憶體中擷取緩存的memoryCacheKey對應的bitmap
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
//bitmap不為空,而且沒有被回收
if (bmp != null && !bmp.isRecycled()) {
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
//如果需要展示加載的進度,預設是不設定BitmapProcessor處理器的
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));
//如果需要同步加載
if (options.isSyncLoading()) {
displayTask.run();//直接進行展現任務
} else {
engine.submit(displayTask);//送出任務到加載引擎中
}
} else {
//從記憶體中加載bitmap設定給imageAware,
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 imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
//構造加載展示圖檔的任務
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
//如果需要同步加載
if (options.isSyncLoading()) {
displayTask.run();//直接進行展現任務
} else {
engine.submit(displayTask);//送出任務到加載引擎中
}
}
}
/***其他代碼省略***/
}
Imageloader
圖檔加載流程叙述:
- 校驗配置;
- 指派預設值(回調監聽、圖檔展現配置);
判斷下載下傳位址為空;
3.1. 取消目前imageAware的圖檔展示任務;
3.2. 如果圖檔展示配置有url為空的預設處理圖那麼加載預設圖;
- 擷取目前需要加載圖的size;
擷取緩存的key
5.1. 根據key從記憶體緩存中擷取bitmap,且bitmap有效;
5.1.1. 如果需要展現加載進度,那麼構造處理圖檔展示任務(ProcessAndDisplayImageTask)并執行(如果展現需要同步那麼直接展示,否則任務送出到線程池);
5.1.2. 否則直接加載bitmap給目前的imageAware;
5.2. 如果需要展現加載進度,那麼擷取圖檔展示配置中的加載狀态資源進行展示,準備下一步加載真實圖檔資源;
5.2.1. 構造加載展示圖檔任務(LoadAndDisplayImageTask)并執行(如果展現需要同步那麼直接展示,否則任務送出到線程池);
圖檔加載引擎
雖然叫做圖檔加載引起,但其實它僅僅隻是一個任務分發處理器,負責分發
LoadAndDisplayImageTask
和
ProcessAndDisplayImageTask
給具體的線程池去執行,以及任務的暫停等操作。
class ImageLoaderEngine {
final ImageLoaderConfiguration configuration;//圖檔加載配置資訊
private Executor taskExecutor;//configuration.taskExecutor
private Executor taskExecutorForCachedImages;//configuration.taskExecutorForCachedImages
private Executor taskDistributor;//配置設定任務的線程池為newCachedThreadPool
//imageview的hashcode和下載下傳的key(url_width_height)
private final Map<Integer, String> cacheKeysForImageAwares = Collections.synchronizedMap(new HashMap<Integer, String>());
private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();
private final AtomicBoolean paused = new AtomicBoolean(false);
private final AtomicBoolean networkDenied = new AtomicBoolean(false);
private final AtomicBoolean slowNetwork = new AtomicBoolean(false);
private final Object pauseLock = new Object();
ImageLoaderEngine(ImageLoaderConfiguration configuration) {
this.configuration = configuration;
taskExecutor = configuration.taskExecutor;
taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
}
/***其他代碼省略***/
}
任務送出處理,主要做了不同類型的任務分發給對應的任務執行的線程池:
class ImageLoaderEngine {
/** Submits task to execution pool */
//執行從磁盤擷取和網絡上加載圖檔的任務
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.execute(task);
}
}
});
}
/** Submits task to execution pool */
//支援從緩存中加載圖檔的任務
void submit(ProcessAndDisplayImageTask task) {
initExecutorsIfNeed();
taskExecutorForCachedImages.execute(task);
}
//任務線程池是否關閉,關閉則重新建立
private void initExecutorsIfNeed() {
if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
taskExecutor = createTaskExecutor();
}
if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
.isShutdown()) {
taskExecutorForCachedImages = createTaskExecutor();
}
}
//建立任務線程池
private Executor createTaskExecutor() {
return DefaultConfigurationFactory
.createExecutor(configuration.threadPoolSize, configuration.threadPriority,
configuration.tasksProcessingType);
}
/***其他代碼省略***/
}
從
ImageLoader
的
displayImage
方法實作和
ImageLoaderEngine
的任務分發可以看出來,
ImageLoader
主要有兩種類型的任務
ProcessAndDisplayImageTask
和
LoadAndDisplayImageTask
。
處理和展示圖檔任務
final class ProcessAndDisplayImageTask implements Runnable {
/***部分代碼省略***/
@Override
public void run() {
L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
//擷取圖檔展現配置中的圖檔處理器
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
//擷取處理過後的Biamtp
Bitmap processedBitmap = processor.process(bitmap);
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE);
//如果isSyncLoading那麼調用displayBitmapTask的run方法,否則如果handler不為空切換到主線程執行displayBitmapTask.run
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
}
加載和展示圖檔任務
先看
LoadAndDisplayImageTask.runTask
方法:
static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
if (sync) {//如果需要同步那麼在目前線程執行
r.run();
} else if (handler == null) {//handler為空切換線程到taskDistributor線程池中執行
engine.fireCallback(r);
} else {
handler.post(r);//切換到handler主線程執行
}
}
run(記憶體緩存)
final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
/***部分代碼省略***/
@Override
public void run() {
//如果ImageLoader暫停執行任務(ImageLoader.pause方法被調用),那麼目前線程進入等待被喚醒(ImageLoader.resume方法被調用);
//否則校驗目前任務是否有效(校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri)
if (waitIfPaused()) return;
//是否需要延遲加載(圖檔展示配置中如果delayBeforeLoading時間大于0)
否則校驗目前任務是否有效(校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri)
if (delayIfNeed()) return;
//擷取目前圖檔加載任務的鎖
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) {
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}
loadFromUriLock.lock();
Bitmap bmp;
try {
//校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
checkTaskNotActual();
//先從記憶體緩存中擷取對應的Bitmap
bmp = configuration.memoryCache.get(memoryCacheKey);
//如果bitmap被回收或者為空
if (bmp == null || bmp.isRecycled()) {
//嘗試加載Bitmap(磁盤、資源、網絡等)
bmp = tryLoadBitmap();
//加載失敗直接傳回
if (bmp == null) return; // listener callback already was fired
//校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
checkTaskNotActual();
//檢驗是否目前線程被打斷
checkTaskInterrupted();
//根據圖檔展示配置項是否要進行儲存前處理
if (options.shouldPreProcess()) {
L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPreProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
}
}
//是否需要對這個Bitmap進行記憶體緩存
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);
}
//根據圖檔展示配置項是否要進行展示前處理
if (bmp != null && options.shouldPostProcess()) {
L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPostProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
}
}
//校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
checkTaskNotActual();
//檢驗是否目前線程被打斷
checkTaskInterrupted();
} catch (TaskCancelledException e) {
//進行失敗處理
fireCancelEvent();
return;
} finally {
//釋放鎖
loadFromUriLock.unlock();
}
//執行展示圖檔任務此處和ProcessAndDisplayImageTask任務後的展示邏輯相同
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
/***部分代碼省略***/
}
任務是否有效:校驗目标
ImageAware
是否已經被回收,或者
ImageAware
需要加載的
uri
已經不是目前的
uri
(被取消或者被代替)。
- 校驗
是否暫停執行任務和目前的任務是否有效;
ImageLoader
- 是否需要進行延遲加載,延遲加載後校驗目前是否任務有效;
- 擷取目前圖檔加載任務的鎖進行上鎖;
- 校驗目前是否任務有效後開始進行
Bitmap
擷取;
4.1 先從記憶體緩存中擷取對應的
Bitmap
;
4.2 擷取
為空擷取已經被回收那麼嘗試加載
Bitmap
Bitmap
;
4.2.1
Bitmap
加載失敗直接傳回;
4.2.2 校驗目前是否任務有效;
4.2.3 檢驗是否目前線程被打斷;
4.2.4 根據圖檔展示配置項是否要進行儲存前處理;
4.2.5 是否需要對這個
Bitmap
進行記憶體緩存;
4.3 根據圖檔展示配置項是否要進行展示前處理
4.4 校驗目前是否任務有效;
4.5 檢驗是否目前線程被打斷;
- 釋放鎖;
- 執行展示圖檔任務;
加載圖檔
final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
/***部分代碼省略***/
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
File imageFile = configuration.diskCache.get(uri);
//從磁盤擷取存儲的圖檔
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;
//校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
checkTaskNotActual();
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));//進行圖檔解碼
}
//bitmap為空,或者長寬小于0重新進行資料擷取
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;
String imageUriForDecoding = uri;
//是否需要緩存在磁盤上,如果需要進行磁盤緩存
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
//校驗目标ImageAware是否已經被回收,或者ImageAware需要加載的uri已經不是目前的uri
checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding);//進行圖檔解碼
//bitmap為空,或者長寬小于0進行異常處理
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
} catch (){
/***異常處理省略***/
}
return bitmap;
}
/***部分代碼省略***/
}
緩存圖檔到磁盤
final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
/***部分代碼省略***/
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
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 {
//通過download擷取資料流
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 boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
boolean saved = false;
//擷取磁盤緩存的檔案
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);
//對圖像檔案做解碼
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);
}
}
//重新儲存,覆寫之前的uri對應的緩存檔案
if (bmp != null) {
saved = configuration.diskCache.save(uri, bmp);
bmp.recycle();
}
}
return saved;
}
/***部分代碼省略***/
}
其他
- 取消目前
對應的任務imageview
public void cancelDisplayTask(ImageView imageView) {
engine.cancelDisplayTaskFor(new ImageViewAware(imageView));
}
- 拒絕或允許
從網絡下載下傳圖像ImageLoader
public void denyNetworkDownloads(boolean denyNetworkDownloads) {
engine.denyNetworkDownloads(denyNetworkDownloads);
}
- 設定
是否使用ImageLoader
進行網絡下載下傳的選項FlushedInputStream
public void handleSlowNetwork(boolean handleSlowNetwork) {
engine.handleSlowNetwork(handleSlowNetwork);
}
- 暫停ImageLoader。在ImageLoader#resume恢複之前,不會執行所有新的“加載和顯示”任務。
- 已經運作的任務不會暫停。
public void pause() {
engine.pause();
}
- 恢複等待的“加載和顯示”任務
public void resume() {
engine.resume();
}
- 取消所有正在運作和計劃的顯示圖像任務
- 還可以繼續使用
ImageLoader
public void stop() {
engine.stop();
}
- 取消所有正在運作和計劃的顯示圖像任務
- 銷毀所有配置,重新使用ImageLoader需要進行初始化
public void destroy() {
if (configuration != null) L.d(LOG_DESTROY);
stop();
configuration.diskCache.close();
engine = null;
configuration = null;
}
- 為了更友好的使用者體驗,在清單滑動過程中可以暫停加載(調用
和pause
);resume
- RGB_565代替ARGB_8888,減少占用記憶體;
- 使用
将記憶體中的memoryCache(new WeakMemoryCache())
變為軟引用;Bitmap
文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦!!