Android Universal Image Loader 源码分析(五)

4.2.41 UnlimitedDiskCache.java

一个无大小限制的本地图片缓存。与BaseDiskCache无异,只是用了个意思明确的类名。

4.2.42 DiskLruCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素。

通过缓存目录下名为journal的文件记录缓存的所有操作,并在缓存open时读取journal的文件内容存储到LinkedHashMap<String, Entry> lruEntries中,后面get(String key)获取缓存内容时,会先从lruEntries中得到图片文件名返回文件。

LRU 的实现跟上面内存缓存类似,lruEntriesnew LinkedHashMap<String, Entry>(0, 0.75f, true),LinkedHashMap 第三个参数表示是否需要根据访问顺序(accessOrder)排序,true 表示根据accessOrder排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时trimToSize()函数始终删除第一个元素,即始终删除最近最少访问的文件。

来源于 JakeWharton 的开源项目 DiskLruCache,具体分析请等待 DiskLruCache 源码解析 完成。

4.2.43 LruDiskCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,实现了DiskCache
内部有个DiskLruCache cache属性,缓存的存、取操作基本都是由该属性代理完成。

4.2.44 StrictLineReader.java

通过readLine()函数从InputStream中读取一行,目前仅用于磁盘缓存操作记录文件journal的解析。

4.2.45 Util.java

工具类。
String readFully(Reader reader)读取 reader 中内容。
deleteContents(File dir)递归删除文件夹内容。

4.2.46 ContentLengthInputStream.java

InputStream的装饰者,可通过available()函数得到 InputStream 对应数据源的长度(总字节数)。主要用于计算文件存储进度即图片下载进度时的总进度。

4.2.47 FailReason.java

图片下载及显示时的错误原因,目前包括:
IO_ERROR 网络连接或是磁盘存储错误。
DECODING_ERROR decode image 为 Bitmap 时错误。
NETWORK_DENIED 当图片不在缓存中,且设置不允许访问网络时的错误。
OUT_OF_MEMORY 内存溢出错误。
UNKNOWN 未知错误。

4.2.48 FlushedInputStream.java

为了解决早期 Android 版本BitmapFactory.decodeStream(…)在慢网络情况下 decode image 异常的 Bug。
主要通过重写FilterInputStream的 skip(long n) 函数解决,确保 skip(long n) 始终跳过了 n 个字节。如果返回结果即跳过的字节数小于 n,则不断循环直到 skip(long n) 跳过 n 字节或到达文件尾。

4.2.49 ImageScaleType.java

Image 的缩放类型,目前包括:
NONE不缩放。
NONE_SAFE根据需要以整数倍缩小图片,使得其尺寸不超过 Texture 可接受最大尺寸。
IN_SAMPLE_POWER_OF_2根据需要以 2 的 n 次幂缩小图片,使其尺寸不超过目标大小,比较快的缩小方式。
IN_SAMPLE_INT根据需要以整数倍缩小图片,使其尺寸不超过目标大小。
EXACTLY根据需要缩小图片到宽或高有一个与目标尺寸一致。
EXACTLY_STRETCHED根据需要缩放图片到宽或高有一个与目标尺寸一致。

4.2.50 ViewScaleType.java

ImageAware的 ScaleType。
将 ImageView 的 ScaleType 简化为两种FIT_INSIDECROP两种。FIT_INSIDE表示将图片缩放到至少宽度和高度有一个小于等于 View 的对应尺寸,CROP表示将图片缩放到宽度和高度都大于等于 View 的对应尺寸。

4.2.51 ImageSize.java

表示图片宽高的类。
scaleDown(…) 等比缩小宽高。
scale(…) 等比放大宽高。

4.2.52 LoadedFrom.java

图片来源枚举类,包括网络、磁盘缓存、内存缓存。

4.2.53 ImageDecoder.java

将图片转换为 Bitmap 的接口,抽象函数:

Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;

表示根据ImageDecodingInfo信息得到图片并根据参数将其转换为 Bitmap。

4.2.54 BaseImageDecoder.java

实现了ImageDecoder。调用ImageDownloader获取图片,然后根据ImageDecodingInfo或图片 Exif 信息处理图片转换为 Bitmap。

主要函数:

(1). decode(ImageDecodingInfo decodingInfo)

调用ImageDownloader获取图片,再调用defineImageSizeAndRotation(…)函数得到图片的相关信息,调用prepareDecodingOptions(…)得到图片缩放的比例,调用BitmapFactory.decodeStream将 InputStream 转换为 Bitmap,最后调用considerExactScaleAndOrientatiton(…)根据参数将图片放大、翻转、旋转为合适的样子返回。

(2). defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)

得到图片真实大小以及 Exif 信息(设置考虑 Exif 的条件下)。

(3). defineExifOrientation(String imageUri)

得到图片 Exif 信息中的翻转以及旋转角度信息。

(4). prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo)

得到图片缩放的比例。

  1. 如果scaleType等于ImageScaleType.NONE,则缩放比例为 1;
  2. 如果scaleType等于ImageScaleType.NONE_SAFE,则缩放比例为 (int)Math.ceil(Math.max((float)srcWidth / maxWidth, (float)srcHeight / maxHeight))
  3. 否则,调用ImageSizeUtils.computeImageSampleSize(…)计算缩放比例。
    在 computeImageSampleSize(…) 中
  4. 如果viewScaleType等于ViewScaleType.FIT_INSIDE
    1.1 如果scaleType等于ImageScaleType.IN_SAMPLE_POWER_OF_2,则缩放比例从 1 开始不断 *2 直到宽或高小于最大尺寸;
    1.2 否则取宽和高分别与最大尺寸比例中较大值,即Math.max(srcWidth / targetWidth, srcHeight / targetHeight)
  5. 如果scaleType等于ViewScaleType.CROP
    2.1 如果scaleType等于ImageScaleType.IN_SAMPLE_POWER_OF_2,则缩放比例从 1 开始不断 *2 直到宽和高都小于最大尺寸。
    2.2 否则取宽和高分别与最大尺寸比例中较小值,即Math.min(srcWidth / targetWidth, srcHeight / targetHeight)
  6. 最后判断宽和高是否超过最大值,如果是 *2 或是 +1 缩放。
(5). considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, int rotation, boolean flipHorizontal)

根据参数将图片放大、翻转、旋转为合适的样子返回。

4.2.55 ImageDecodingInfo.java

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。

4.2.56 BitmapDisplayer.java

ImageAware中显示 bitmap 对象的接口。可在实现中对 bitmap 做一些额外处理,比如加圆角、动画效果。

4.2.57 FadeInBitmapDisplayer.java

图片淡入方式显示在ImageAware中,实现了BitmapDisplayer接口。

4.2.58 RoundedBitmapDisplayer.java

为图片添加圆角显示在ImageAware中,实现了BitmapDisplayer接口。主要通过BitmapShader实现。

4.2.59 RoundedVignetteBitmapDisplayer.java

为图片添加渐变效果的圆角显示在ImageAware中,实现了BitmapDisplayer接口。主要通过RadialGradient实现。

4.2.60 SimpleBitmapDisplayer.java

直接将图片显示在ImageAware中,实现了BitmapDisplayer接口。

4.2.61 BitmapProcessor.java

图片处理接口。可用于对图片预处理(Pre-process Bitmap)和后处理(Post-process Bitmap)。抽象函数:

public interface BitmapProcessor {
    Bitmap process(Bitmap bitmap);
}

用户可以根据自己需求去实现它。比如你想要为你的图片添加一个水印,那么可以自己去实现 BitmapProcessor 接口,在DisplayImageOptions中配置 Pre-process 阶段预处理图片,这样设置后存储在文件系统以及内存缓存中的图片都是加了水印后的。如果只希望在显示时改变不动原图片,可以在BitmapDisplayer中处理。

4.2.62 PauseOnScrollListener.java

可在 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变量确定是否需要暂停图片加载。这种时候大都属于快速滚动状态,所以建议暂停图片加载以节省资源。

4.2.63 QueueProcessingType.java

任务队列的处理类型,包括FIFO先进先出、LIFO后进先出。

4.2.64 LIFOLinkedBlockingDeque.java

后进先出阻塞队列。重写LinkedBlockingDequeoffer(…)函数如下:

@Override
public boolean offer(T e) {
    return super.offerFirst(e);
}

LinkedBlockingDeque插入总在最前,而remove()本身始终删除第一个元素,所以就变为了后进先出阻塞队列。
实际一般情况只重写offer(…)函数是不够的,但因为ThreadPoolExecutor默认只用到了BlockingQueueoffer(…)函数,所以这种简单重写后做为ThreadPoolExecutor的任务队列没问题。

LIFOLinkedBlockingDeque.java包下的LinkedBlockingDeque.javaBlockingDeque.javaDeque.java都是 Java 1.6 源码中的,这里不做分析。

4.2.65 DiskCacheUtils.java

磁盘缓存工具类,可用于查找或删除某个 uri 对应的磁盘缓存。

4.2.66 MemoryCacheUtils.java

内存缓存工具类。可用于根据 uri 生成内存缓存 key,缓存 key 比较,根据 uri 得到所有相关的 key 或图片,删除某个 uri 的内存缓存。
generateKey(String imageUri, ImageSize targetSize)
根据 uri 生成内存缓存 key,key 规则为[imageUri]_[width]x[height]

4.2.67 StorageUtils.java

得到图片 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 这种方式不生效。

4.2.68 ImageSizeUtils.java

用于计算图片尺寸、缩放比例相关的工具类。

4.2.69 IoUtils.java

IO 相关工具类,包括 stream 拷贝,关闭等。

4.2.70 L.java

Log 工具类。

5. 杂谈

聊聊 LRU

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 回收。




你可能感兴趣的:(Android Universal Image Loader 源码分析(五))