标签:
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);
}
}
}这样 就完成了 图片的 缓存 与显示
标签:
原文地址:http://blog.csdn.net/u012760183/article/details/51209308