Android-Universal-Image-Loader 是 github上一个开源的图片缓存框架 ,提供图片MemoryCache和DiskCache的功能,并支持加载网络、本地、contentProvider图片的功能
"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images) //通常不用。
NOTE: Use drawable://
only if you really need it! Always consider the native way to load drawables -ImageView.setImageResource(...)
instead of using of ImageLoader
.
首先 先写一个简单的例子:
ImageLoader imageLoader = ImageLoader.getInstance(); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).build(); imageLoader.init(config); imageLoader.displayImage("http://pic32.nipic.com/20130829/12906030_124355855000_2.png", image);第一行 要先实例化ImageLoader 采用了单例模式实例化
然后需要给imageLoader 初始化配置信息,也就是ImageLoaderConfiguration 这个类 如果不初始化 会报异常
接下来我们来看看这个类中都可以初始化哪些变量:
final Resources resources; //用于加载app中资源文件 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; //自定义的缓存图片任务执行者 final int threadPoolSize; //线程池 大小 默认为3 final int threadPriority; //线程优先级 final QueueProcessingType tasksProcessingType; //队列的类型 可以选择 FIFO(先进先出)LIFO(后进先出) final MemoryCache memoryCache; //内存缓存 final DiskCache diskCache; //磁盘缓存 final ImageDownloader downloader; //图片下载器 final ImageDecoder decoder; //图片解码器 final DisplayImageOptions defaultDisplayImageOptions; //图片展示选项 final ImageDownloader networkDeniedDownloader; //离线图片下载器 final ImageDownloader slowNetworkDownloader; //网速慢图片下载器在这个配置类中可以初始化以上内容 下面是一些默认的初始化
File cacheDir = StorageUtils.getCacheDirectory(context); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) .memoryCacheExtraOptions(480, 800) // default = device screen dimensions .diskCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null) .taskExecutor(...) .taskExecutorForCachedImages(...) .threadPoolSize(3) // default .threadPriority(Thread.NORM_PRIORITY - 1) // default .tasksProcessingOrder(QueueProcessingType.FIFO) // default .denyCacheImageMultipleSizesInMemory() .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) .memoryCacheSize(2 * 1024 * 1024) .memoryCacheSizePercentage(13) // default .diskCache(new UnlimitedDiscCache(cacheDir)) // default .diskCacheSize(50 * 1024 * 1024) .diskCacheFileCount(100) .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default .imageDownloader(new BaseImageDownloader(context)) // default .imageDecoder(new BaseImageDecoder()) // default .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default .writeDebugLogs() .build();
可以根据自己的需要选择需要使用的disk和memory缓存策略
接下来我们继续往下看:
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); } }init方法 传入配置信息 并根据配置信息初始化 ImageLoaderEngine引擎类 (主要是 初始化其中的TaskExecutor)
之后 便是 displayImage方法了
下面我们来看 displayImage这个方法
这个方法 参数最多的重载是 :
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
参数包括 图片uri 图片控件 展示图片的选项、图像的大小 、图像加载的监听、图像加载的进度条监听等 其中 options中还可以设置更多的选项
下面正式开始看 displayImage方法的源码 (由于太长 一步步来看):
<span style="white-space:pre"> </span>checkConfiguration(); if (imageAware == null) { throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS); } if (listener == null) { listener = defaultListener; } if (options == null) { options = configuration.defaultDisplayImageOptions; }
private void checkConfiguration() {
if (configuration == null) {
throw new IllegalStateException(ERROR_NOT_INIT);
}
}
下面几行 也是类似 如果所判断的变量为空则初始化一个
<span style="white-space:pre"> </span>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()); }
然后调用listener的start 之后由于 uri为空 如果设置了需要设置空的图像那么直接设置 图像是 空的时候需要设置的图像即可 如果没设置,直接不显示就好
之后调用 complete 回调 返回 这是uri为空的情况 不需要做太多操作 也不需要缓存
如果 图像的大小 设置是空 那么根据控件设置的大小 设置 要展示图片的大小
public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {
int width = imageAware.getWidth();
if (width <= 0) width = maxImageSize.getWidth();
int height = imageAware.getHeight();
if (height <= 0) height = maxImageSize.getHeight();
return new ImageSize(width, height);
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); engine.prepareDisplayTaskFor(imageAware, memoryCacheKey); listener.onLoadingStarted(uri, imageAware.getWrappedView());之后 根据 uri和目标的大小 生成一个key 并把 这个任务放入 engine 的集合中
回调 started方法
<span style="white-space:pre"> </span>Bitmap bmp = configuration.memoryCache.get(memoryCacheKey); //从内存缓存取 if (bmp != null && !bmp.isRecycled()) { //如果存在 并且没被回收 L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey); if (options.shouldPostProcess()) { //如果设置了 postProcess 执行 默认没设置 设置这个可以提前对图片进行某些处理 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); } }
首先第一行 从内存缓存中根据key取bitmap
第二行 判断 内存中有没有和 有没有被内存回收 如果存在切没被回收 那么就比较简单了
先对图片进行一些处理 然后把图片展示出来即可
其中 上述的那几行 task 代码 的主要目的就是 封装了一些在展示图片之前的一些对图片的处理 然后再展示图片
倒数第五行的else 语句 是在 不需要 在展示图片之前处理图片时,那么就直接使用 displaywe 对 图片进行 展示 并回调complete函数
其中 这个displayer可以设置 fadeIn(透明度) 和 Circle displayer(圆角) 看自己需要了
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
普通的displayer display非常简单 见上面代码
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()) { //表示同步 直接执行 <span style="white-space:pre"> </span>displayTask.run(); <span style="white-space:pre"> </span>} else { // 不同步 那么就交给线程池 对象执行 engine中 有 Executor 这其中有 线程池 <span style="white-space:pre"> </span>engine.submit(displayTask); <span style="white-space:pre"> </span>} }继续 源码 如果 不在内存缓存中 那么 就麻烦了 大体的操作步骤是 先从图片原始地加载图片,得到图片后放入硬盘和内存 然后展示
第二行 如果加载时需要显示图片 那么设置 否则 不设置图片
然后 设置正在加载时的信息 ImageLoadingInfo 和 任务LoadAndDisplayImageTask
之后根据是否同步 执行任务
接下来看 displayTask的run方法
<span style="white-space:pre"> </span>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();前两行是
如果waitIfPaused(), delayIfNeed()返回true的话,直接从run()方法中返回了,不执行下面的逻辑,
这两个方法 主要是判断是否是被中断了任务 或者要延时任务的
继续看 第四行 获取了 一个锁 然后 给其 加锁 这是为了防止重复的加载
假如在一个ListView中,某个item正在获取图片的过程中,而此时我们将这个item滚出界面之后又将其滚进来,滚进来之后如果没有加锁,该item又会去加载一次图片,假设在很短的时间内滚动很频繁,那么就会出现多次去网络上面请求图片,所以这里根据图片的Url去对应一个ReentrantLock对象,让具有相同Url的请求就会在最后一行等待,等到这次图片加载完成之后,ReentrantLock就被释放,刚刚那些相同Url的请求就会继续执行下面的代码
<span style="white-space:pre"> </span>Bitmap bmp; try { checkTaskNotActual(); //检查任务是否还在 bmp = configuration.memoryCache.get(memoryCacheKey); //从内存缓存获取bmp 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 { //内存缓存中有 设置 from 为 内存缓存 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(); //释放锁 } //下面两行是显示图片的任务 上面是加载bitmap 现已加载好 并缓存到 内存和磁盘中 只需要显示即可 //回调接口的 oncancle 和 oncomplete方法 在这里调用 进度条的 在 从网络获取的时候回调 onstart在最开始回调 DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom); runTask(displayBitmapTask, syncLoading, handler, engine);
主要是先从 磁盘中读取 如果没有 再从 网络上加载
让我们进入这个方法看看
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())); //直接解析出bitmap } 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()) { //tryCahcheImageDisk 方法 从网络下载 并缓存到硬盘 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这个方法的作用 是 在磁盘中未取到 时 从网络获取图片 并缓存到磁盘中去
/** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */ 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 { 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); } } }这样 就完成了 图片的 缓存 与显示