图片加载框架之UIL

1. 功能介绍

1.1 Android Universal Image Loader

Android Universal Image Loader 是一个强大的、可高度定制的图片缓存,本文简称为UIL
简单的说 UIL 就作了一件事——获取图片并显示在相应的控件上。java

1.2 基本使用

1.2.1 初始化

添加完依赖后在ApplicationActivity中初始化ImageLoader,以下:android

public class YourApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
            // 添加你的配置需求
            .build();
        ImageLoader.getInstance().init(configuration);
    }
}

其中 configuration 表示ImageLoader的配置信息,可包括图片最大尺寸、线程池、缓存、下载器、解码器等等。git

1.2.2 Manifest 配置
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:name=".YourApplication"
        …… >
        ……
    </application>
</manifest>

添加网络权限。若是容许磁盘缓存,须要添加写外设的权限。github

1.2.3 下载显示图片

下载图片,解析为 Bitmap 并在 ImageView 中显示。算法

imageLoader.displayImage(imageUri, imageView);

下载图片,解析为 Bitmap 传递给回调接口。缓存

imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
    @Override
    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
        // 图片处理
    }
});

以上是简单使用,更复杂 API 见本文详细设计性能优化

1.3 特色

  • 可配置度高。支持任务线程池、下载器、解码器、内存及磁盘缓存、显示选项等等的配置。
  • 包含内存缓存和磁盘缓存两级缓存。
  • 支持多线程,支持异步和同步加载。
  • 支持多种缓存算法、下载进度监听、ListView 图片错乱解决等。

2. 整体设计

2.1. 整体设计图

整体设计图
上面是 UIL 的整体设计图。整个库分为ImageLoaderEngineCacheImageDownloaderImageDecoderBitmapDisplayerBitmapProcessor五大模块,其中Cache分为MemoryCacheDiskCache两部分。网络

简单的讲就是ImageLoader收到加载及显示图片的任务,并将它交给ImageLoaderEngineImageLoaderEngine分发任务到具体线程池去执行,任务经过CacheImageDownloader获取图片,中间可能通过BitmapProcessorImageDecoder处理,最终转换为Bitmap交给BitmapDisplayerImageAware中显示。多线程

2.2. UIL 中的概念

简单介绍一些概念,在4\. 详细设计中会仔细介绍。
ImageLoaderEngine:任务分发器,负责分发LoadAndDisplayImageTaskProcessAndDisplayImageTask给具体的线程池去执行,本文中也称其为engine,具体参考4.2.6 ImageLoaderEngine.java并发

ImageAware:显示图片的对象,能够是ImageView等,具体参考4.2.9 ImageAware.java

ImageDownloader:图片下载器,负责从图片的各个来源获取输入流, 具体参考4.2.22 ImageDownloader.java

Cache:图片缓存,分为MemoryCacheDiskCache两部分。

MemoryCache:内存图片缓存,可向内存缓存缓存图片或从内存缓存读取图片,具体参考4.2.24 MemoryCache.java

DiskCache:本地图片缓存,可向本地磁盘缓存保存图片或从本地磁盘读取图片,具体参考4.2.38 DiskCache.java

ImageDecoder:图片解码器,负责将图片输入流InputStream转换为Bitmap对象, 具体参考4.2.53 ImageDecoder.java

BitmapProcessor:图片处理器,负责从缓存读取或写入前对图片进行处理。具体参考4.2.61 BitmapProcessor.java

BitmapDisplayer:Bitmap对象显示在相应的控件ImageAware上, 具体参考4.2.56 BitmapDisplayer.java

LoadAndDisplayImageTask:用于加载并显示图片的任务, 具体参考4.2.20 LoadAndDisplayImageTask.java

ProcessAndDisplayImageTask:用于处理并显示图片的任务, 具体参考4.2.19 ProcessAndDisplayImageTask.java

DisplayBitmapTask:用于显示图片的任务, 具体参考4.2.18 DisplayBitmapTask.java

3. 流程图


上图为图片加载及显示流程图,在 uil 库中给出,这里用中文从新画出。

4. 详细设计

4.1 类关系图

4.2 核心类功能介绍

4.2.1 ImageLoader.java

图片加载器,对外的主要 API,采起了单例模式,用于图片的加载和显示。

主要函数:

(1). getInstance()

获得ImageLoader的单例。经过双层是否为 null 判断提升性能。

(2). init(ImageLoaderConfiguration configuration)

初始化配置参数,参数configurationImageLoader的配置信息,包括图片最大尺寸、任务线程池、磁盘缓存、下载器、解码器等等。
实现中会初始化ImageLoaderEngine engine属性,该属性为任务分发器。

(3). displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

加载并显示图片或加载并执行回调接口。ImageLoader 加载图片主要分为三类接口:

  • displayImage(…) 表示异步加载并显示图片到对应的ImageAware上。
  • loadImage(…) 表示异步加载图片并执行回调接口。
  • loadImageSync(…) 表示同步加载图片。

以上三类接口最终都会调用到这个函数进行图片加载。函数参数解释以下:
uri: 图片的 uri。uri 支持多种来源的图片,包括 http、https、file、content、assets、drawable 及自定义,具体介绍可见ImageDownloader
imageAware: 一个接口,表示须要加载图片的对象,可包装 View。
options: 图片显示的配置项。好比加载前、加载中、加载失败应该显示的占位图片,图片是否须要在磁盘缓存,是否须要在内存缓存等。
listener: 图片加载各类时刻的回调接口,包括开始加载、加载失败、加载成功、取消加载四个时刻的回调函数。
progressListener: 图片加载进度的回调接口。

函数流程图以下:
ImageLoader Display Image Flow Chart

4.2.2 ImageLoaderConfiguration.java

ImageLoader的配置信息,包括图片最大尺寸、线程池、缓存、下载器、解码器等等。

主要属性:

(1). Resources resources

程序本地资源访问器,用于加载DisplayImageOptions中设置的一些 App 中图片资源。

(2). int maxImageWidthForMemoryCache

内存缓存的图片最大宽度。

(3). int maxImageHeightForMemoryCache

内存缓存的图片最大高度。

(4). int maxImageWidthForDiskCache

磁盘缓存的图片最大宽度。

(5). int maxImageHeightForDiskCache

磁盘缓存的图片最大高度。

(6). BitmapProcessor processorForDiskCache

图片处理器,用于处理从磁盘缓存中读取到的图片。

(7). Executor taskExecutor

ImageLoaderEngine中用于执行从源获取图片任务的 Executor。

(18). Executor taskExecutorForCachedImages

ImageLoaderEngine中用于执行从缓存获取图片任务的 Executor。

(19). boolean customExecutor

用户是否自定义了上面的 taskExecutor。

(20). boolean customExecutorForCachedImages

用户是否自定义了上面的 taskExecutorForCachedImages。

(21). int threadPoolSize

上面两个默认线程池的核心池大小,即最大并发数。

(22). int threadPriority

上面两个默认线程池的线程优先级。

(23). QueueProcessingType tasksProcessingType

上面两个默认线程池的线程队列类型。目前只有 FIFO, LIFO 两种可供选择。

(24). MemoryCache memoryCache

图片内存缓存。

(25). DiskCache diskCache

图片磁盘缓存,通常放在 SD 卡。

(26). ImageDownloader downloader

图片下载器。

(27). ImageDecoder decoder

图片解码器,内部可以使用咱们经常使用的BitmapFactory.decode(…)将图片资源解码成Bitmap对象。

(28). DisplayImageOptions defaultDisplayImageOptions

图片显示的配置项。好比加载前、加载中、加载失败应该显示的占位图片,图片是否须要在磁盘缓存,是否须要在内存缓存等。

(29). ImageDownloader networkDeniedDownloader

不容许访问网络的图片下载器。

(30). ImageDownloader slowNetworkDownloader

慢网络状况下的图片下载器。

4.2.3 ImageLoaderConfiguration.Builder.java 静态内部类

Builder 模式,用于构造参数繁多的ImageLoaderConfiguration
其属性与ImageLoaderConfiguration相似,函数可能是属性设置函数。

主要函数及含义:

(1). build()

按照配置,生成 ImageLoaderConfiguration。代码以下:

public ImageLoaderConfiguration build() {
    initEmptyFieldsWithDefaultValues();
    return new ImageLoaderConfiguration(this);
}
(2). initEmptyFieldsWithDefaultValues()

初始化值为null的属性。若用户没有配置相关项,UIL 会经过调用DefaultConfigurationFactory中的函数返回一个默认值当配置。
taskExecutorForCachedImagestaskExecutorImageLoaderEnginetaskDistributor的默认值以下:

parameters taskDistributor taskExecutorForCachedImages/taskExecutor
corePoolSize 0 3
maximumPoolSize Integer.MAX_VALUE 3
keepAliveTime 60 0
unit SECONDS MILLISECONDS
workQueue SynchronousQueue LIFOLinkedBlockingDeque / LinkedBlockingQueue
priority 5 3

diskCacheFileNameGenerator默认值为HashCodeFileNameGenerator
memoryCache默认值为LruMemoryCache。若是内存缓存不容许缓存一张图片的多个尺寸,则用FuzzyKeyMemoryCache作封装,同一个图片新的尺寸会覆盖缓存中该图片老的尺寸。
diskCache默认值与diskCacheSizediskCacheFileCount值有关,若是他们有一个大于 0,则默认为LruDiskCache,不然使用无大小限制的UnlimitedDiskCache
downloader默认值为BaseImageDownloader
decoder默认值为BaseImageDecoder
详细及其余属性默认值请到DefaultConfigurationFactory中查看。

(3). denyCacheImageMultipleSizesInMemory()

设置内存缓存不容许缓存一张图片的多个尺寸,默认容许。
后面会讲到 View 的 getWidth() 在初始化先后的不一样值与这个设置的关系。

(4). diskCacheSize(int maxCacheSize)

设置磁盘缓存的最大字节数,若是大于 0 或者下面的maxFileCount大于 0,默认的DiskCache会用LruDiskCache,不然使用无大小限制的UnlimitedDiskCache

(5). diskCacheFileCount(int maxFileCount)

设置磁盘缓存文件夹下最大文件数,若是大于 0 或者上面的maxCacheSize大于 0,默认的DiskCache会用LruDiskCache,不然使用无大小限制的UnlimitedDiskCache

4.2.4 ImageLoaderConfiguration.NetworkDeniedImageDownloader.java 静态内部类

不容许访问网络的图片下载器,实现了ImageDownloader接口。
实现也比较简单,包装一个ImageDownloader对象,经过在 getStream(…) 函数中禁止 Http 和 Https Scheme 禁止网络访问,以下:

@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
    switch (Scheme.ofUri(imageUri)) {
        case HTTP:
        case HTTPS:
            throw new IllegalStateException();
        default:
            return wrappedDownloader.getStream(imageUri, extra);
    }
}
4.2.5 ImageLoaderConfiguration.SlowNetworkImageDownloader.java 静态内部类

慢网络状况下的图片下载器,实现了ImageDownloader接口。
经过包装一个ImageDownloader对象实现,在 getStream(…) 函数中当 Scheme 为 Http 和 Https 时,用FlushedInputStream代替InputStream处理慢网络状况,具体见后面FlushedInputStream的介绍。

4.2.6 ImageLoaderEngine.java

LoadAndDisplayImageTaskProcessAndDisplayImageTask任务分发器,负责分发任务给具体的线程池。

主要属性:

(1). ImageLoaderConfiguration configuration

ImageLoader的配置信息,可包括图片最大尺寸、线程池、缓存、下载器、解码器等等。

(2). Executor taskExecutor

用于执行从源获取图片任务的 Executor,为configuration中的 taskExecutor,若是为null,则会调用DefaultConfigurationFactory.createExecutor(…)根据配置返回一个默认的线程池。

(3). Executor taskExecutorForCachedImages

用于执行从缓存获取图片任务的 Executor,为configuration中的 taskExecutorForCachedImages,若是为null,则会调用DefaultConfigurationFactory.createExecutor(…)根据配置返回一个默认的线程池。

(4). Executor taskDistributor

任务分发线程池,任务指LoadAndDisplayImageTaskProcessAndDisplayImageTask,由于只须要分发给上面的两个 Executor 去执行任务,不存在较耗时或阻塞操做,因此用无并发数(Int 最大值)限制的线程池便可。

(5). Map <integer, string="">cacheKeysForImageAwares</integer,>

ImageAware与内存缓存 key 对应的 map,key 为ImageAware的 id,value 为内存缓存的 key。

(6). Map <string, reentrantlock="">uriLocks</string,>

图片正在加载的重入锁 map,key 为图片的 uri,value 为标识其正在加载的重入锁。

(7). AtomicBoolean paused

是否被暂停。若是为true,则全部新的加载或显示任务都会等待直到取消暂停(为false)。

(8). AtomicBoolean networkDenied

是否不容许访问网络,若是为true,经过ImageLoadingListener.onLoadingFailed(…)获取图片,则全部不在缓存中须要网络访问的请求都会失败,返回失败缘由为网络访问被禁止

(9). AtomicBoolean slowNetwork

是不是慢网络状况,若是为true,则自动调用SlowNetworkImageDownloader下载图片。

(10). Object pauseLock

暂停的等待锁,可在engine被暂停后调用这个锁等待。

主要函数:

(1). void submit(final LoadAndDisplayImageTask task)

添加一个LoadAndDisplayImageTask。直接用taskDistributor执行一个 Runnable,在 Runnable 内部根据图片是否被磁盘缓存过肯定使用taskExecutorForCachedImages仍是taskExecutor执行该 task。

(2). void submit(ProcessAndDisplayImageTask task)

添加一个ProcessAndDisplayImageTask。直接用taskExecutorForCachedImages执行该 task。

(3). void pause()

暂停图片加载任务。全部新的加载或显示任务都会等待直到取消暂停(为false)。

(4). void resume()

继续图片加载任务。

(5). stop()

暂停全部加载和显示图片任务并清除这里的内部属性值。

(6). fireCallback(Runnable r)

taskDistributor当即执行某个任务。

(7). getLockForUri(String uri)

获得某个 uri 的重入锁,若是不存在则新建。

(8). createTaskExecutor()

调用DefaultConfigurationFactory.createExecutor(…)建立一个线程池。

(9). getLoadingUriForView(ImageAware imageAware)

获得某个imageAware正在加载的图片 uri。

(10). prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey)

准备开始一个Task。向cacheKeysForImageAwares中插入ImageAware的 id 和图片在内存缓存中的 key。

(11). void cancelDisplayTaskFor(ImageAware imageAware)

取消一个显示任务。从cacheKeysForImageAwares中删除ImageAware对应元素。

(12). denyNetworkDownloads(boolean denyNetworkDownloads)

设置是否不容许网络访问。

(13). handleSlowNetwork(boolean handleSlowNetwork)

设置是否慢网络状况。

4.2.7 DefaultConfigurationFactory.java

ImageLoaderConfigurationImageLoaderEngine提供一些默认配置。

主要函数:

(1). createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType)

建立线程池。
threadPoolSize表示核心池大小(最大并发数)。
threadPriority表示线程优先级。
tasksProcessingType表示线程队列类型,目前只有 FIFO, LIFO 两种可供选择。
内部实现会调用createThreadFactory(…)返回一个支持线程优先级设置,而且以固定规则命名新建的线程的线程工厂类DefaultConfigurationFactory.DefaultThreadFactory

(2). createTaskDistributor()

ImageLoaderEngine中的任务分发器taskDistributor提供线程池,该线程池为 normal 优先级的无并发大小限制的线程池。

(3). createFileNameGenerator()

返回一个HashCodeFileNameGenerator对象,即以 uri HashCode 为文件名的文件名生成器。

(4). createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator, long diskCacheSize, int diskCacheFileCount)

建立一个 Disk Cache。若是 diskCacheSize 或者 diskCacheFileCount 大于 0,返回一个LruDiskCache,不然返回无大小限制的UnlimitedDiskCache

(5). createMemoryCache(Context context, int memoryCacheSize)

建立一个 Memory Cache。返回一个LruMemoryCache,若 memoryCacheSize 为 0,则设置该内存缓存的最大字节数为 App 最大可用内存的 1/8。
这里 App 的最大可用内存也支持系统在 Honeycomb 以后(ApiLevel >= 11) application 中android:largeHeap="true"的设置。

(6). createImageDownloader(Context context)

建立图片下载器,返回一个BaseImageDownloader

(7). createImageDecoder(boolean loggingEnabled)

建立图片解码器,返回一个BaseImageDecoder

(8). createBitmapDisplayer()

建立图片显示器,返回一个SimpleBitmapDisplayer

4.2.8 DefaultConfigurationFactory.DefaultThreadFactory

默认的线程工厂类,为
DefaultConfigurationFactory.createExecutor(…)

DefaultConfigurationFactory.createTaskDistributor(…)
提供线程工厂。支持线程优先级设置,而且以固定规则命名新建的线程。

PS:重命名线程是个很好的习惯,它的一大做用就是方便问题排查,好比性能优化,用 TraceView 查看线程,根据名字很容易分辨各个线程。

4.2.9 ImageAware.java

须要显示图片的对象的接口,可包装 View 表示某个须要显示图片的 View。

主要函数:

(1). View getWrappedView()

获得被包装的 View,图片在该 View 上显示。

(2). getWidth() 与 getHeight()

获得宽度高度,在计算图片缩放比例时会用到。

(3). getId()

获得惟一标识 id。ImageLoaderEngine中用这个 id 标识正在加载图片的ImageAware和图片内存缓存 key 的对应关系,图片请求前会将内存缓存 key 与新的内存缓存 key 进行比较,若是不相等,则以前的图片请求会被取消。这样当ImageAware被复用时就不会因异步加载(前面任务未取消)而形成错乱了。

4.2.10 ViewAware.java

封装 Android View 来显示图片的抽象类,实现了ImageAware接口,利用Reference来 Warp View 防止内存泄露。

主要函数:

(1). ViewAware(View view, boolean checkActualViewSize)

构造函数。
view表示须要显示图片的对象。
checkActualViewSize表示经过getWidth()getHeight()获取图片宽高时返回真实的宽和高,仍是LayoutParams的宽高,true 表示返回真实宽和高。
若是为true会致使一个问题,View在尚未初始化完成时加载图片,这时它的真实宽高为 0,会取它LayoutParams的宽高,而图片缓存的 key 与这个宽高有关,因此当View初始化完成再次须要加载该图片时,getWidth()getHeight()返回的宽高都已经变化,缓存 key 不同,从而致使缓存命中失败会再次从网络下载一次图片。可经过ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory()设置不容许内存缓存缓存一张图片的多个尺寸。

(2). setImageDrawable(Drawable drawable)

若是当前操做在主线程而且 View 没有被回收,则调用抽象函数setImageDrawableInto(Drawable drawable, View view)去向View设置图片。

(3). setImageBitmap(Bitmap bitmap)

若是当前操做在主线程而且 View 没有被回收,则调用抽象函数setImageBitmapInto(Bitmap bitmap, View view)去向View设置图片。

4.2.11 ImageViewAware.java

封装 Android ImageView 来显示图片的ImageAware,继承了ViewAware,利用Reference来 Warp View 防止内存泄露。
若是getWidth()函数小于等于 0,会利用反射获取mMaxWidth的值做为宽。
若是getHeight()函数小于等于 0,会利用反射获取mMaxHeight的值做为高。

4.2.12 NonViewAware.java

仅包含处理图片相关信息却没有须要显示图片的 View 的ImageAware,实现了ImageAware接口。经常使用于加载图片后调用回调接口而不是显示的状况。

4.2.13 DisplayImageOptions.java

图片显示的配置项。好比加载前、加载中、加载失败应该显示的占位图片,图片是否须要在磁盘缓存,是否须要在 memory 缓存等。

主要属性及含义:

(1). int imageResOnLoading

图片正在加载中的占位图片的 resource id,优先级比下面的imageOnLoading高,当存在时,imageOnLoading不起做用。

(2). int imageResForEmptyUri

空 uri 时的占位图片的 resource id,优先级比下面的imageForEmptyUri高,当存在时,imageForEmptyUri不起做用。

(3). int imageResOnFail

加载失败时的占位图片的 resource id,优先级比下面的imageOnFail高,当存在时,imageOnFail不起做用。

(4). Drawable imageOnLoading

加载中的占位图片的 drawabled 对象,默认为 null。

(5). Drawable imageForEmptyUri

空 uri 时的占位图片的 drawabled 对象,默认为 null。

(6). Drawable imageOnFail

加载失败时的占位图片的 drawabled 对象,默认为 null。

(7). boolean resetViewBeforeLoading

在加载前是否重置 view,经过 Builder 构建的对象默认为 false。

(8). boolean cacheInMemory

是否缓存在内存中,经过 Builder 构建的对象默认为 false。

(9). boolean cacheOnDisk

是否缓存在磁盘中,经过 Builder 构建的对象默认为 false。

(10). ImageScaleType imageScaleType

图片的缩放类型,经过 Builder 构建的对象默认为IN_SAMPLE_POWER_OF_2

(11). Options decodingOptions;

为 BitmapFactory.Options,用于BitmapFactory.decodeStream(imageStream, null, decodingOptions)获得图片尺寸等信息。

(12). int delayBeforeLoading

设置在开始加载前的延迟时间,单位为毫秒,经过 Builder 构建的对象默认为 0。

(13). boolean considerExifParams

是否考虑图片的 EXIF 信息,经过 Builder 构建的对象默认为 false。

(14). Object extraForDownloader

下载器须要的辅助信息。下载时传入ImageDownloader.getStream(String, Object)的对象,方便用户本身扩展,默认为 null。

(15). BitmapProcessor preProcessor

缓存在内存以前的处理程序,默认为 null。

(16). BitmapProcessor postProcessor

缓存在内存以后的处理程序,默认为 null。

(17). BitmapDisplayer displayer

图片的显示方式,经过 Builder 构建的对象默认为SimpleBitmapDisplayer

(18). Handler handler

handler 对象,默认为 null。

(19). boolean isSyncLoading

是否同步加载,经过 Builder 构建的对象默认为 false。

4.2.14 DisplayImageOptions.Builder.java 静态内部类

Builder 模式,用于构造参数繁多的DisplayImageOptions
其属性与DisplayImageOptions相似,函数可能是属性设置函数。

4.2.15 ImageLoadingListener.java

图片加载各类时刻的回调接口,可在图片加载的某些点作监听。
包括开始加载(onLoadingStarted)、加载失败(onLoadingFailed)、加载成功(onLoadingComplete)、取消加载(onLoadingCancelled)四个回调函数。

4.2.16 SimpleImageLoadingListener.java

实现ImageLoadingListener接口,不过各个函数都是空实现,表示不在 Image 加载过程当中作任何回调监听。
ImageLoader.displayImage(…)函数中当入参listener为空时的默认值。

4.2.17 ImageLoadingProgressListener.java

Image 加载进度的回调接口。其中抽象函数

void onProgressUpdate(String imageUri, View view, int current, int total)

会在获取图片存储到文件系统时被回调。其中total表示图片总大小,为网络请求结果Response Headercontent-length字段,若是不存在则为 -1。

4.2.18 DisplayBitmapTask.java

显示图片的Task,实现了Runnable接口,必须在主线程调用。

主要函数:

(1) run()

首先判断imageAware是否被 GC 回收,若是是直接调用取消加载回调接口ImageLoadingListener.onLoadingCancelled(…)
不然判断imageAware是否被复用,若是是直接调用取消加载回调接口ImageLoadingListener.onLoadingCancelled(…)
不然调用displayer显示图片,并将imageAware从正在加载的 map 中移除。调用加载成功回调接口ImageLoadingListener.onLoadingComplete(…)

对于 ListView 或是 GridView 这类会缓存 Item 的 View 来讲,单个 Item 中若是含有 ImageView,在滑动过程当中可能由于异步加载及 View 复用致使图片错乱,这里对imageAware是否被复用的判断就能很好的解决这个问题。缘由相似:Android ListView 滑动过程当中图片显示重复错位闪烁问题缘由及解决方案

4.2.19 ProcessAndDisplayImageTask.java

处理并显示图片的Task,实现了Runnable接口。

主要函数:

(1) run()

主要经过 imageLoadingInfo 获得BitmapProcessor处理图片,并用处理后的图片和配置新建一个DisplayBitmapTaskImageAware中显示图片。

4.2.20 LoadAndDisplayImageTask.java

加载并显示图片的Task,实现了Runnable接口,用于从网络、文件系统或内存获取图片并解析,而后调用DisplayBitmapTaskImageAware中显示图片。

主要函数:

(1) run()

获取图片并显示,核心代码以下:

bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
    bmp = tryLoadBitmap();
    ...
    ...
    ...
    if (bmp != null && options.isCacheInMemory()) {
        L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
        configuration.memoryCache.put(memoryCacheKey, bmp);
    }
}
……
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);

从上面代码段中能够看到先是从内存缓存中去读取 bitmap 对象,若 bitmap 对象不存在,则调用 tryLoadBitmap() 函数获取 bitmap 对象,获取成功后若在 DisplayImageOptions.Builder 中设置了 cacheInMemory(true), 同时将 bitmap 对象缓存到内存中。
最后新建DisplayBitmapTask显示图片。

函数流程图以下:
Load and Display Image Task Flow Chart

  1. 判断图片的内存缓存是否存在,若存在直接执行步骤 8;
  2. 判断图片的磁盘缓存是否存在,若存在直接执行步骤 5;
  3. 从网络上下载图片;
  4. 将图片缓存在磁盘上;
  5. 将图片 decode 成 bitmap 对象;
  6. 根据DisplayImageOptions配置对图片进行预处理(Pre-process Bitmap);
  7. 将 bitmap 对象缓存到内存中;
  8. 根据DisplayImageOptions配置对图片进行后处理(Post-process Bitmap);
  9. 执行DisplayBitmapTask将图片显示在相应的控件上。

    流程图能够参见`3\. 流程图`。
(2) tryLoadBitmap()

从磁盘缓存或网络获取图片,核心代码以下:

File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists()) {
    ...
    bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
    ...
    String imageUriForDecoding = uri;
    if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
        imageFile = configuration.diskCache.get(uri);
        if (imageFile != null) {
            imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
        }
    }
    checkTaskNotActual();
    bitmap = decodeImage(imageUriForDecoding);
    ...
}

首先根据 uri 看看磁盘中是否是已经缓存了这个文件,若是已经缓存,调用 decodeImage 函数,将图片文件 decode 成 bitmap 对象; 若是 bitmap 不合法或缓存文件不存在,判断是否须要缓存在磁盘,须要则调用tryCacheImageOnDisk()函数去下载并缓存图片到本地磁盘,再经过decodeImage(imageUri)函数将图片文件 decode 成 bitmap 对象,不然直接经过decodeImage(imageUriForDecoding)下载图片并解析。

(3) tryCacheImageOnDisk()

下载图片并存储在磁盘内,根据磁盘缓存图片最长宽高的配置处理图片。

loaded = downloadImage();

主要就是这一句话,调用下载器下载并保存图片。
若是你在ImageLoaderConfiguration中还配置了maxImageWidthForDiskCache或者maxImageHeightForDiskCache,还会调用resizeAndSaveImage()函数,调整图片尺寸,并保存新的图片文件。

(4) downloadImage()

下载图片并存储在磁盘内。调用getDownloader()获得ImageDownloader去下载图片。

(4) resizeAndSaveImage(int maxWidth, int maxHeight)

从磁盘缓存中获得图片,从新设置大小及进行一些处理后保存。

(5) getDownloader()

根据ImageLoaderEngine配置获得下载器。
若是不容许访问网络,则使用不容许访问网络的图片下载器NetworkDeniedImageDownloader;若是是慢网络状况,则使用慢网络状况下的图片下载器SlowNetworkImageDownloader;不然直接使用ImageLoaderConfiguration中的downloader

4.2.21 ImageLoadingInfo.java

加载和显示图片任务须要的信息。
String uri 图片 url。
String memoryCacheKey 图片缓存 key。
ImageAware imageAware 须要加载图片的对象。
ImageSize targetSize 图片的显示尺寸。
DisplayImageOptions options 图片显示的配置项。
ImageLoadingListener listener 图片加载各类时刻的回调接口。
ImageLoadingProgressListener progressListener 图片加载进度的回调接口。
ReentrantLock loadFromUriLock 图片加载中的重入锁。

4.2.22 ImageDownloader.java

图片下载接口。待实现函数

getStream(String imageUri, Object extra)

表示经过 uri 获得 InputStream。
经过内部定义的枚举Scheme, 能够看出 UIL 支持哪些图片来源。

HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
4.2.23 BaseImageDownloader.java

ImageDownloader的具体实现类。获得上面各类Scheme对应的图片 InputStream。

主要函数

(1). getStream(String imageUri, Object extra)

getStream(…)函数内根据不一样Scheme类型获取图片输入流。

@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
    switch (Scheme.ofUri(imageUri)) {
        case HTTP:
        case HTTPS:
            return getStreamFromNetwork(imageUri, extra);
        case FILE:
            return getStreamFromFile(imageUri, extra);
        case CONTENT:
            return getStreamFromContent(imageUri, extra);
        case ASSETS:
            return getStreamFromAssets(imageUri, extra);
        case DRAWABLE:
            return getStreamFromDrawable(imageUri, extra);
        case UNKNOWN:
        default:
            return getStreamFromOtherSource(imageUri, extra);
    }
}

具体见下面各函数介绍。

(2). getStreamFromNetwork(String imageUri, Object extra)

经过HttpURLConnection从网络获取图片的InputStream。支持 response code 为 3xx 的重定向。这里有个小细节代码以下:

try {
    imageStream = conn.getInputStream();
} catch (IOException e) {
    // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
    IoUtils.readAndCloseStream(conn.getErrorStream());
    throw e;
}

在发生异常时会调用conn.getErrorStream()继续读取 Error Stream,这是为了利于网络链接回收及复用。但有意思的是在 Froyo(2.2) 以前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响链接池,致使链接复用失效,很多库经过在 2.3 以前使用 AndroidHttpClient 解决这个问题。

(3). getStreamFromFile(String imageUri, Object extra)

从文件系统获取图片的InputStream。若是 uri 是 video 类型,则须要单独获得 video 的缩略图返回,不然按照通常读取文件操做返回。

(4). getStreamFromContent(String imageUri, Object extra)

从 ContentProvider 获取图片的InputStream
若是是 video 类型,则先从MediaStore获得 video 的缩略图返回;
若是是联系人类型,经过ContactsContract.Contacts.openContactPhotoInputStream(res, uri)读取内容返回。
不然经过 ContentResolver.openInputStream(…) 读取内容返回。

(5). getStreamFromAssets(String imageUri, Object extra)

从 Assets 中获取图片的InputStream

(6). getStreamFromDrawable(String imageUri, Object extra)

从 Drawable 资源中获取图片的InputStream

(7). getStreamFromOtherSource(String imageUri, Object extra)

UNKNOWN(自定义)类型的处理,目前是直接抛出不支持的异常。

4.2.24 MemoryCache.java

Bitmap 内存缓存接口,须要实现的接口包括 get(…)、put(…)、remove(…)、clear()、keys()。

4.2.25 BaseMemoryCache.java

实现了MemoryCache主要函数的抽象类,以 Map<string, reference<bitmap="">> softMap 作为缓存池,利于虚拟机在内存不足时回收缓存对象。提供抽象函数:</string,>

protected abstract Reference<Bitmap> createReference(Bitmap value)

表示根据 Bitmap 建立一个 Reference <bitmap>作为缓存对象。Reference 能够是 WeakReference、SoftReference 等。</bitmap>

4.2.26 WeakMemoryCache.java

WeakReference<Bitmap>作为缓存 value 的内存缓存,实现了BaseMemoryCache
实现了BaseMemoryCachecreateReference(Bitmap value)函数,直接返回一个new WeakReference<Bitmap>(value)作为缓存 value。

4.2.27 LimitedMemoryCache.java

限制总字节大小的内存缓存,继承自BaseMemoryCache的抽象类。
会在 put(…) 函数中判断整体大小是否超出了上限,是则循环删除缓存对象直到小于上限。删除顺序由抽象函数

protected abstract Bitmap removeNext()

决定。抽象函数

protected abstract int getSize(Bitmap value)

表示每一个元素大小。

4.2.28 LargestLimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除 size 最大的元素,继承自LimitedMemoryCache
实现了LimitedMemoryCache缓存removeNext()函数,老是返回当前缓存中 size 最大的元素。

4.2.29 UsingFreqLimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除使用次数最少的元素,继承自LimitedMemoryCache
实现了LimitedMemoryCache缓存removeNext()函数,老是返回当前缓存中使用次数最少的元素。

4.2.30 LRULimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,继承自LimitedMemoryCache
经过new LinkedHashMap<String, Bitmap>(10, 1.1f, true)做为缓存池。LinkedHashMap 第三个参数表示是否须要根据访问顺序(accessOrder)排序,true 表示根据accessOrder排序,最近访问的跟最新加入的同样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时始终删除第一个元素,即始终删除最近最少访问的元素。
实现了LimitedMemoryCache缓存removeNext()函数,老是返回第一个元素,即最近最少使用的元素。

4.2.31 FIFOLimitedMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除先进入缓存的元素,继承自LimitedMemoryCache
实现了LimitedMemoryCache缓存removeNext()函数,老是返回最早进入缓存的元素。

以上全部LimitedMemoryCache子类都有个问题,就是 Bitmap 虽然经过WeakReference<Bitmap>包装,但实际根本不会被虚拟机回收,由于他们子类中同时都保留了 Bitmap 的强引用。大都是 UIL 早期实现的版本,不推荐使用。

4.2.32 LruMemoryCache.java

限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,实现了MemoryCache。LRU(Least Recently Used) 为最近最少使用算法。

new LinkedHashMap<String, Bitmap>(0, 0.75f, true)做为缓存池。LinkedHashMap 第三个参数表示是否须要根据访问顺序(accessOrder)排序,true 表示根据accessOrder排序,最近访问的跟最新加入的同样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时始终删除第一个元素,即始终删除最近最少访问的元素。

put(…)函数中经过trimToSize(int maxSize)函数判断整体大小是否超出了上限,是则删除第缓存池中第一个元素,即最近最少使用的元素,直到整体大小小于上限。

LruMemoryCache功能上与LRULimitedMemoryCache相似,不过在实现上更加优雅。用简单的实现接口方式,而不是不断继承的方式。

4.2.33 LimitedAgeMemoryCache.java

限制了对象最长存活周期的内存缓存。
MemoryCache的装饰者,至关于为MemoryCache添加了一个特性。以一个MemoryCache内存缓存和一个 maxAge 作为构造函数入参。在 get(…) 时判断若是对象存活时间已经超过设置的最长时间,则删除。

4.2.34 FuzzyKeyMemoryCache.java

能够将某些本来不一样的 key 看作相等,在 put 时删除这些相等的 key。
MemoryCache的装饰者,至关于为MemoryCache添加了一个特性。以一个MemoryCache内存缓存和一个 keyComparator 作为构造函数入参。在 put(…) 时判断若是 key 与缓存中已有 key 通过Comparator比较后相等,则删除以前的元素。

4.2.35 FileNameGenerator.java

根据 uri 获得文件名的接口。

4.2.36 HashCodeFileNameGenerator.java

以 uri 的 hashCode 做为文件名。

4.2.37 Md5FileNameGenerator.java

以 uri 的 MD5 值做为文件名。

4.2.38 DiskCache.java

图片的磁盘缓存接口。

主要函数:

(1) File get(String imageUri)

根据原始图片的 uri 去获取缓存图片的文件。

(2) boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)

保存 imageStream 到磁盘中,listener 表示保存进度且可在其中取消某些段的保存。

(3) boolean save(String imageUri, Bitmap bitmap)

保存图片到磁盘。

(4) boolean remove(String imageUri)

根据图片 uri 删除缓存图片。

(5) void close()

关闭磁盘缓存,并释放资源。

(6) void clear()

清空磁盘缓存。

(7) File getDirectory()

获得磁盘缓存的根目录。

4.2.39 BaseDiskCache.java

一个无大小限制的本地图片缓存,实现了DiskCache主要函数的抽象类。
图片缓存在cacheDir文件夹内,当cacheDir不可用时,则使用备库reserveCacheDir

主要函数:

(1). save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)

先根据imageUri获得目标文件,将imageStream先写入与目标文件同一文件夹的 .tmp 结尾的临时文件内,若未被listener取消且写入成功则将临时文件重命名为目标文件并返回 true,不然删除临时文件并返回 false。

(2). save(String imageUri, Bitmap bitmap)

先根据imageUri获得目标文件,经过Bitmap.compress(…)函数将bitmap先写入与目标文件同一文件夹的 .tmp 结尾的临时文件内,若写入成功则将临时文件重命名为目标文件并返回 true,不然删除临时文件并返回 false。

(3). File getFile(String imageUri)

根据 imageUri 和 fileNameGenerator获得文件名,返回cacheDir内该文件,若cacheDir不可用,则使用备库reserveCacheDir

4.2.40 LimitedAgeDiskCache.java

限制了缓存对象最长存活周期的磁盘缓存,继承自BaseDiskCache
在 get(…) 时判断若是缓存对象存活时间已经超过设置的最长时间,则删除。在 save(…) 时保存当存时间做为对象的建立时间。

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




相关文章
相关标签/搜索