ImageLoader的简单分析(五)

来源:互联网 发布:约瑟夫环 java 递归 编辑:程序博客网 时间:2024/05/16 09:31

虽然现在有好多图片缓存库,功能还很强大,但是本文还是继续对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) {        //初始化engine            engine = 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就是worker            this.thread = getThreadFactory().newThread(this);        }

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

1 0
原创粉丝点击