在介绍了一些基本组件后,我们越来越接近核心逻辑,任务调度设计一些数据结构,这些数据结构决定了任务如何调度。
在开始之前,笔者建议如果读者还不知道ReentrantLock Condition如何使用,建议先查一下,否则理解线程如何调度有些困难
双端队列:既可以当栈使用,又可以当做队列使用, LIFO LIFO两种
框架使用的接口
/**
* Insert(插入操作)
* addFirst(e)
* offerFirst(e)
* addLast(e)
* offerLast(e)
* Remove(删除操作)
* removeFirst()
* pollFirst()
* removeLast()
* pollLast()
* Examine(只查看不删除)
* getFirst()
* peekFirst()
* getLast()
* peekLast()
*
*
* 当做队列使用 Queue Method
* 等价方法 Deque Method
* java.util.Queue.add(e)
* addLast(e)
* java.util.Queue.offer(e)
* offerLast(e)
* java.util.Queue.remove()
* removeFirst()
* java.util.Queue.poll()
* pollFirst()
* java.util.Queue.element()
* getFirst()
* java.util.Queue.peek()
* peekFirst()
*
* 当做栈使用 Stack Method
* 等价方法 Deque Method
* push(e)
* addFirst(e)
*
* pop()
* removeFirst()
* peek()
* peekFirst()
*/
public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
// *** Queue methods ***
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
// *** Stack methods ***
void push(E e);
E pop();
// *** Collection methods ***
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
为了完成任务调度,还需要一个插入删除操作线程阻塞的队列BlockingDequeue,在这里就不介绍接口方法了,我们把目光转向具体实现:
LinkedBlockingDeque ,用两个条件变量来控制插入和删除的阻塞动作,lock锁为当前队列的对象
/** Condition for waiting takes */
private final Condition notEmpty = lock.newCondition();
/** Condition for waiting puts */
private final Condition notFull = lock.newCondition();
/**
* Links node as last element, or returns false if full.
*/
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false;
Node<E> l = last;
node.prev = l;
last = node;
if (first == null)
first = node;
else
l.next = node;
++count;
notEmpty.signal();//线程同步操作,这样因为没有数据要处理的线程便可以继续处理了
return true;
}
public void putLast(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
while (!linkLast(node))
notFull.await();//满了就等待,直到notFull.notify()被调用
} finally {
lock.unlock();
}
}
LIFOLinkedBlockingDeque 的实现逻辑几乎和LinkedBlockingDeque一样,只是颠倒了一下插入删除的顺序
总之,blocking的含义就是,满了就等待处理,空了也等待数据
有了这个双端队列,那么任务是如何被调度的呢?
到最核心的包com.nostra13.universalimageloader.core 下,找到 DefaultConfigurationFactory类,这个类为我们提供了一系列静态方法,来创建默认的配置参数,其中有这样一个方法:
/** Creates default implementation of task executor */
public static Executor createExecutor(int threadPoolSize, int threadPriority,
QueueProcessingType tasksProcessingType) {
boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
BlockingQueue<Runnable> taskQueue =
lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
createThreadFactory(threadPriority, "uil-pool-"));
}
可以看到,我们之前提到的阻塞队列实际的用图是调度Runnable,也是就线程。这个方法负责创建Executor来执行Runnable
/** Creates default implementation of task distributor */
public static Executor createTaskDistributor() {
return Executors.newCachedThreadPool(createThreadFactory(Thread.NORM_PRIORITY, "uil-pool-d-"));
}
这个方法负责创建任务分发的Executor, 其他静态方法,如果你读了之前的文章一定对那些组件不陌生。
UniversalImageLoader加载图片分为任务分发 和 任务执行 两个步骤来进行,读者先有个概念,我们先来看一看Runnable的具体实现是什么
LoadAndDisplayImageTask ProcessAndDisplayImageTask DisplayBitmapTask 是Runnable的具体实现 ,最终的图片显示都会由DisplayBitmapTask 来做。
LoadAndDisplayImageTask 负责下载后将图片显示出来
ProcessAndDisplayImageTask 仅仅是在图片被显示之前做了一下处理后,流程就转给LoadAndDisplayImageTask处理
LoadAndDisplayImageTask的核心方法是
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);
}
}
我们先不管engine是什么,只要知道它是用来执行任务的即可
另一个核心方法就是:
@Override
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; // 都没有就返回空
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();
}
//代码走道这里,这个bmp应该是不为null的
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);//执行显示图片的任务
runTask(displayBitmapTask, syncLoading, handler, engine);
}
最后,也是最重量级的一个组件ImageLoaderEngine,先看构造函数:
ImageLoaderEngine(ImageLoaderConfiguration configuration) {
this.configuration = configuration;//参数配置类,保存我们的配置参数,后面会讲解
taskExecutor = configuration.taskExecutor;//任务执行
taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;//任务执行,针对已缓存图片
taskDistributor = DefaultConfigurationFactory.createTaskDistributor();//任务分发
}
3个Executor,一个是负责任务分发的,另两个是负责任务执行的。
void fireCallback(Runnable r) {
taskDistributor.execute(r);
}
/** Submits task to execution pool */
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();
if (isImageCachedOnDisk) {
taskExecutorForCachedImages.execute(task);//执行加载缓存图片的任务
} else {
taskExecutor.execute(task);//执行下载并缓存的任务
}
}
});
}
/** Submits task to execution pool */
void submit(ProcessAndDisplayImageTask task) {
initExecutorsIfNeed();
taskExecutorForCachedImages.execute(task);
}
这三个方法,包含了engine的主要逻辑,我们可以看到,任务是被submit的,LoadAndDisplayImageTask 和ProcessAndDisplayImageTask的区别就是,前者不能确定图片被缓存了,而后者可以确定图片缓存下来了。可见taskDistributor的作用仅仅是把加载网络图片的任务和加载缓存图片的任务分开到另俩个Executor分别执行罢了。
先小小总结一下:Engine用了3个Executor,一个负责分发,另两个负责显示图片(一个负责从缓存里加载,一个负责从网上加载并缓存),而我们的BlockingQueue,在Executor初始化的时候作为参数传了进去。
是时候开始介绍最后三个应用接口类了,我想你一定不陌生:ImageLoader ImageLoaderConfiguration DisplayImageOptions
为了使用ImageLoader,你需要对ImageLoader进行初始化,大概是这样的
private synchronized void initImageLoaderConfig(){
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
.threadPoolSize(3)//线程池内加载的数量
.threadPriority(Thread.NORM_PRIORITY - 1)
.memoryCache(new FIFOLimitedMemoryCache(720 * 1280 * 8))
.tasksProcessingOrder(QueueProcessingType.LIFO)
.imageDownloader(new FDSVideoDownloader(this.activity(), 15000, 30000))
.build();
ImageLoader.getInstance().init(config);
}
然后呢,在加载图片的地方,你需要实例化一个DisplayImageOptions,大概是这样的
private void initImageDisplayOptions(Context context){
options = new DisplayImageOptions.Builder()
.showImageOnLoading(context.getResources().getDrawable(R.drawable.load_fail))
.showImageForEmptyUri(context.getResources().getDrawable(R.drawable.load_fail))
.showImageOnFail(context.getResources().getDrawable(R.drawable.load_fail))
.cacheInMemory(true)
.cacheOnDisk(true)
.considerExifParams(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
.build();
}
然后,当你加载图片的时候,应该是这样:
ImageLoader.getInstance().displayImage(
"http://www.example_imageuri, mHolder.mImage, options, null);
最后,当你不需要ImageLoader时,别忘了退出软件的时候,在activity回调方法里
@Override
public void onDestroy() {
super.onDestroy();
ImageLoader.getInstance().destroy();
}
好了,知道了基本的使用后,我们看一下displayImage这个方法
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options,
ImageLoadingListener listener) {
displayImage(uri, imageView, options, listener, null);
}
最终的具体实现则都调用这个方法,有点长:
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 = emptyListener;
}
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;
}
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);//仅仅缓存了key而已,方便查找
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));//new 了一个task
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);//提交给Executor去处理
}
} 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));//new 了一个task
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);//提交给Executor去处理
}
}
}
至此,你应该知道了整个UIL的工作流程,本人分析的比较详细,只是为了避繁从简,能让大家马上抓住UIL的主干,所以采取了这种方式。如果有哪位博友对这里面的问题不是很清楚,可以给我发邮件,或者给我留言,源码分析并没有完成,下一篇文章主要讲一下有关内存泄漏的问题,是的,如果你使用不当,UIL会造成Activity泄漏,后果是致命的