任務流圖
任務流圖中的每一步都有自己的接口(在圖片底部)來負責這部分的任務。大部分接口(除了BitmapProcessor)都擁有默認的實現從左到右依次是BaseImageDowloader、UnlimitedDiscCache、BaseImageDecoder、LruMemoryCache、SimpleBitmapDisplayer
ImageDownloader
接口
public interface ImageDownloader {
InputStream getStream(String var1, Object var2) throws IOException;
//返回Schema
public static enum Schema{...}
}
基本實現
public class BaseImageDownloader implements ImageDownloader {
//...
//通過URL來從不同地方獲取圖片資源
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch(...) {
case 1:
case 2:
return this.getStreamFromNetwork(imageUri, extra);
case 3:
return this.getStreamFromFile(imageUri, extra);
case 4:
return this.getStreamFromContent(imageUri, extra);
case 5:
return this.getStreamFromAssets(imageUri, extra);
case 6:
return this.getStreamFromDrawable(imageUri, extra);
case 7:
default:
return this.getStreamFromOtherSource(imageUri, extra);
}
}
//網絡請求邏輯
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = this.createConnection(imageUri, extra);
for(int redirectCount = 0; conn.getResponseCode() / 100 == 3 && redirectCount < 5; ++redirectCount) {
conn = this.createConnection(conn.getHeaderField("Location"), extra);
}
InputStream imageStream;
try {
imageStream = conn.getInputStream();
} catch (IOException var7) {
IoUtils.readAndCloseStream(conn.getErrorStream());
throw var7;
}
if(!this.shouldBeProcessed(conn)) {
IoUtils.closeSilently(imageStream);
throw new IOException("Image request failed with response code " + conn.getResponseCode());
} else {
return new ContentLengthInputStream(new BufferedInputStream(imageStream, '耀'), conn.getContentLength());
}
}
//...
//創建Http請求對象
protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
String encodedUrl = Uri.encode(url, "@#&=*+-_.,:!?()/~\'%");
HttpURLConnection conn = (HttpURLConnection)(new URL(encodedUrl)).openConnection();
conn.setConnectTimeout(this.connectTimeout);
conn.setReadTimeout(this.readTimeout);
return conn;
}
}
磁盤緩存
接口
public interface DiskCache {
File getDirectory();
File get(String var1);
boolean save(String var1, InputStream var2, CopyListener var3) throws IOException;
boolean save(String var1, Bitmap var2) throws IOException;
boolean remove(String var1);
void close();
void clear();
}
基本實現
- BaseDiskCache (LimitedAgeDiskCache、UnlimitedDiskCache)
- LruDiskCache
圖片解碼
接口
public interface ImageDecoder {
Bitmap decode(ImageDecodingInfo var1) throws IOException;
}
圖片解碼信息類
public class ImageDecodingInfo {
private final String imageKey;
private final String imageUri;
private final String originalImageUri;
private final ImageSize targetSize;
private final ImageScaleType imageScaleType;
private final ViewScaleType viewScaleType;
private final ImageDownloader downloader;
private final Object extraForDownloader;
private final boolean considerExifParams;
private final Options decodingOptions;
...
}
基本實現
public class BaseImageDecoder implements ImageDecoder {
...
public BaseImageDecoder(boolean loggingEnabled) {
this.loggingEnabled = loggingEnabled;
}
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
//通過decodingInfo裏面的downloader獲取圖片流
InputStream imageStream = this.getImageStream(decodingInfo);
if(imageStream == null) {
L.e("No stream for image [%s]", new Object[]{decodingInfo.getImageKey()});
return null;
} else {
Bitmap decodedBitmap;
BaseImageDecoder.ImageFileInfo imageInfo;
try {
imageInfo = this.defineImageSizeAndRotation(imageStream, decodingInfo);
imageStream = this.resetStream(imageStream, decodingInfo);
Options decodingOptions = this.prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
decodedBitmap = BitmapFactory.decodeStream(imageStream, (Rect)null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if(decodedBitmap == null) {
L.e("Image can\'t be decoded [%s]", new Object[]{decodingInfo.getImageKey()});
} else {
decodedBitmap = this.considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
}
....
}
內存緩存
接口
public interface MemoryCache {
boolean put(String var1, Bitmap var2);
Bitmap get(String var1);
Bitmap remove(String var1);
Collection<String> keys();
void clear();
}
基本實現
public class LruMemoryCache implements MemoryCache {
private final LinkedHashMap<String, Bitmap> map;
private final int maxSize;
private int size;
public LruMemoryCache(int maxSize) {
if(maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
} else {
this.maxSize = maxSize;
this.map = new LinkedHashMap(0, 0.75F, true);
}
}
public final Bitmap get(String key) {
if(key == null) {
throw new NullPointerException("key == null");
} else {
synchronized(this) {
return (Bitmap)this.map.get(key);
}
}
}
public final boolean put(String key, Bitmap value) {
if(key != null && value != null) {
synchronized(this) {
this.size += this.sizeOf(key, value);
Bitmap previous = (Bitmap)this.map.put(key, value);
if(previous != null) {
this.size -= this.sizeOf(key, previous);
}
}
this.trimToSize(this.maxSize);
return true;
} else {
throw new NullPointerException("key == null || value == null");
}
}
private void trimToSize(int maxSize) {
while(true) {
synchronized(this) {
if(this.size < 0 || this.map.isEmpty() && this.size != 0) {
throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
if(this.size > maxSize && !this.map.isEmpty()) {
Entry toEvict = (Entry)this.map.entrySet().iterator().next();
if(toEvict != null) {
String key = (String)toEvict.getKey();
Bitmap value = (Bitmap)toEvict.getValue();
this.map.remove(key);
this.size -= this.sizeOf(key, value);
continue;
}
}
return;
}
}
}
public final Bitmap remove(String key) {
if(key == null) {
throw new NullPointerException("key == null");
} else {
synchronized(this) {
Bitmap previous = (Bitmap)this.map.remove(key);
if(previous != null) {
this.size -= this.sizeOf(key, previous);
}
return previous;
}
}
}
public Collection<String> keys() {
synchronized(this) {
return new HashSet(this.map.keySet());
}
}
public void clear() {
this.trimToSize(-1);
}
private int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
public final synchronized String toString() {
return String.format("LruCache[maxSize=%d]", new Object[]{Integer.valueOf(this.maxSize)});
}
}
圖片顯示器
定義圖片顯示效果,形狀或動畫,很nice
接口
public interface BitmapDisplayer {
void display(Bitmap var1, ImageAware var2, LoadedFrom var3);
}
其中LoadedFrom代表圖片加載的位置
public enum LoadedFrom {
NETWORK,
DISC_CACHE,
MEMORY_CACHE;
private LoadedFrom() {
}
}
基本實現
無特效
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
public SimpleBitmapDisplayer() {
}
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
}
圓形
好好學習,內部自定義了一個CircleDrawable
public class CircleBitmapDisplayer implements BitmapDisplayer {
protected final Integer strokeColor;
protected final float strokeWidth;
public CircleBitmapDisplayer() {
this((Integer)null);
}
public CircleBitmapDisplayer(Integer strokeColor) {
this(strokeColor, 0.0F);
}
public CircleBitmapDisplayer(Integer strokeColor, float strokeWidth) {
this.strokeColor = strokeColor;
this.strokeWidth = strokeWidth;
}
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
if(!(imageAware instanceof ImageViewAware)) {
throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected.");
} else {
imageAware.setImageDrawable(new CircleBitmapDisplayer.CircleDrawable(bitmap, this.strokeColor, this.strokeWidth));
}
}
public static class CircleDrawable extends Drawable {
protected float radius;
protected final RectF mRect = new RectF();
protected final RectF mBitmapRect;
protected final BitmapShader bitmapShader;
protected final Paint paint;
protected final Paint strokePaint;
protected final float strokeWidth;
protected float strokeRadius;
public CircleDrawable(Bitmap bitmap, Integer strokeColor, float strokeWidth) {
this.radius = (float)(Math.min(bitmap.getWidth(), bitmap.getHeight()) / 2);
this.bitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
this.mBitmapRect = new RectF(0.0F, 0.0F, (float)bitmap.getWidth(), (float)bitmap.getHeight());
this.paint = new Paint();
this.paint.setAntiAlias(true);
this.paint.setShader(this.bitmapShader);
this.paint.setFilterBitmap(true);
this.paint.setDither(true);
if(strokeColor == null) {
this.strokePaint = null;
} else {
this.strokePaint = new Paint();
this.strokePaint.setStyle(Style.STROKE);
this.strokePaint.setColor(strokeColor.intValue());
this.strokePaint.setStrokeWidth(strokeWidth);
this.strokePaint.setAntiAlias(true);
}
this.strokeWidth = strokeWidth;
this.strokeRadius = this.radius - strokeWidth / 2.0F;
}
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
this.mRect.set(0.0F, 0.0F, (float)bounds.width(), (float)bounds.height());
this.radius = (float)(Math.min(bounds.width(), bounds.height()) / 2);
this.strokeRadius = this.radius - this.strokeWidth / 2.0F;
Matrix shaderMatrix = new Matrix();
shaderMatrix.setRectToRect(this.mBitmapRect, this.mRect, ScaleToFit.FILL);
this.bitmapShader.setLocalMatrix(shaderMatrix);
}
public void draw(Canvas canvas) {
canvas.drawCircle(this.radius, this.radius, this.radius, this.paint);
if(this.strokePaint != null) {
canvas.drawCircle(this.radius, this.radius, this.strokeRadius, this.strokePaint);
}
}
public int getOpacity() {
return -3;
}
public void setAlpha(int alpha) {
this.paint.setAlpha(alpha);
}
public void setColorFilter(ColorFilter cf) {
this.paint.setColorFilter(cf);
}
}
}
漸入動畫
public class FadeInBitmapDisplayer implements BitmapDisplayer {
private final int durationMillis;
private final boolean animateFromNetwork;
private final boolean animateFromDisk;
private final boolean animateFromMemory;
public FadeInBitmapDisplayer(int durationMillis) {
this(durationMillis, true, true, true);
}
public FadeInBitmapDisplayer(int durationMillis, boolean animateFromNetwork, boolean animateFromDisk, boolean animateFromMemory) {
this.durationMillis = durationMillis;
this.animateFromNetwork = animateFromNetwork;
this.animateFromDisk = animateFromDisk;
this.animateFromMemory = animateFromMemory;
}
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
if(this.animateFromNetwork && loadedFrom == LoadedFrom.NETWORK || this.animateFromDisk && loadedFrom == LoadedFrom.DISC_CACHE || this.animateFromMemory && loadedFrom == LoadedFrom.MEMORY_CACHE) {
animate(imageAware.getWrappedView(), this.durationMillis);
}
}
public static void animate(View imageView, int durationMillis) {
if(imageView != null) {
AlphaAnimation fadeImage = new AlphaAnimation(0.0F, 1.0F);
fadeImage.setDuration((long)durationMillis);
fadeImage.setInterpolator(new DecelerateInterpolator());
imageView.startAnimation(fadeImage);
}
}
}
處理邏輯
入口
//所有的同步加載、異步加載、有回調的、沒回調的都會最終調用這個方法
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListene
//在這檢查是否init ImageLoderConfiguration
this.checkConfiguration();
//封裝了ImageView的屬性和操作
if(imageAware == null) {
throw new IllegalArgumentException("Wrong arguments were passed to displayImage() method (ImageView reference must not be null)");
} else {
if(listener == null) {
//默認爲SimpleLoadingListener
listener = this.defaultListener;
}
if(options == null) {
//如果不設置圖片顯示屬性,則使用默認值
options = this.configuration.defaultDisplayImageOptions;
}
//圖片URL爲空
if(TextUtils.isEmpty(uri)) {
this.engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//是否設置圖片URL爲空時要設置的圖片
if(options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(this.configuration.resources));
} else {
imageAware.setImageDrawable((Drawable)null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), (Bitmap)null);
} else {
//圖片URL不爲空的情況
//沒有特別設置圖片顯示尺寸的話,顯示最大尺寸
if(targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, this.configuration.getMaxImageSize());
}
//通過尺寸和URL生成內存緩存的鍵值
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
this.engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//試圖取出圖片緩存
Bitmap bmp = this.configuration.memoryCache.get(memoryCacheKey);
ImageLoadingInfo imageLoadingInfo;
//有圖片緩存&&bitmap沒有被回收
if(bmp != null && !bmp.isRecycled()) {
L.d("Load image from memory cache [%s]", new Object[]{memoryCacheKey});
//是否要對圖片進行特殊處理,比如加水印什麼的(主要實現接口爲BitmapProcessor)
if(options.shouldPostProcess()) {
//把圖片加載的配置信息封裝起來
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, this.engine.getLockForUri(uri));
//Runnable類型的處理顯示圖片的任務,其實還是用調用LoadAndDisplayImageTask來執行的
ProcessAndDisplayImageTask displayTask1 = new ProcessAndDisplayImageTask(this.engine, bmp, imageLoadingInfo, defineHandler(options));
if(options.isSyncLoading()) {
//同步加載
displayTask1.run();
} else {
//異步加載,提交到線程池
this.engine.submit(displayTask1);
}
} else {
//圖片不需要特殊處理
//使用顯示器顯示,默認的實現(SimpleBitmapDisplayer)就是imageAware.setImageBirmap
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
//回調圖片加載完成
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
if(options.shouldShowImageOnLoading()) {
//在加載階段顯示的圖片
imageAware.setImageDrawable(options.getImageOnLoading(this.configuration.resources));
} else if(options.isResetViewBeforeLoading()) {
//加載前重置的圖片
imageAware.setImageDrawable((Drawable)null);
}
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, this.engine.getLockForUri(uri));
//新建加載和顯示任務,也是Runnable類型的
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, defineHandler(options));
if(options.isSyncLoading()) {
displayTask.run();
} else {
this.engine.submit(displayTask);
}
}
}
}
任務類型
處理和顯示
圖片在內存已緩存,執行此任務
final class ProcessAndDisplayImageTask implements Runnable {
private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
private final ImageLoaderEngine engine;
private final Bitmap bitmap;
private final ImageLoadingInfo imageLoadingInfo;
private final Handler handler;
//...
public void run() {
L.d("PostProcess image before displaying [%s]", new Object[]{this.imageLoadingInfo.memoryCacheKey});
BitmapProcessor processor = this.imageLoadingInfo.options.getPostProcessor();
Bitmap processedBitmap = processor.process(this.bitmap);
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, this.imageLoadingInfo, this.engine, LoadedFrom.MEMORY_CACHE);
LoadAndDisplayImageTask.runTask(displayBitmapTask, this.imageLoadingInfo.options.isSyncLoading(), this.handler, this.engine);
}
}
加載和(處理和)顯示
LoadAndDisplayImageTask,代碼太多就不貼出來了,簡單捋下邏輯:
1. 在run方法裏面判斷內存中是否有緩存,否則調用tryLoadBitmap
2. tryLoadBitmap首先判斷磁盤是否有緩存,有則調用decodeImage返回Bitmap(經過以上分析,其實是調用了ImageDecoder,而在decoder裏面又會調用ImageDowloader的getStream獲得要解碼的圖片流)
3. 如果磁盤沒有緩存,則調用網絡獲取圖片流
執行引擎
從磁盤、網絡加載圖片等都是耗時操作,需要使用線程池執行操作。ImageLoaderEngine,顧名思義爲UIL的整個動力源,沒錯它裏面有三個線程池。
class ImageLoaderEngine {
//分發任務,在run裏面分發不同任務到下面兩個線程池
private Executor taskExecutor;
//執行ProcessAndDisplayImageTask任務,從緩存(磁盤、內存)裏面讀
private Executor taskExecutorForCachedImages;
//執行LoadAndDisplayImageTask,從網絡讀
private Executor taskDistributor;
//...
void submit(final LoadAndDisplayImageTask task) {
this.taskDistributor.execute(new Runnable() {
public void run() {
File image = ImageLoaderEngine.this.configuration.diskCache.get(task.getLoadingUri());
boolean isImageCachedOnDisk = image != null && image.exists();
ImageLoaderEngine.this.initExecutorsIfNeed();
if(isImageCachedOnDisk) {
ImageLoaderEngine.this.taskExecutorForCachedImages.execute(task);
} else {
ImageLoaderEngine.this.taskExecutor.execute(task);
}
}
});
}
void submit(ProcessAndDisplayImageTask task) {
this.initExecutorsIfNeed();
this.taskExecutorForCachedImages.execute(task);
}
}
//...
相關閱讀:UIL源碼解析