这一次是学习作为一个工具包,怎么对外提供方法;
我们可以先看看这个开源库存在哪些特征:
- 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
- 支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
- 支持图片的内存缓存,文件系统缓存或者SD卡缓存
- 支持图片下载过程的监听
- 根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
- 较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
- 提供在较慢的网络下对图片进行加载
那怎么联系局部的功能(缓存...)和外部接口函数呢?
从最简单的开始:
public void displayImage(String uri, ImageAware imageAware) {
displayImage(uri, imageAware, null, null, null);
}
displayImage的有很多(8个左右)重载函数,最后调用的都是一个重载函数:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
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)) {
//engin是一个图片展示的一个引擎,细节不管
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;
}
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
//void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
// cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
//把这个memoryCacheKey和imageAware的ID信息绑在一起,放到ImageLoaderEngine一个地方缓存起来--
/*调用的是:
* void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
}
* 存放的数据结构是: final Map<Integer, String> cacheKeysForImageAwares = Collections
.synchronizedMap(new HashMap<Integer, String>());
*
*/
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//从缓存中去找对应的memoryCacheKey的数据
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);
}
//这里注意了,通常我们的思路就是:既然内存缓存中没有,那么先去本地缓存上找,如果还没有就去网络上找;这就需要传URL参数进去,
//从而得到我们要的Bitmap数据,再用imageAware的boolean setImageBitmap(Bitmap bitmap)就行;思路肯定是这样的,只是代码的结构安排问题;
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
//初始化一个任务:包括加载图片(本地缓存/网络上)和显示图片,所以这部这需要提供一些信息,封装在ImageLoadingInfo里面
//LoadAndDisplayImageTask是继承Runnable接口的,但是这个displayTask.run()还是在当前线程中进行的,没有额外开启线程;-<span style="white-space:pre"> </span>//可以有一个判断是否同步的方法isSyncLoading()
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
//如果是异步那么就调用ImageLoaderEngine的submit去执行
engine.submit(displayTask);
}
}
}
首先ImageAware 是一个接口,用来提供要处理或者显示的图片的属性/行为
用的时候我们是传一个ImageView初始化对象进去,那为什么可以这样做呢?
如果是一个接口传进去,那么必须实例化,那ImageView肯定是实现了ImageAware 的一个类,那肯定也完成了一个动作:实例化ImageAware,
ImageAware aware=new ImageView();
这个地方我还没弄清楚,ImageView是andorid源码里面的东西,不可能去实现这个别人写的接口啊!--待高手指点
/**
*
* Represents image aware view which provides all needed properties and behavior for image processing and displaying
* through {@link com.nostra13.universalimageloader.core.ImageLoader ImageLoader}.
* It can wrap any Android {@link android.view.View View} which can be accessed by {@link #getWrappedView()}. Wrapped
* view is returned in {@link com.nostra13.universalimageloader.core.listener.ImageLoadingListener ImageLoadingListener}'s
* callbacks.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see ViewAware
* @see ImageViewAware
* @see NonViewAware
* @since 1.9.0
*/
public interface ImageAware {
/**
* Returns width of image aware view. This value is used to define scale size for original image.
* Can return 0 if width is undefined.<br />
* Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
*/
int getWidth();
/**
* Returns height of image aware view. This value is used to define scale size for original image.
* Can return 0 if height is undefined.<br />
* Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
*/
int getHeight();
/**
* Returns {@linkplain com.nostra13.universalimageloader.core.assist.ViewScaleType scale type} which is used for
* scaling image for this image aware view. Must <b>NOT</b> return <b>null</b>.
*/
ViewScaleType getScaleType();
/**
* Returns wrapped Android {@link android.view.View View}. Can return <b>null</b> if no view is wrapped or view was
* collected by GC.<br />
* Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
*/
View getWrappedView();
/**
* Returns a flag whether image aware view is collected by GC or whatsoever. If so then ImageLoader stop processing
* of task for this image aware view and fires
* {@link com.nostra13.universalimageloader.core.listener.ImageLoadingListener#onLoadingCancelled(String,
* android.view.View) ImageLoadingListener#onLoadingCancelled(String, View)} callback.<br />
* Mey be called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
*
* @return <b>true</b> - if view is collected by GC and ImageLoader should stop processing this image aware view;
* <b>false</b> - otherwise
*/
boolean isCollected();
/**
* Returns ID of image aware view. Point of ID is similar to Object's hashCode. This ID should be unique for every
* image view instance and should be the same for same instances. This ID identifies processing task in ImageLoader
* so ImageLoader won't process two image aware views with the same ID in one time. When ImageLoader get new task
* it cancels old task with this ID (if any) and starts new task.
* <p/>
* It's reasonable to return hash code of wrapped view (if any) to prevent displaying non-actual images in view
* because of view re-using.
*/
int getId();
/**
* Sets image drawable into this image aware view.<br />
* Displays drawable in this image aware view
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageForEmptyUri(
*android.graphics.drawable.Drawable) for empty Uri},
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnLoading(
*android.graphics.drawable.Drawable) on loading} or
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnFail(
*android.graphics.drawable.Drawable) on loading fail}. These drawables can be specified in
* {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions display options}.<br />
* Also can be called in {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
* Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
*
* @return <b>true</b> if drawable was set successfully; <b>false</b> - otherwise
*/
boolean setImageDrawable(Drawable drawable);
/**
* Sets image bitmap into this image aware view.<br />
* Displays loaded and decoded image {@link android.graphics.Bitmap} in this image view aware.
* Actually it's used only in
* {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
* Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
*
* @return <b>true</b> if bitmap was set successfully; <b>false</b> - otherwise
*/
boolean setImageBitmap(Bitmap bitmap);
}
图片URL传进来了,要显示的地方也有了,其他的都是一些附属的配置内的东西了:缓存配置,以什么方式显示得到的图片(带特殊效果不)..还有加入2个监听器接口
剩下要解决的问题就是:
1.怎么用URK从网络得到图片,里面有什么特出的处理,考虑并发了么等...?
后头看displayImage那个函数的后半部分
//初始化一个任务:包括加载图片(本地缓存/网络上)和显示图片,所以这部这需要提供一些信息,封装在ImageLoadingInfo里面
//LoadAndDisplayImageTask是继承Runnable接口的,但是这个displayTask.run()还是在主线程中进行的;--有一个是否同步的判断isSyncLoading()
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
//如果是异步那么就调用ImageLoaderEngine的submit去执行
engine.submit(displayTask);
}
(1)同步方法
沿着内存-》本地-》网络的思路去做,只是注意一个地方:同步方法没有开启额外的线程;
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)
//跳出run函数是吧?~~~~~~~~~~~~
//有些不对啊,如果是跳出去,那么就不从网络上获得数据了么?
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);
}
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;
//如果用户配置了本地缓存,那么从网络上得到的数据保存在本地,再去从本地取得--
//最终用到的是HttpURLConnection conn = createConnection(imageUri, extra);
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;
}
(2)异步任务
异步任务是用线程池完成的,线程池的
异步任务传入的参数还是LoadAndDisplayTask这个类的实例,
void submit(final LoadAndDisplayImageTask task) {
taskDistributor.execute(new Runnable() {
@Override
public void run() {
File image = configuration.diskCache.get(task.getLoadingUri());
boolean isImageCachedOnDisk = image != null && image.exists();
initExecutorsIfNeed();
//如果在本地已经有缓存的话,那么就用taskExecutorForCachedImages;默认情况下(用户没有配置)2个Excutor默认情况下开启的线程池大小、优先级、处理类型都是一样的
if (isImageCachedOnDisk) {
taskExecutorForCachedImages.execute(task);
} else {
taskExecutor.execute(task);
}
}
});
}
taskDistributor的实例化是这么完成的:
public static Executor createTaskDistributor() {
return Executors.newCachedThreadPool(createThreadFactory(Thread.NORM_PRIORITY, "uil-pool-d-"));
}
taskExecutorForCachedImages(task);和 taskExecutor.execute(task);--他们的Executor实例化都是ImageLoaderConfiguration配置好的传进来的
/*
if (taskExecutorForCachedImages == null) {
taskExecutorForCachedImages = DefaultConfigurationFactory
.createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
}
*/
分析道最后,发现有一个译文
http://www.cnblogs.com/kissazi2/p/3886563.html
这里的写的很清楚,一篇篇看下去比我写的清楚多了~原因是自己对各个知识点还不是非常熟悉,能看懂但是不能熟练掌握,领悟到一点:写文章也是一门细活,还是考验熟练程度的一个东西~