Android-Universal-Image-Loader完全解析---加载流程
我们拿最常用的函数来分析:
总体概况包含三个过程:保存图片,加载图片,显示图片。
displayImage(String uri, ImageView imageView)
所有的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; } 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); } } }
一.分析入参
* @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
* @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
* which should display image
* @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
* decoding and displaying. If <b>null</b> - default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
* from configuration} will be used.
* @param targetSize {@linkplain ImageSize} Image target size. If <b>null</b> - size will depend on the view
* @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
* events on UI thread if this method is called on UI thread.
* @param progressListener {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
* Listener} for image loading progress. Listener fires events on UI thread if this method
* is called on UI thread. Caching on disk should be enabled in
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions options} to make
* this listener work.
uri:Image路径,支持网络路径,本地路径,drawable路径等
imageAware:Bitmap显示的容器,这个类是个接口,扩展性极好,凡是实现接口的容器ImageLoader都可以支持,不只局限于ImageVIew
public interface ImageAware { int getWidth(); int getHeight(); ViewScaleType getScaleType(); View getWrappedView(); boolean isCollected(); int getId(); boolean setImageDrawable(Drawable drawable); boolean setImageBitmap(Bitmap bitmap); }
displayImage(String uri, ImageView imageView)参数ImageView是如何转化成ImageAware的?
public void displayImage(String uri, ImageView imageView) { displayImage(uri, new ImageViewAware(imageView), null, null, null); }
看下ImageViewAware的实现
public class ImageViewAware extends ViewAware { /** * Constructor. <br /> * References {@link #ImageViewAware(android.widget.ImageView, boolean) ImageViewAware(imageView, true)}. * * @param imageView {@link android.widget.ImageView ImageView} to work with */ public ImageViewAware(ImageView imageView) { super(imageView); } /** * Constructor * * @param imageView {@link android.widget.ImageView ImageView} to work with * @param checkActualViewSize <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual * size of ImageView. It can cause known issues like * <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>. * But it helps to save memory because memory cache keeps bitmaps of actual (less in * general) size. * <p/> * <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b> * consider actual size of ImageView, just layout parameters. <br /> If you set 'false' * it's recommended 'android:layout_width' and 'android:layout_height' (or * 'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to * save memory. * <p/> */ public ImageViewAware(ImageView imageView, boolean checkActualViewSize) { super(imageView, checkActualViewSize); } /** * {@inheritDoc} * <br /> * 3) Get <b>maxWidth</b>. */ @Override public int getWidth() { int width = super.getWidth(); if (width <= 0) { ImageView imageView = (ImageView) viewRef.get(); if (imageView != null) { width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check maxWidth parameter } } return width; } /** * {@inheritDoc} * <br /> * 3) Get <b>maxHeight</b> */ @Override public int getHeight() { int height = super.getHeight(); if (height <= 0) { ImageView imageView = (ImageView) viewRef.get(); if (imageView != null) { height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check maxHeight parameter } } return height; } @Override public ViewScaleType getScaleType() { ImageView imageView = (ImageView) viewRef.get(); if (imageView != null) { return ViewScaleType.fromImageView(imageView); } return super.getScaleType(); } @Override public ImageView getWrappedView() { return (ImageView) super.getWrappedView(); } @Override protected void setImageDrawableInto(Drawable drawable, View view) { ((ImageView) view).setImageDrawable(drawable); if (drawable instanceof AnimationDrawable) { ((AnimationDrawable)drawable).start(); } } @Override protected void setImageBitmapInto(Bitmap bitmap, View view) { ((ImageView) view).setImageBitmap(bitmap); } private static int getImageViewFieldValue(Object object, String fieldName) { int value = 0; try { Field field = ImageView.class.getDeclaredField(fieldName); field.setAccessible(true); int fieldValue = (Integer) field.get(object); if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) { value = fieldValue; } } catch (Exception e) { L.e(e); } return value; } }
ViewAware的实现
public abstract class ViewAware implements ImageAware { public static final String WARN_CANT_SET_DRAWABLE = "Can't set a drawable into view. You should call ImageLoader on UI thread for it."; public static final String WARN_CANT_SET_BITMAP = "Can't set a bitmap into view. You should call ImageLoader on UI thread for it."; protected Reference<View> viewRef; protected boolean checkActualViewSize; /** * Constructor. <br /> * References {@link #ViewAware(android.view.View, boolean) ImageViewAware(imageView, true)}. * * @param view {@link android.view.View View} to work with */ public ViewAware(View view) { this(view, true); } /** * Constructor * * @param view {@link android.view.View View} to work with * @param checkActualViewSize <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual * size of View. It can cause known issues like * <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>. * But it helps to save memory because memory cache keeps bitmaps of actual (less in * general) size. * <p/> * <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b> * consider actual size of View, just layout parameters. <br /> If you set 'false' * it's recommended 'android:layout_width' and 'android:layout_height' (or * 'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to * save memory. */ public ViewAware(View view, boolean checkActualViewSize) { if (view == null) throw new IllegalArgumentException("view must not be null"); this.viewRef = new WeakReference<View>(view); this.checkActualViewSize = checkActualViewSize; } /** * {@inheritDoc} * <p/> * Width is defined by target {@link android.view.View view} parameters, configuration * parameters or device display dimensions.<br /> * Size computing algorithm (go by steps until get non-zero value):<br /> * 1) Get the actual drawn <b>getWidth()</b> of the View<br /> * 2) Get <b>layout_width</b> */ @Override public int getWidth() { View view = viewRef.get(); if (view != null) { final ViewGroup.LayoutParams params = view.getLayoutParams(); int width = 0; if (checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) { width = view.getWidth(); // Get actual image width } if (width <= 0 && params != null) width = params.width; // Get layout width parameter return width; } return 0; } /** * {@inheritDoc} * <p/> * Height is defined by target {@link android.view.View view} parameters, configuration * parameters or device display dimensions.<br /> * Size computing algorithm (go by steps until get non-zero value):<br /> * 1) Get the actual drawn <b>getHeight()</b> of the View<br /> * 2) Get <b>layout_height</b> */ @Override public int getHeight() { View view = viewRef.get(); if (view != null) { final ViewGroup.LayoutParams params = view.getLayoutParams(); int height = 0; if (checkActualViewSize && params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) { height = view.getHeight(); // Get actual image height } if (height <= 0 && params != null) height = params.height; // Get layout height parameter return height; } return 0; } @Override public ViewScaleType getScaleType() { return ViewScaleType.CROP; } @Override public View getWrappedView() { return viewRef.get(); } @Override public boolean isCollected() { return viewRef.get() == null; } @Override public int getId() { View view = viewRef.get(); return view == null ? super.hashCode() : view.hashCode(); } @Override public boolean setImageDrawable(Drawable drawable) { if (Looper.myLooper() == Looper.getMainLooper()) { View view = viewRef.get(); if (view != null) { setImageDrawableInto(drawable, view); return true; } } else { L.w(WARN_CANT_SET_DRAWABLE); } return false; } @Override public boolean setImageBitmap(Bitmap bitmap) { if (Looper.myLooper() == Looper.getMainLooper()) { View view = viewRef.get(); if (view != null) { setImageBitmapInto(bitmap, view); return true; } } else { L.w(WARN_CANT_SET_BITMAP); } return false; } /** * Should set drawable into incoming view. Incoming view is guaranteed not null.<br /> * This method is called on UI thread. */ protected abstract void setImageDrawableInto(Drawable drawable, View view); /** * Should set Bitmap into incoming view. Incoming view is guaranteed not null.< br /> * This method is called on UI thread. */ protected abstract void setImageBitmapInto(Bitmap bitmap, View view); }
其中 boolean isCollected() 是否被gc回收 注意实现函数。
options参数:显示的一些配置,后续会统一讲解
imageSize参数:加载图片最大宽高
ImageLoadingListener,ImageLoadingProgressListener加载过程中的监听器
二.加载过程(保存,加载,显示)
1.一些参数的校验和初始化
其中:ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
targetSize(最大宽高)如果为空会取容器的宽高(避免图片太大内存溢出)。
2.接下来开始加载图片。
首先由路径生成内存缓存的唯一Key值memoryCacheKey ,且缓存起来。格式:[imageUri]_[width]x[height] 由此可见同一图片不同宽高缓存的是两个Key。
接着根据key获取缓存中的bitmap值。
如果本地缓存有则直接显示 其中 options.shouldPostProcess()==true表示后期需要Bitmap处理的情况,具体Bitmap处理逻辑需要配置 BitmapProcessor postProcessor去处理,实现 process方法,如切图等等耗时操作放在线程中去处理,此类如下:
public interface BitmapProcessor { /** * Makes some processing of incoming bitmap.<br /> * This method is executing on additional thread (not on UI thread).<br /> * <b>Note:</b> If this processor is used as {@linkplain DisplayImageOptions.Builder#preProcessor(BitmapProcessor) * pre-processor} then don't forget {@linkplain Bitmap#recycle() to recycle} incoming bitmap if you return a new * created one. * * @param bitmap Original {@linkplain Bitmap bitmap} * @return Processed {@linkplain Bitmap bitmap} */ Bitmap process(Bitmap bitmap); }
如果无本地缓存去加载,其中如果配置加载图片则显示
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
接着我们分析比较关键在加载代码:
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); }
engine.submit 就是线程池中加入线程异步处理,如果配置不限制线程数量默认最大线程数。我们直接看run方法(图片加载过程)
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); }
if (waitIfPaused()) return; 如果当前暂停加载无限期wait,开关在ImageLoader的pause()和resume()方法,比如快速滑动时暂停加载,停止滑动时恢复
if (delayIfNeed()) return; 延迟显示选项 开关和延迟时间配置在 option里
ReentrantLock loadFromUriLock 用到了同步锁 同步锁的创建在 ImageLoaderEngine中
ReentrantLock getLockForUri(String uri) { ReentrantLock lock = uriLocks.get(uri); if (lock == null) { lock = new ReentrantLock(); uriLocks.put(uri, lock); } return lock; }
可以看到同一路径bitmap的加载需要同步操作
接着看,从缓存中取缓存没有执行tryLoadBitmap加载Bitmap文件,看 tryLoadBitmap 源码如下:
从本地磁盘找如果找到去解析(加载本地文件),找不到执行缓存到本地磁盘操作(存文件)
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; }
将路径uri直接转流存文件 downloadImage
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); } } }
其中 resizeAndSaveImage 是什么意思? 大图片超过全局配置的最大宽高,进行压缩保存覆盖,节省空间
/** Decodes image file into Bitmap, resize it and save it back */ private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException { // Decode image file, compress and re-save it 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); } } if (bmp != null) { saved = configuration.diskCache.save(uri, bmp); bmp.recycle(); } } return saved; }
从缓存中存在文件或者保存完文件会执行加载bitmap过程,bitmap = decodeImage(imageUriForDecoding);
private Bitmap decodeImage(String imageUri) throws IOException { ViewScaleType viewScaleType = imageAware.getScaleType(); ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType, getDownloader(), options); return decoder.decode(decodingInfo); }
ImageDecoder 是给接口 默认用的是 BaseImageDecoder,我们看下 decode 方法
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException { Bitmap decodedBitmap; ImageFileInfo imageInfo; 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); 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 { decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal); } return decodedBitmap; }
获取输入流
defineImageSizeAndRotation解析旋转角度,如对于相册中的图片(ExifInfo对象)
resetStream初始化流
considerExactScaleAndOrientatiton 根据各种配置缩放图片,旋转图片
protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, int rotation, boolean flipHorizontal) { Matrix m = new Matrix(); // Scale to exact size if need 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 if (flipHorizontal) { m.postScale(-1, 1); if (loggingEnabled) L.d(LOG_FLIP_IMAGE, decodingInfo.getImageKey()); } // Rotate bitmap if need 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; }
3.最后显示图片
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
保证在UI线程中执行
static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) { if (sync) { r.run(); } else if (handler == null) { engine.fireCallback(r); } else { handler.post(r); } }
看显示task的实现 displayBitmapTask.run
public void run() { if (imageAware.isCollected()) { L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey); listener.onLoadingCancelled(imageUri, imageAware.getWrappedView()); } else if (isViewWasReused()) { L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey); listener.onLoadingCancelled(imageUri, imageAware.getWrappedView()); } else { L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey); displayer.display(bitmap, imageAware, loadedFrom); engine.cancelDisplayTaskFor(imageAware); listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap); } }
BitmapDisplayer 是个接口,可以各种扩展显示不同样式
public interface BitmapDisplayer { /** * Displays bitmap in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}. * <b>NOTE:</b> This method is called on UI thread so it's strongly recommended not to do any heavy work in it. * * @param bitmap Source bitmap * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view} to * display Bitmap * @param loadedFrom Source of loaded image */ void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom); }
如自带的实现圆角效果
public class RoundedBitmapDisplayer implements BitmapDisplayer { protected final int cornerRadius; protected final int margin; public RoundedBitmapDisplayer(int cornerRadiusPixels) { this(cornerRadiusPixels, 0); } public RoundedBitmapDisplayer(int cornerRadiusPixels, int marginPixels) { this.cornerRadius = cornerRadiusPixels; this.margin = marginPixels; } @Override public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { if (!(imageAware instanceof ImageViewAware)) { throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected."); } imageAware.setImageDrawable(new RoundedDrawable(bitmap, cornerRadius, margin)); } public static class RoundedDrawable extends Drawable { protected final float cornerRadius; protected final int margin; protected final RectF mRect = new RectF(), mBitmapRect; protected final BitmapShader bitmapShader; protected final Paint paint; public RoundedDrawable(Bitmap bitmap, int cornerRadius, int margin) { this.cornerRadius = cornerRadius; this.margin = margin; bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mBitmapRect = new RectF (margin, margin, bitmap.getWidth() - margin, bitmap.getHeight() - margin); paint = new Paint(); paint.setAntiAlias(true); paint.setShader(bitmapShader); paint.setFilterBitmap(true); paint.setDither(true); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mRect.set(margin, margin, bounds.width() - margin, bounds.height() - margin); // Resize the original bitmap to fit the new bound Matrix shaderMatrix = new Matrix(); shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL); bitmapShader.setLocalMatrix(shaderMatrix); } @Override public void draw(Canvas canvas) { canvas.drawRoundRect(mRect, cornerRadius, cornerRadius, paint); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void setAlpha(int alpha) { paint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { paint.setColorFilter(cf); } } }
到此over了,流程通了。
----------------------------------------分割线-----------------------------------------------
总结:
1.保存图片:路径转流直接存文件,如果文件过大直接裁剪覆盖文件
2.加载图片:先看检查内存中是否缓存,未缓存拿本地缓存,根据配置裁剪和角度旋转
3.显示:根据配置定制不同的显示