此處拋開(kāi)線(xiàn)程調(diào)度、圖片處理等,僅拿出圖片加載流程概要锦援。
ImageLoader
單例模式的請(qǐng)求入口。
每一個(gè)請(qǐng)求都將進(jìn)入到ImageLoader
的displayImage()
方法中:
主要工作:
- 負(fù)責(zé)請(qǐng)求的一些初始化
- 首先嘗試從內(nèi)存中獲取剥悟,如果成功灵寺,則直接開(kāi)啟后續(xù)的圖片處理、展示等任務(wù)区岗。如果失敗略板,則將請(qǐng)求信息包裝到
ImageLoadingInfo
中,包括圖片URL慈缔、ImageView叮称、圖片的大小、內(nèi)存緩存key胀糜、監(jiān)聽(tīng)器等等颅拦。然后再創(chuàng)建一個(gè)圖片加載的任務(wù)LoadAndDisplayImageTask
蒂誉,此任務(wù)將之后被執(zhí)行(視同步還是異步請(qǐng)求而定)教藻,任務(wù)完成后,將會(huì)把圖片緩存到內(nèi)存中右锨。
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;
}
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
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);
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 {
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);
}
}
}
LoadAndDisplayImageTask
一個(gè)實(shí)現(xiàn)了Runnable
接口的圖片加載任務(wù)括堤,負(fù)責(zé)圖片的獲取、緩存到不同位置(磁盤(pán)绍移、內(nèi)存等)悄窃、解碼、處理蹂窖、進(jìn)度監(jiān)聽(tīng)等轧抗。
當(dāng)此任務(wù)被執(zhí)行時(shí),將首先執(zhí)行其 run()
方法:
@Override
public void run() {
if (waitIfPaused()) return;
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 {
checkTaskNotActual();
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
bmp = tryLoadBitmap();
if (bmp == null) return; // listener callback already was fired
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);
}
}
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);
}
}
checkTaskNotActual();
checkTaskInterrupted();
} catch (TaskCancelledException e) {
fireCancelEvent();
return;
} finally {
loadFromUriLock.unlock();
}
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
- 首先再次判斷此圖片是否已經(jīng)存在于內(nèi)存中(因有可能其他任務(wù)也負(fù)責(zé)加載此圖片)
- 如果沒(méi)有或者已經(jīng)被GC回收瞬测,則
tryLoadBitmap()
嘗試從磁盤(pán)或者網(wǎng)絡(luò)中獲取横媚,如果加載失敗,直接return
- 加載成功月趟,則對(duì)圖片做后續(xù)處理灯蝴。
下面是從磁盤(pán)或者網(wǎng)絡(luò)加載圖片的過(guò)程:
-
tryLoadBitmap()
會(huì)線(xiàn)判斷圖片是否已經(jīng)緩存在磁盤(pán)上,如果有孝宗,則調(diào)用decode()
方法進(jìn)行解碼穷躁,得到Bitmap
實(shí)例。 - 如果解碼失敗因妇,就會(huì)調(diào)用
tryCacheImageOnDisk ()
去網(wǎng)絡(luò)上獲取圖片了问潭,加載完成后會(huì)先緩存圖片到磁盤(pán)上猿诸。 - 完成后將再次調(diào)用
decode()
方法進(jìn)行解碼,得到Bitmap
實(shí)例睦授。 - 如果
Bitmap
還是null
两芳,這時(shí)將通知調(diào)用者,圖片加載失敗了去枷。
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;
checkTaskNotActual();
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;
String imageUriForDecoding = uri;
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
} 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;
}
下面是tryCacheImageOnDisk ()
:
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;
}
在這里怖辆,downloadImage()
方法才是真正的開(kāi)始進(jìn)入圖片下載過(guò)程,繼續(xù)跟進(jìn):
private boolean downloadImage() throws IOException {
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);
}
}
}
這里獲取getDownloader ()
到了下載器ImageDownloader
接口的具體實(shí)現(xiàn)删顶,這個(gè)UIL它提供了默認(rèn)的實(shí)現(xiàn)BaseImageDownloader
竖螃,我們也可以自己實(shí)現(xiàn)一個(gè),在UIL的初始配置時(shí)傳入逗余。看看默認(rèn)下載器的getStream()
:
BaseImageDownloader
@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);
}
}
在這里就可以看到,這里根據(jù)圖片的URL去到了不同的來(lái)源去獲取圖片
跟進(jìn)Scheme
看看,它是一個(gè)枚舉類(lèi)型酬核,
public enum Scheme {
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
...
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);
}
}
根據(jù)uri的開(kāi)頭嫡意,分別從網(wǎng)絡(luò)蔬螟、SD卡汽畴、app內(nèi)部的文件系統(tǒng)去加載了圖片菠齿。
先看看UIL是如何從網(wǎng)絡(luò)上加載圖片的:
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
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;
}
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());
}
看了才知道,它的圖片加載核心代碼也很簡(jiǎn)單,就是使用了HttpURLConnection
去獲取了圖片字節(jié)流是尔。
而最后返回的ContentLengthInputStream
其實(shí)就是一個(gè)使用裝飾者模式包裝了的InputStream
殉了,它僅僅是重寫(xiě)了InputStream
的available ()
方法
@Override
public int available() {
return length;
}
這個(gè)方法干嘛的呢隔箍?其實(shí)就是提供本輸入流的字節(jié)量大小奶稠。這個(gè)最后用在了圖片下載的進(jìn)度監(jiān)聽(tīng)上竹握。
跑遠(yuǎn)了,獲取到圖片的輸入流之后战得,繼續(xù)回到downloadImage()
方法常侦,可以看到它的返回是:
return configuration.diskCache.save(uri, is, this);
這就是在從網(wǎng)絡(luò)加載圖片后坡倔,將圖片緩存到磁盤(pán)了瘩缆。這里傳入了輸入流和this
佃蚜,這里的diskCache是UIL初始配置的時(shí)候初始化的庸娱,它也有個(gè)默認(rèn)的磁盤(pán)緩存:
BaseDiskCache
@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
File imageFile = getFile(imageUri);
File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
boolean loaded = false;
try {
OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
try {
loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
} finally {
IoUtils.closeSilently(os);
}
} finally {
if (loaded && !tmpFile.renameTo(imageFile)) {
loaded = false;
}
if (!loaded) {
tmpFile.delete();
}
}
return loaded;
}
可以看到這里將輸入流轉(zhuǎn)為輸出流,保存文件到了本地磁盤(pán)上谐算。
這里傳入的參數(shù)里涌韩, 有IoUtils.CopyListener
,這個(gè)就是圖片下載進(jìn)度的監(jiān)聽(tīng)器了氯夷,看到這里臣樱,就該想到,真正實(shí)現(xiàn)進(jìn)度監(jiān)聽(tīng)功能的代碼腮考,就要到了雇毫。
跟進(jìn)IoUtils.copyStream
,看到:
public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener, int bufferSize)
throws IOException {
int current = 0;
int total = is.available();
if (total <= 0) {
total = DEFAULT_IMAGE_TOTAL_SIZE;
}
final byte[] bytes = new byte[bufferSize];
int count;
if (shouldStopLoading(listener, current, total)) return false;
while ((count = is.read(bytes, 0, bufferSize)) != -1) {
os.write(bytes, 0, count);
current += count;
if (shouldStopLoading(listener, current, total)) return false;
}
os.flush();
return true;
}
這里is.available()
就是獲取到了之前getStreamFromNetwork()
所創(chuàng)建的InputStream
的裝飾者中的文件字節(jié)流大小踩蔚。我們的目標(biāo)是找到進(jìn)度回調(diào)的最終調(diào)用鏈源點(diǎn)棚放,于是緊盯著listener
,于是跟進(jìn)了方法shouldStopLoading
:
private static boolean shouldStopLoading(CopyListener listener, int current, int total) {
if (listener != null) {
boolean shouldContinue = listener.onBytesCopied(current, total);
if (!shouldContinue) {
if (100 * current / total < CONTINUE_LOADING_PERCENTAGE) {
return true; // if loaded more than 75% then continue loading anyway
}
}
}
return false;
}
就是這里了馅闽!進(jìn)度監(jiān)聽(tīng)就是這么實(shí)現(xiàn)的飘蚯。
下面的while()
循環(huán)里就是將圖片數(shù)據(jù)流寫(xiě)入到輸出流了。圖片最終保存到了磁盤(pán)上福也。