虽然现在有好多图片缓存库,功能还很强大,但是本文还是继续对ImageLoader源码的解读。就算是以后不用ImageLoader这个库了,它的设计到实现还是有很多的地方值得去学习、钻研和琢磨的。我觉得思想有时候比具体的代码实现很重要。
前四篇关于ImageLoader的博客对其工作原理做了梳理,但是有一点我故意没有讲到—-ImageLoader的异步机制工作原理,下面就对此进行异步工作方式进行梳理,加深对ImageLoader的理解。其实我一直在犹豫这篇关于ImageLoader的异步机制的博客到底写还是不写,写吧怕多线程这块自己水平不够,会误导读者;不写吧,总觉得之前写的关于Imageloader的博客没有写实质性的东西,感觉心里不是很舒坦,后来我想既然把Imageloader的源码都看完了,就大胆也一下吧,关于多线程这块自己不理解的,还有网络资源可以查。最主要的是如果读者中有大牛的话发现不对的地方说不定会不吝赐教,可以留言指教一下,帮助自己提高。其实这一直是我写博客的最大动力和原因:第一帮助别人理解一点东西,第二获取别人的帮助,如果自己博文中有不对的地方,获取有人会给讲解然后自己改正,何乐而不为呢?废话说的太多了,正式开始!(注:在读此篇博文的时候,博主假设你已经读取过博主的其他关于ImageLoader的博客)。
在读取内存缓存并且展示图片的时候,如果是ImageLoader异步处理机制的话会有如下的代码:

//ProcessAndDisplayImageTask 这个类是一个Runnable的实现类
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,defineHandler(options));
//提交Ruannable,并执行之
engine.submit(displayTask);

可以发现会有一个engine的引用来提交和执行ProcessAndDisplayImageTask 这个Ruannable。先来说说这个engine是什么东东,这个东西是在ImageLoader调用init方法的时候进行初始化的:

public synchronized void init(ImageLoaderConfiguration configuration) {if (configuration == null) {throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);}if (this.configuration == null) {//初始化engineengine = new ImageLoaderEngine(configuration);this.configuration = configuration;} }

很简单的一段代码,只是把configuration作为参传给engine,它指向的是ImageLoaderEngine对象:

ImageLoaderEngine(ImageLoaderConfiguration configuration) {this.configuration = configuration;//省略了部分代码,下面会说明taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;}

在构造器初始化的时候初始化了taskExecutorForCachedImages这个引用,它是一个Executor,可以发现是从configuration的taskExecutorForCachedImages得来的,这个Executor的作用就是使ImageView用异步的方式展示memory cache里面的bitmap缓存的!同时这个对象最初初始化的地方根据前面博文的讲解,很显然如果你没有配置自己的Exceutor,那么ImageLoaderConfiguration的Builder就会自己自己提供默认实现:

//此段代码位于Builder的initEmptyFieldsWithDefaultValues方法中
if (taskExecutorForCachedImages == null) {taskExecutorForCachedImages = DefaultConfigurationFactory.createExecutor(threadPoolSize, threadPriority, tasksProcessingType);}

可以发现我们把在ImageLoaderConfiguration配置好的线程池大小、线程优先级、任务队列的类型这些信息交给createExecutor方法处理,然后该方法返回一个Executor!具体实现如下:

    public static Executor createExecutor(int threadPoolSize, int threadPriority,QueueProcessingType tasksProcessingType) {//判断是否是后进先出任务队列boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;//根据lifo创建不同的人物队列BlockingQueue<Runnable> taskQueue =lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();//返回线程池 ThreadPoolExecutor对象    return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,createThreadFactory(threadPriority, "uil-pool-"));}

下面将对线程的一些知识结合ImageLoader的实现来进行本篇博文的讲解:

BlockingQueue的简单说明:

:它是Queue的子类,Queue这个队列的操作不会阻塞,而BlockingQueue顾名思义,在Queue的基础上增加了可阻塞的插入/获取操作。如果队列为空的话,不像它的父类Queue那样使得获取元素的操作返回空值,BlockingQueue会使获取元素的操作一直阻塞,直到对列中有一个可用的元素(在ImageLoader的设计中阻塞队列中的元素就是ProcessAndDisplayImageTask 这个Ruannable!!!).同理如果队列满的话,BlockingQueue对插入操作将一直阻塞,直到队列中有可用的空间。BlockingQueue这个阻塞队列在“生产者–消费者“此种模式中,是非常有用的。
在这里你可以问一下自己为什么ImageLoader读取内存中的bitmap并最终展示的时候要用阻塞队列或者说要用“生产者–消费者”这种模式呢?答案其实也很简单:

1)生产者-消费者模型使得生产者(线程)和消费者(线程)之间的依赖进行了消除
2)将数据生产的过程和使用数据的过程解耦。
而在我们的android应用用UI线程是用来展示和修改View的,如果不使用生产者消费者这种模型的话,那么UI线程既要负责图片资源的加载(生产数据),加载完成后又要把数据展现在UI上(数据的使用)。这种方式肯定不可取,所以ImageLoader采用了阻塞队列来模拟生产者-消费者:用ProcessAndDisplayImageTask 这个Ruannable在线程中不断生产数据(bitmap),等执行完毕后交给UI线程来处理数据(显示Image).这样由非UI线程生产数据,然后交给UI线程去处理的方式很明显能极大的提高用户体验!同时在加载大量图片的情况下,阻塞队列通过设置队列的大小或者线程池的大小也可以避免一次性把全部网络资源全部进行 请求。

ThreadPoolExecutor的简单说明:

这个线程池它基于生产者-消费者模式,正如上面代码所示,该线程池允许提供一个阻塞队列来保存等待执行的任务,用Ruannable来表示任务,在ImageLoader读取内存并显示的逻辑中,这个Ruannable就是ProcessAndDisplayImageTask 了。因为ThreadPoolExecutor的父类AbstractExecutorService实现了ExecutorService,所以ThreadPoolExecutor也具有了ExecutorService生命周期中的三种状态:运行,关闭和已经终止。ExecutorService提供了关闭任务的方法:shutdown方法和shutdownNow方法。二者的区别是:shutdown方法关闭过程比较温和:它不在接受新的任务,同事等待已经提交正在运行的或者已经提交的还未开始执行的任务执行完成。而shutdownNow方法比较粗暴:它会尝试取消所有正在执行的任务,并且不再执行队列中已经提交而尚未开始执行的任务。
在ImageLoader的实现中采取了是第二种关闭任务的方式,即shutdownNow的方式,我们可以通过ImageLoader对象的stop()方法来关闭任务.代码体现如下:

public void stop() {engine.stop();}

同样engine对象(ImageLoaderEngine)即的stop方法如下:

void stop() {//如果用的是ImageLoader的线程池if (!configuration.customExecutor) {((ExecutorService) taskExecutor).shutdownNow();}//如果用的是ImageLoader的线程池if (!configuration.customExecutorForCachedImages) {((ExecutorService) taskExecutorForCachedImages).shutdownNow();}此处省略部分代码(两行)}

ImageLoader的提供的这个stop方法很有用,当你的页面destroy的时候调用这个方法可以最大限度的避免在activity或者dialog,Fragment等销毁的情况下还有线程在运行!说道stop,不得不提的还有ImageLoader的pause方法和resume方法,这三个方法倒是可以接和Activity,Fragment的声明周期方法来灵活使用,避免页面切换时ImageLoader的还在异步的执行图片加载任务,减少内存的消耗。

ThreadFactory的简单说明:

既然是线程池,就不得不说线程工厂ThreadFactory。每当线程池需要创建一个线程的时候,都会通过ThreadFactory的newThread方法来创建一个线程。同样的ImageLoader也实现了自己的ThreadFactory:

//创建一个线程工厂
private static ThreadFactory createThreadFactory(int threadPriority, String threadNamePrefix) {return new DefaultThreadFactory(threadPriority, threadNamePrefix);}

正如上面代码所示,返回的是DefaultThreadFactory这个ImageLoader自己的实现ThreadFactory,限于篇幅直接贴其newThread方法:

        //注意这个r不是ProcessAndDisplayImageTask,而是一个Worker.public Thread newThread(Runnable r) {//常见一个新的线程Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);//如果是守护线程的话,就设置为非守护线程if (t.isDaemon()) t.setDaemon(false);t.setPriority(threadPriority);return t;}

注意在读取内存缓存bitmap时候newThread方法参数r为ProcessAndDisplayImageTask 这个Runnable!到现在的话我们就把ImageLoader加载内存缓存的线程池部分初步讲解完毕了,还回到文章开始的部分:engine.submit来执行异步显示内存缓存的task

void submit(ProcessAndDisplayImageTask task) {initExecutorsIfNeed();//执行异步任务taskExecutorForCachedImages.execute(task);}
private void initExecutorsIfNeed() {此处有省略代码if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages).isShutdown()) {taskExecutorForCachedImages = createTaskExecutor();}}

submit方法执行两种工作:
1)调用initExecutorsIfNeed方法检测customExecutorForCachedImages 不为null,并且之前的任务已经shutdown,并初始化一个新的Executor。
2)执行Executor.execute(ProcessAndDisplayImageTask);

还记得上面所说的newThread(Runnable r)方法参数r为Worker而非ProcessAndDisplayImageTask么,其实查看一下execute方法就很容易得出这个结论:

 public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}。。。此处省略部分代码。。}

很显然,把ProcessAndDisplayImageTask传给addWorker这个方法:

private boolean addWorker(Runnable firstTask, boolean core) {      //省略了大量代码        Worker w = new Worker(firstTask);Thread t = w.thread;

代码中显示创建一个Worker,这个Worker是干什么呢,其实就是一个工作队列(Worker Queue)是与线程池密不可分的部分,它保存了所有等待执行的任务,它的任务就是从工作队列中获取一个任务(Runnable),然后执行之,然后返回线程池并等待下一个任务。
在此不多说:看看Worker吧:

 Worker(Runnable firstTask) {//而我们的ProcessAndDisplayImageTask由firstTask持有this.firstTask = firstTask;//传this,this就是workerthis.thread = getThreadFactory().newThread(this);}

到此关于ProcessAndDisplayImageTask的工作的异步工作机制就讲解完毕,原理也很简单就是用阻塞队列+线程池来异步执行ProcessAndDisplayImageTask这个Runnable,并最终在处理完bitmap后交给UI线程中的handler并让ImageView最终展现出图片来了!
关于ProcessAndDisplayImageTask的具体实现原理,可参考《ImageLoader的简单分析(二)》和《ImageLoader的简单分析(四)》这两篇博客。
到此为止关于ImageLoader读取内存中的bitmap并最终展现的异步逻辑简单解析完毕,如果不正确的地方欢迎批评指正。
本篇博文参考书籍《java并发编程实战》

ImageLoader的简单分析(五)相关推荐

  1. [EntLib]微软企业库5.0 学习之路——第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—上篇...

    在完成了后,今天开始介绍企业库中的新模块:Cryptographer(加密模块),这个模块在日常的大多数项目的作用非常重要,例如:网站会员密码.身份证号.网站配置等,通过对信息进行加密可以保证项目数据 ...

  2. 五年级计算机学情分析,五年级学情分析

    与<五年级学情分析>相关的范文 2016届文学类文本阅读典型题例 (2016年6月高考前版本) 散文类文本 高考题:见<2011-2015年江苏高考语文试题汇编> ●2011江 ...

  3. x264源代码简单分析:宏块分析(Analysis)部分-帧间宏块(Inter)

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  4. x264源代码简单分析:滤波(Filter)部分

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  5. x264源代码简单分析:x264_slice_write()

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  6. x264源代码简单分析:x264命令行工具(x264.exe)

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  7. FFmpeg的H.264解码器源代码简单分析:宏块解码(Decode)部分-帧间宏块(Inter)

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  8. Nginx介绍及原理简单分析

    快速入门 ------------------------ 关于Nginx,我们可以到其官网 http://nginx.org/  以及WIKI http://wiki.nginx.org 进行下载和 ...

  9. 2021-12-22 AndroidR 电池信息 简单分析记录

    一.应用接口层 frameworks\base\core\java\android\os\BatteryManager.java  二.服务层 frameworks\bfase\services\co ...

  10. FFmpeg的HEVC解码器源代码简单分析:环路滤波(Loop Filter)

    ===================================================== HEVC源代码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpe ...

最新文章

  1. js进阶 12-1 jquery的鼠标事件有哪些
  2. Algs4-1.4.12找出两个有序数组的公共元素-方法1
  3. 浙大python判断两个字符串是否为变位词_python数据结构与算法 变位词
  4. centos php日志分析,记录一下CentOS7安装GoAccess日志分析工具
  5. Servlet、Struts2以及SpringMvc中的线程安全
  6. 解析Node.js v6.9.5官方文档的第一个例子的知识点
  7. wpf 加载page后启动_App启动之Dyld在做什么
  8. 如何通过钉钉群接收报警通知
  9. 密码学——常见的密码学习总结
  10. 面食有哪些 面食的种类大全
  11. VS2015 保护视力 背景色设置
  12. FPGA基础之VGA(三)移动方块
  13. 基于机器学习的笑脸检测
  14. IJCAI 22 | 面向第三方代码库的代码生成
  15. XAMP安装Apacher无法启动
  16. 《数据结构》网课 邓俊辉 习题详细解析(第七章:二叉搜索树)
  17. 2021-05-11 MongoDB面试题 分析器在MongoDB中的作用是什么
  18. 法语初级学习笔记-01-语音
  19. oracle安装点下一步退出,学习笔记:oracle之win10安装卸载oracle 11gR2步骤及常见问题解决...
  20. nestjs[typeorm学习之一对一表关系探究与使用]

热门文章

  1. 22. Django进阶:文件上传
  2. 谷粒商城:10.商品服务 — 属性分组
  3. python通讯录运用的知识点_Python实现通讯录功能
  4. Antlr4 简单入门
  5. 算法_EXCEL中 A表示第一列,B表示第二列...AA表示27列,AB表示28列,问随意一组字母是多少列
  6. 两幅图的RGB+Depth点云拼接
  7. opencv数字图像处理(3)- 图像平滑与锐化
  8. 自动色彩均衡算法(ACE)原理及实现
  9. 使用select和show命令查看mysql数据库系统信息
  10. caffe安装编译问题-ImportError: No module named caffe