专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
开发者全社区  ·  阿里员工:今天被主管谈话了,给我两个选择:主 ... ·  5 小时前  
开发者全社区  ·  捡漏清华刷屏,这也行? ·  昨天  
开发者全社区  ·  清华美女状元嫁初中黄毛 ·  昨天  
开发者全社区  ·  中国十大古都城市 ·  2 天前  
51好读  ›  专栏  ›  郭霖

图片加载框架Universal-Image-Loader源码解析

郭霖  · 公众号  · android  · 2017-05-12 08:01

正文

今日科技快讯

腾讯表示:在5月10日的某影视公司年度新剧推荐会上,优酷员工用酒杯砸伤了腾讯的女员工。对此,优酷官微回应:你们爱员工,谁不爱员工?但大家都需要真相。人不犯我,我不犯人,江湖事江湖了是条汉子,斗嘴斗气无意义,我们毫无兴趣。

作者简介

明天就是周末啦,提前祝大家周末愉快!

本篇来自 易水南风 的投稿,分享了他对于Universal-Image-Loader框架的分析,希望能对大家有所帮助。

易水南风 的博客地址:

http://blog.csdn.net/sinat_23092639

前言

Universal-Image-Loader

https://github.com/nostra13/Android-Universal-Image-Loader

可以说是安卓知名图片开源框架中最古老、使用率最高的一个了。一张图片的加载对于安卓应用的开发也许是件简单的事,但是如果要同时加载大量的图片,并且图片用于 ListView、GridView、ViewPager 等控件,如何防止出现OOM、如何防止图片错位(因为列表的View复用功能)、如何更快地加载、如何让客户端程序员用最简单的操作完成本来十分复杂的图片加载工作,成了全世界安卓应用开发程序员心头的一大难题,所幸有了Universal-Image-Loader,让这一切变得简单,从某种意义来讲,它延长了安卓开发者的寿命~

针对上述几个问题,Universal-Image-Loader 可谓是兵来将挡水来土掩,见招拆招:

  • 如何同时加载大量图片:采用线程池优化高并发

  • 如何提高加载速度:使用内存、磁盘缓存

  • 如何避免OOM:加载的图片进行压缩,内存缓存具有相关的淘汰算法

  • 如何避免ListView的图片错位:将要加载的图片和要显示的ImageView绑定, 在请求过程中判断该ImageView当前绑定的图片是否是当前请求的图片,不是则停止当前请求。

首先来看下 Universal-Image-Loader 整体流程图:

总的来说就是:下载图片——将图片缓存在磁盘中——解码图片成为Bitmap——Bitmap的预处理——缓存在Bitmap内存中——Bitmap的后期处理——显示Bitmap

基本用法

关于基本用法想必很多朋友也知道了,或者直接看下Github说明里的使用方法,这里就不再赘述。

和源码见面之前,先和几个重要的类打招呼:

ImageLoaderEngine: 任务分发器,负责分发LoadAndDisplayImageTask和ProcessAndDisplayImageTask给具体的线程池去执行。

ImageAware: 显示图片的对象,可以是ImageView等。

ImageDownloader: 图片下载器,负责从图片的各个来源获取输入流。

Cache: 图片缓存,分为MemoryCache和DiskCache两部分。

MemoryCache: 内存图片缓存,可向内存缓存缓存图片或从内存缓存读取图片。

DiskCache: 本地图片缓存,可向本地磁盘缓存保存图片或从本地磁盘读取图片。

ImageDecoder: 图片解码器,负责将图片输入流InputStream转换为Bitmap对象。

BitmapProcessor: 图片处理器,负责从缓存读取或写入前对图片进行处理。

BitmapDisplayer: 将Bitmap对象显示在相应的控件ImageAware上。

LoadAndDisplayImageTask: 用于加载并显示图片的任务。

ProcessAndDisplayImageTask: 用于处理并显示图片的任务。

DisplayBitmapTask: 用于显示图片的任务。

其中有个全局图片加载配置类贯穿整个框架, ImageLoaderConfiguration ,可以配置的东西实在有点多:

主要是图片最大尺寸、线程池、缓存、下载器、解码器等等。

经过前面那么多的铺垫,终于迎来了源码~~

源码分析

整个框架的源码上万,全部讲完不可能,最好的方式还是按照加载流程走一遍,细枝末节各位可以自己慢慢研究,一旦整体把握好了,其他的一切就水到渠成,切勿只见树木不见森林,迷失在各种代码细节中~~

好了,简单点,讲代码的方式简单点,从最简单的代码切入:

imageLoader.displayImage(imageUri, imageView);

进入方法:

ImageAware 是 ImageView 的包装类,持有 ImageView对象 的弱引用,防止 ImageView 出现内存泄漏发生。主要是提供了获取 ImageView 宽度高度和 ScaleType 等。

最终会执行这一个重载方法:

这个方法基本描绘出了整个图片加载的流程,重要的地方已经加上注释。

if (listener == null ) 的 listener 就是上面基本使用说明复杂版本的进度回调接口 ImageLoadingListener,大家看下就知道,如果没有配置的话设置为默认,而默认其实啥都没做,方法都是空实现。

if (options == null ) 也是类似地如果没有配置 DisplayImageOptions,即图片显示的配置项,则取默认的。
看下这个图片显示配置类可以配置什么:

配置是否内存磁盘缓存以及设置图片预处理和后期处理器(预处理和后期处理器默认为 null ,留给客户端程序员扩展)等。

displayImage 方法 中 if (targetSize == null) 这一行,如果没有专门设置 targetSize,即指定图片的尺寸,就取 ImageAware 的宽高,即包装在里面的 ImageView 的宽高,如果得到的宽高小于等于0,则设置为最大宽高,如果没有设置内存缓存的最大宽高(maxImageWidthForMemoryCache,maxImageHeightForMemoryCache),则为屏幕的宽高。

然后根据图片uri和图片的尺寸生成一个内存缓存的key,之所以使用图片尺寸是因为内存缓存的图片同一张图片拥有不同尺寸的版本。

下面代码中:

engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

engine 指的是任务分发器 ImageLoaderEngine,它持有一个 HashMap,作为记录图片加载任务使用,一旦任务停止加载或者加载完毕则会删除对应的任务引用。

prepareDisplayTaskFor 方法正是将任务的引用添加到该 HashMap 中,这里以内存缓存的Key为键。

接下来如果拿到的图片需要做后期处理,则创建一个图片显示信息的对象,然后以之前创建的图片显示信息的对象为参数之一创建一个处理显示任务对象 ProcessAndDisplayImageTask(实现Runnable),如果是指定同步加载则直接调用它的run方法,不是则将其添加到任务分发器 ImageLoaderEngine 的线程池中异步执行。

若不需要对图片进行后期处理则调用 Displayer 去显示显示图片,默认的 Displayer 为 SimpleBitmapDisplayer,只是简单地显示图片到指定的 ImageView 中。

如果拿不到内存缓存的对应图片,则创建加载显示图片任务对象 LoadAndDisplayImageTask,然后执行该任务去磁盘缓存或者网络加载图片。

接下来的关键就是看下 LoadAndDisplayImageTask 的 run方法 怎么执行的,就知道整个图片加载怎么运行的了。

run方法不长,就直接展示了,我们看这里:

if (waitIfPaused()) return;
if (delayIfNeed()) return;

第1行中的 waitIfPaused 方法:

这里如果 engine 的 pause 标志位被置位为 true,则会调用 engine.getPauseLock() 对象(其实就是一个普通的Object对象)的wait方法使当前线程暂停运行。为什么要这样呢?看下 engine 的 pause 标志位什么时候会被置位为 true,终于在 PauseOnScrollListener 的重写方法 onScrollStateChanged 中找到:

我们知道 ListView 滑动时候加载显示图片会使得滑动有卡顿现象。这就是在列表滑动时暂停加载的方式,只要给 ListView 设置:







请到「今天看啥」查看全文