一个无大小限制的本地图片缓存。与BaseDiskCache
无异,只是用了个意思明确的类名。
限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素。
通过缓存目录下名为journal
的文件记录缓存的所有操作,并在缓存open
时读取journal
的文件内容存储到LinkedHashMap<String, Entry> lruEntries
中,后面get(String key)
获取缓存内容时,会先从lruEntries
中得到图片文件名返回文件。
LRU 的实现跟上面内存缓存类似,lruEntries
为new LinkedHashMap<String, Entry>(0, 0.75f, true)
,LinkedHashMap 第三个参数表示是否需要根据访问顺序(accessOrder)排序,true 表示根据accessOrder
排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时trimToSize()
函数始终删除第一个元素,即始终删除最近最少访问的文件。
来源于 JakeWharton 的开源项目 DiskLruCache,具体分析请等待 DiskLruCache 源码解析 完成。
限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,实现了DiskCache
。
内部有个DiskLruCache cache
属性,缓存的存、取操作基本都是由该属性代理完成。
通过readLine()
函数从InputStream
中读取一行,目前仅用于磁盘缓存操作记录文件journal
的解析。
工具类。
String readFully(Reader reader)
读取 reader 中内容。
deleteContents(File dir)
递归删除文件夹内容。
InputStream
的装饰者,可通过available()
函数得到 InputStream 对应数据源的长度(总字节数)。主要用于计算文件存储进度即图片下载进度时的总进度。
图片下载及显示时的错误原因,目前包括:
IO_ERROR
网络连接或是磁盘存储错误。
DECODING_ERROR
decode image 为 Bitmap 时错误。
NETWORK_DENIED
当图片不在缓存中,且设置不允许访问网络时的错误。
OUT_OF_MEMORY
内存溢出错误。
UNKNOWN
未知错误。
为了解决早期 Android 版本BitmapFactory.decodeStream(…)
在慢网络情况下 decode image 异常的 Bug。
主要通过重写FilterInputStream
的 skip(long n) 函数解决,确保 skip(long n) 始终跳过了 n 个字节。如果返回结果即跳过的字节数小于 n,则不断循环直到 skip(long n) 跳过 n 字节或到达文件尾。
Image 的缩放类型,目前包括:
NONE
不缩放。
NONE_SAFE
根据需要以整数倍缩小图片,使得其尺寸不超过 Texture 可接受最大尺寸。
IN_SAMPLE_POWER_OF_2
根据需要以 2 的 n 次幂缩小图片,使其尺寸不超过目标大小,比较快的缩小方式。
IN_SAMPLE_INT
根据需要以整数倍缩小图片,使其尺寸不超过目标大小。
EXACTLY
根据需要缩小图片到宽或高有一个与目标尺寸一致。
EXACTLY_STRETCHED
根据需要缩放图片到宽或高有一个与目标尺寸一致。
ImageAware
的 ScaleType。
将 ImageView 的 ScaleType 简化为两种FIT_INSIDE
和CROP
两种。FIT_INSIDE
表示将图片缩放到至少宽度和高度有一个小于等于 View 的对应尺寸,CROP
表示将图片缩放到宽度和高度都大于等于 View 的对应尺寸。
表示图片宽高的类。
scaleDown(…)
等比缩小宽高。
scale(…)
等比放大宽高。
图片来源枚举类,包括网络、磁盘缓存、内存缓存。
将图片转换为 Bitmap 的接口,抽象函数:
Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
表示根据ImageDecodingInfo
信息得到图片并根据参数将其转换为 Bitmap。
实现了ImageDecoder
。调用ImageDownloader
获取图片,然后根据ImageDecodingInfo
或图片 Exif 信息处理图片转换为 Bitmap。
主要函数:
调用ImageDownloader
获取图片,再调用defineImageSizeAndRotation(…)
函数得到图片的相关信息,调用prepareDecodingOptions(…)
得到图片缩放的比例,调用BitmapFactory.decodeStream
将 InputStream 转换为 Bitmap,最后调用considerExactScaleAndOrientatiton(…)
根据参数将图片放大、翻转、旋转为合适的样子返回。
得到图片真实大小以及 Exif 信息(设置考虑 Exif 的条件下)。
得到图片 Exif 信息中的翻转以及旋转角度信息。
得到图片缩放的比例。
scaleType
等于ImageScaleType.NONE
,则缩放比例为 1;scaleType
等于ImageScaleType.NONE_SAFE
,则缩放比例为 (int)Math.ceil(Math.max((float)srcWidth / maxWidth, (float)srcHeight / maxHeight))
;ImageSizeUtils.computeImageSampleSize(…)
计算缩放比例。viewScaleType
等于ViewScaleType.FIT_INSIDE
;scaleType
等于ImageScaleType.IN_SAMPLE_POWER_OF_2
,则缩放比例从 1 开始不断 *2 直到宽或高小于最大尺寸;Math.max(srcWidth / targetWidth, srcHeight / targetHeight)
。scaleType
等于ViewScaleType.CROP
;scaleType
等于ImageScaleType.IN_SAMPLE_POWER_OF_2
,则缩放比例从 1 开始不断 *2 直到宽和高都小于最大尺寸。Math.min(srcWidth / targetWidth, srcHeight / targetHeight)
。根据参数将图片放大、翻转、旋转为合适的样子返回。
Image Decode 需要的信息。
String imageKey
图片。
String imageUri
图片 uri,可能是缓存文件的 uri。
String originalImageUri
图片原 uri。
ImageSize targetSize
图片的显示尺寸。
imageScaleType
图片的 ScaleType。
ImageDownloader downloader
图片的下载器。
Object extraForDownloader
下载器需要的辅助信息。
boolean considerExifParams
是否需要考虑图片 Exif 信息。
Options decodingOptions
图片的解码信息,为 BitmapFactory.Options。
在ImageAware
中显示 bitmap 对象的接口。可在实现中对 bitmap 做一些额外处理,比如加圆角、动画效果。
图片淡入方式显示在ImageAware
中,实现了BitmapDisplayer
接口。
为图片添加圆角显示在ImageAware
中,实现了BitmapDisplayer
接口。主要通过BitmapShader
实现。
为图片添加渐变效果的圆角显示在ImageAware
中,实现了BitmapDisplayer
接口。主要通过RadialGradient
实现。
直接将图片显示在ImageAware
中,实现了BitmapDisplayer
接口。
图片处理接口。可用于对图片预处理(Pre-process Bitmap)和后处理(Post-process Bitmap)。抽象函数:
public interface BitmapProcessor {
Bitmap process(Bitmap bitmap);
}
用户可以根据自己需求去实现它。比如你想要为你的图片添加一个水印,那么可以自己去实现 BitmapProcessor 接口,在DisplayImageOptions
中配置 Pre-process 阶段预处理图片,这样设置后存储在文件系统以及内存缓存中的图片都是加了水印后的。如果只希望在显示时改变不动原图片,可以在BitmapDisplayer
中处理。
可在 View 滚动过程中暂停图片加载的 Listener,实现了 OnScrollListener 接口。
它的好处是防止滚动中不必要的图片加载,比如快速滚动不希望滚动中的图片加载。在 ListView 或 GridView 中 item 加载图片最好使用它,简单的一行代码:
gridView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), false, true));
主要的成员变量:
pauseOnScroll
触摸滑动(手指依然在屏幕上)过程中是否暂停图片加载。
pauseOnFling
甩指滚动(手指已离开屏幕)过程中是否暂停图片加载。
externalListener
自定义的 OnScrollListener 接口,适用于 View 原来就有自定义 OnScrollListener 情况设置。
实现原理:
重写onScrollStateChanged(…)
函数判断不同的状态下暂停或继续图片加载。
OnScrollListener.SCROLL_STATE_IDLE
表示 View 处于空闲状态,没有在滚动,这时候会加载图片。
OnScrollListener.SCROLL_STATE_TOUCH_SCROLL
表示 View 处于触摸滑动状态,手指依然在屏幕上,通过pauseOnScroll
变量确定是否需要暂停图片加载。这种时候大都属于慢速滚动浏览状态,所以建议继续图片加载。
OnScrollListener.SCROLL_STATE_FLING
表示 View 处于甩指滚动状态,手指已离开屏幕,通过pauseOnFling
变量确定是否需要暂停图片加载。这种时候大都属于快速滚动状态,所以建议暂停图片加载以节省资源。
任务队列的处理类型,包括FIFO
先进先出、LIFO
后进先出。
后进先出阻塞队列。重写LinkedBlockingDeque
的offer(…)
函数如下:
@Override
public boolean offer(T e) {
return super.offerFirst(e);
}
让LinkedBlockingDeque
插入总在最前,而remove()
本身始终删除第一个元素,所以就变为了后进先出阻塞队列。
实际一般情况只重写offer(…)
函数是不够的,但因为ThreadPoolExecutor
默认只用到了BlockingQueue
的offer(…)
函数,所以这种简单重写后做为ThreadPoolExecutor
的任务队列没问题。
LIFOLinkedBlockingDeque.java
包下的LinkedBlockingDeque.java
、BlockingDeque.java
、Deque.java
都是 Java 1.6 源码中的,这里不做分析。
磁盘缓存工具类,可用于查找或删除某个 uri 对应的磁盘缓存。
内存缓存工具类。可用于根据 uri 生成内存缓存 key,缓存 key 比较,根据 uri 得到所有相关的 key 或图片,删除某个 uri 的内存缓存。
generateKey(String imageUri, ImageSize targetSize)
根据 uri 生成内存缓存 key,key 规则为[imageUri]_[width]x[height]
。
得到图片 SD 卡缓存目录路径。
缓存目录优先选择/Android/data/[app_package_name]/cache
;若无权限或不可用,则选择 App 在文件系统的缓存目录context.getCacheDir()
;若无权限或不可用,则选择/data/data/[app_package_name]/cache
。
如果缓存目录选择了/Android/data/[app_package_name]/cache
,则新建.nomedia
文件表示不允许类似 Galley 这些应用显示此文件夹下图片。不过在 4.0 系统有 Bug 这种方式不生效。
用于计算图片尺寸、缩放比例相关的工具类。
IO 相关工具类,包括 stream 拷贝,关闭等。
Log 工具类。
UIL 的内存缓存默认使用了 LRU 算法。 LRU: Least Recently Used 近期最少使用算法, 选用了基于链表结构的 LinkedHashMap 作为存储结构。
假设情景:内存缓存设置的阈值只够存储两个 bitmap 对象,当 put 第三个 bitmap 对象时,将近期最少使用的 bitmap 对象移除。
图 1: 初始化 LinkedHashMap, 并按使用顺序来排序, accessOrder = true;
图 2: 向缓存池中放入 bitmap1 和 bitmap2 两个对象。
图 3: 继续放入第三个 bitmap3,根据假设情景,将会超过设定缓存池阈值。
图 4: 释放对 bitmap1 对象的引用。
图 5: bitmap1 对象被 GC 回收。