概述
Universal-Image-Loader是经典的图片加载框架,虽然现在该项目不再维护,但对于初学者依旧是值得学习的开源项目之一,本文就该框架的加载图片流程做简要梳理,希望读者有所收获。
该文参考了【codeKK】 Android Universal Image Loader 源码分析一文,该文详细分析了Universal-Image-Loader的设计思想,想深入了解,可以祥读此文。
基本工作流程
首先来看看作者给出的工作流程图:
上图基本的加载流程是当图片请求发出时,首先会从内存缓存中查找该bitmap是否存在,如果存在,则由BitmapProcessor
这个类来进行处理,然后由DisplayDisplaer
这个类来进行显示;
如果内存缓存不存在该图片,那么会从硬盘缓存查找,如果存在,则由ImageDecoder
类来将图片解析为Bitmap
,然后由BitmapProcessor
来进行图片处理,然后由MemoryCache
进行内存缓存,方便下次查找,之后再交由BitmapProcessor
和DisplayDisplaer
来进行处理和最终显示;
如果内存缓存和硬盘缓存都没有找到该图片,那么将由ImageDownloader
来下载图片,然后将该图片由DiskCache
来进行硬盘缓存,缓存好后,之后的流程就和上面的流程一致了。
使用方法
了解了这个基本流程,我们看看Universal-Image-Loader
的基本使用方法:
首先使用时候需要初始化设置:
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
ImageLoader.getInstance().init(configuration);
传递的
this
参数为Context
类型,因此一般会在自定义的Application
中的onCreate
方法中进行初始化:
public class BaseApplication extends Application{
private static Context mAppContext;
@Override
public void onCreate() {
super.onCreate();
//用于初始化默认配置,会配置一些默认参数
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
//将上面初始化的配置传递给ImageLoader
ImageLoader.getInstance().init(configuration);
}
}
在正式加载图片之前,我们还可能需要做一些配置:
public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.bj_weixianshi)//加载中的等待图片
.displayer(new SimpleBitmapDisplayer())//选择的显示类
.showImageOnFail(R.drawable.bj_weixianshi)//显示失败加载的图片
.cacheInMemory(true)//开启内存缓存
.cacheOnDisk(true)//开启硬盘缓存
.bitmapConfig(Bitmap.Config.RGB_565)//图片显示模式
.build();
好了,这样就可以调用真正的加载方法进行展示了:
ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );
方法很简单,第一个参数就是图片URI,支持类型:
"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)
可以看到支持本地和网络的图片uri;
第二个参数就是ImageView
,第三个参数就是上面的simpleOptions
,通过这三个参数,就能完成图片的显示。
流程分析
首先我们来这个这行代码:
ImageLoader.getInstance().init(configuration);
看起来是个单例模式,我们跟踪源码看看:
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
很典型的单例模式,保证全局只有一个ImageLoader,不必重复创建对象,节省内存。
再来看看配置:
public static DisplayImageOptions simpleOptions = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.bj_weixianshi)//加载中的等待图片
...省略
.build();
因为可选参数较多,这里用了建造者模式。
好了,看完上面,分析下加载图片的流程:
ImageLoader.getInstance().displayImage(url, imageView, simpleOptions );
该方法最终会调用到这个方法:
public void displayImage(String uri, //图片的uri,可能来自本地或者网络
ImageAware imageAware,//该接口类型主要封装了一些获取View控件宽高等的常见方法,实现类之一ImageViewAware
DisplayImageOptions options,//展示图片的一些可选项封装类,如是否缓存,同步异步等
ImageSize targetSize,//图片显示的最终尺寸,封装处理类,主要是给图片一个合理的显示尺寸
ImageLoadingListener listener,//图片加载监听,加载开始,加载失败,加载完成,加载取消几种状态
ImageLoadingProgressListener progressListener//图片加载中的监听,可用于显示加载进度条
) {
checkConfiguration();//检查配置是否为null,为Null报异常
if (imageAware == null) {//检查配置是否为null,为Null报异常
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {//检查加载监听,如果为Null,就加载defaultListener
listener = defaultListener;
}
if (options == null) {//加载配置选项,为null,则加载默认的options
options = configuration.defaultDisplayImageOptions;
}
if (TextUtils.isEmpty(uri)) {//检查图片uri是否为空
engine.cancelDisplayTaskFor(imageAware);//空的情况下,取消加载任务
listener.onLoadingStarted(uri, imageAware.getWrappedView());//调用监听方法
if (options.shouldShowImageForEmptyUri()) {//如果配置了空uri情况下的图片显示,那么加载默认图片
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {//否则显示Null
imageAware.setImageDrawable(null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);//加载完成,调用监听
return;
}
if (targetSize == null) {//检查图片大小配置,如果为Null,生成默认的图片大小
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);//根据uri和targetSize来生成内存缓存的Key
//将要显示的View和key加入一个线程同步的缓存Map,key为view的id,value为缓存memoryCacheKey,该map用于判断加载任务是否重复
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());//调用监听方法
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根据Key从内存缓存查找是否有缓存的bitmap
if (bmp != null && !bmp.isRecycled()) {//检查缓存的bitmap是否为null,或是否被回收
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
if (options.shouldPostProcess()) {//是否进行bitmap处理
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));//封装图片加载信息
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));//封装显示和处理任务
if (options.isSyncLoading()) {//如果同步加载,displayTask将进行相关处理,并最终显示图片
displayTask.run();
} else {//异步加载,提交displayTask到任务队列,再进行处理显示
engine.submit(displayTask);
}
} else {//不进行bitmap处理,直接显示图片
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);//加载完成,调用监听
}
} else {//如果内存中没有缓存的bitmap,则根据uri从本地缓存或网络加载
if (options.shouldShowImageOnLoading()) {//如果设置了加载中的图片,则进行显示
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {//如果设置了加载前重置图片,那么给图片设置Null
imageAware.setImageDrawable(null);
}
//封装加载信息
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));//封装加载任务
if (options.isSyncLoading()) {//如果是同步的,那么displayTask立即执行
displayTask.run();
} else {//否则提交displayTask到任务队列再执行
engine.submit(displayTask);
}
}
}
源码中进行了简单注释,整个加载流程就在这一个方法中。
我们将其中几个重要步骤分解来看:
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);//根据Key从内存缓存查找是否有缓存的bitmap
从内存缓存中查找缓存的bitmap,我们看看memoryCache
的具体实现:
final MemoryCache memoryCache;
为ImageLoaderConfiguration
的成员变量,看看它在哪里初始化的:
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this);
}
private void initEmptyFieldsWithDefaultValues() {
...省略
if (memoryCache == null) {//如果没有配置,则创建默认的内存缓存
memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
}
...省略
}
我们再看看这个方法:
public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
if (memoryCacheSize == 0) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
if (hasHoneycomb() && isLargeHeap(context)) {//如果SDK版本为3.0以上,并配置了android:largeHeap="true",可以申请大内存
memoryClass = getLargeMemoryClass(am);
}
memoryCacheSize = 1024 * 1024 * memoryClass / 8;// 1/8可用内存
}
return new LruMemoryCache(memoryCacheSize);//默认实现为LruMemoryCache
}
LruMemoryCache
为接口MemoryCache
的实现类,作者为我们提供了多种MemoryCache
的实现类,来适应不用的内存缓存需求:
有兴趣的同学可以分析下这些缓存实现,本文只分析
LruMemoryCache
,其实该类个人理解是简化版的
LruCache
,看看源码:
/**
* A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
* the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
* become eligible for garbage collection.
*
* NOTE: This cache uses only strong references for stored Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.1
*
* 缓存了一定数量的Bitmap的强引用。当一个Bitmap被访问时,它会移动到序列的队尾。当缓存满时,再添加Bitmap,会将
* 序列头部的Bitmap释放掉,等待GC回收。
*
*/
public class LruMemoryCache implements MemoryCache {
//内部使用LinkedHashMap来保存Bitmap
private final LinkedHashMap map;
//最大缓存的字节数
private final int maxSize;
/** Size of this cache in bytes */
private int size;//当前缓存的字节数
/** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
public LruMemoryCache(int maxSize) {
if (maxSize <= 0) {//检查设置的缓存大小,小于0抛出异常
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//创建一个基于访问顺序的LinkedHashMap,设置false就是默认的插入顺序,这里不讨论LinkedHashMap的实现了
this.map = new LinkedHashMap(0, 0.75f, true);
}
/**
* Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
* of the queue. This returns null if a Bitmap is not cached.
* 根据key获取缓存的Bitmap,如果能够获取到,那么该引用会移动到队列尾部。如果没有缓存就返回Null
*/
@Override
public final Bitmap get(String key) {
if (key == null) {//检查key是否为null,是null抛异常
throw new NullPointerException("key == null");
}
//同步,多线程访问时保证线程安全
synchronized (this) {
return map.get(key);
}
}
/** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue.
* put进的bitmap会放进队尾
*/
@Override
public final boolean put(String key, Bitmap value) {
if (key == null || value == null) {//检查key和value值,如果为null抛出异常
throw new NullPointerException("key == null || value == null");
}
synchronized (this) {//开启同步
size += sizeOf(key, value);//累计计算bitmap的大小
Bitmap previous = map.put(key, value);//加入map
if (previous != null) {//如果之前存在该缓存的bitmap,那么size就不再累计该bitmap的大小
size -= sizeOf(key, previous);//
}
}
//检查size是否在合理的范围内,如果不再做相应处理
trimToSize(maxSize);
return true;
}
/**
* Remove the eldest entries until the total of remaining entries is at or below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
*
* 该方法主要控制缓存的bitmap不超过maxSize,一旦超过就移除最久没用用过的bitmap,直到小于maxSize
*/
private void trimToSize(int maxSize) {
while (true) {//开启循环
String key;
Bitmap value;
synchronized (this) {//开启同步
if (size < 0 || (map.isEmpty() && size != 0)) {//检查size或map是否正常,否则抛出异常
throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
//如果size小于maxSize或者map是空的,那么说明map状态正常,结束循环
if (size <= maxSize || map.isEmpty()) {
break;
}
//查找队列头部存储的bitmap
Map.Entry toEvict = map.entrySet().iterator().next();
if (toEvict == null) {//如果要移除的entry对象为null,那么也结束循环
break;
}
//获取key,和value
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);//移除对应的bitmap
size -= sizeOf(key, value);//重新计算size大小
}
}
}
/** Removes the entry for {@code key} if it exists.
* 根据Key移除对应的bitmap
*/
@Override
public final Bitmap remove(String key) {
if (key == null) {//检查key是否为null,null抛出异常
throw new NullPointerException("key == null");
}
synchronized (this) {//开启线程同步
Bitmap previous = map.remove(key);//移除
if (previous != null) {//如果之前的bitmap存在,那么重新计算size大小
size -= sizeOf(key, previous);
}
return previous;
}
}
@Override
public Collection keys() {//获取key的set集合
synchronized (this) {
return new HashSet(map.keySet());
}
}
@Override//清空map
public void clear() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* Returns the size {@code Bitmap} in bytes.
*
* An entry's size must not change while it is in the cache.
* 计算size方法
*/
private int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
public synchronized final String toString() {
return String.format("LruCache[maxSize=%d]", maxSize);
}
}
设计还是比较清晰简单的,主要是通过LinkedHashMap
来进行存储。
如果内存缓存中获取不到bitmap,那么将根据uri从本地或网络进行加载,所有的信息都封装在LoadAndDisplayImageTask
类中,该类实现了Runnable
接口,因此整个过程是在run
方法进行的:
public void run() {
if (waitIfPaused()) return;//加载任务是否暂停
if (delayIfNeed()) return;//加载任务是否延时
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) {
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}
loadFromUriLock.lock();//获取锁
Bitmap bmp;
try {
checkTaskNotActual();//判断当前任务是否正常,view是否被回收,任务是否正常
//再次从内存中获取缓存的bitmap,个人理解是当请求任务很多时,很可能之前的线程,已经将图片缓存了,所以再次获取
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
bmp = tryLoadBitmap();//获取图片在这个方法里
if (bmp == null) return; // listener callback already was fired
checkTaskNotActual();//判断当前任务是否正常
checkTaskInterrupted();//判断任务是否中断
//是否进行图片预处理
if (options.shouldPreProcess()) {
L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPreProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
}
}
//是否开启了图片缓存,如果开启了,那么进行缓存
if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {//如果存在缓存,那么做标记,说明是内存缓存
loadedFrom = LoadedFrom.MEMORY_CACHE;
L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
}
//是否需要处理图片
if (bmp != null && options.shouldPostProcess()) {
L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPostProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
}
}
checkTaskNotActual();//判断当前任务是否正常
checkTaskInterrupted();//判断任务是否中断
} catch (TaskCancelledException e) {
fireCancelEvent();//如果捕捉到取消任务的异常,那么调用取消监听方法
return;
} finally {
loadFromUriLock.unlock();//释放锁
}
//封装显示图片任务
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);//显示图片
}
这里看下bmp = configuration.memoryCache.get(memoryCacheKey);
,如果if (bmp == null || bmp.isRecycled())
那么就会走到bmp = tryLoadBitmap();
,我们看看这个方法:
/**
* 获取图片
* @return
* @throws TaskCancelledException
*/
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {//根据uri从本地缓存获取图片的缓存文件
File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;//如果图片存在,做本地缓存标记
checkTaskNotActual();//判断任务是否正常
//根据文件路径,解析出bitmap
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
//判断是否bitmap是否存在,如果不存在,那么将从网络进行加载
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;//做网络标记
String imageUriForDecoding = uri;
//如果设置了本地缓存开启,tryCacheImageOnDisk()方法会去网络获取图片,并缓存到本地
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();//检测任务是否正常
//如果设置了本地缓存开启,那么bitmap会从本地加载,如果没有则从网络加载
bitmap = decodeImage(imageUriForDecoding);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);//如果加载失败,设置失败监听
}
}
} catch (IllegalStateException e) {
fireFailEvent(FailType.NETWORK_DENIED, null);//如果加载失败,设置失败监听
} catch (TaskCancelledException e) {
throw e;
} catch (IOException e) {
L.e(e);
fireFailEvent(FailType.IO_ERROR, e);
} catch (OutOfMemoryError e) {
L.e(e);
fireFailEvent(FailType.OUT_OF_MEMORY, e);
} catch (Throwable e) {
L.e(e);
fireFailEvent(FailType.UNKNOWN, e);
}
return bitmap;
}
通过上面的方法分析我们可以大致知道如果开启了本地缓存,那么将会执行tryCacheImageOnDisk()
先下载图片再本地缓存,而如果没用开启缓存,那么会执行decodeImage(imageUriForDecoding)
去网络下载图片,这里我们分析下通过本地缓存和网络加载两种方式,主要了解下两者的实现区别,看看tryCacheImageOnDisk()
方法:
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
loaded = downloadImage();//下载图片,并进行本地缓存
if (loaded) {//判断有没有下载成功
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
if (width > 0 || height > 0) {
L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
// 如果设置了本地图片缓存的最大宽高,默认为0,重新设置图片大小并再次本地缓存
resizeAndSaveImage(width, height);
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
看看downloadImage()
方法:
private boolean downloadImage() throws IOException {
//根据uri来获取流
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {//进行本地缓存
return configuration.diskCache.save(uri, is, this);
} finally {
IoUtils.closeSilently(is);
}
}
}
getStream()
方法默认由BaseImageDownloader
对象实现:
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://content provider
return getStreamFromContent(imageUri, extra);
case ASSETS://assets 目录
return getStreamFromAssets(imageUri, extra);
case DRAWABLE://图片
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
根据uri的前缀来选择不同的加载方式,这里我们看看从getStreamFromNetwork(imageUri, extra)`方法:
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);//创建一个HttpURLConnection对象
int redirectCount = 0;//重定向次数
//最大5次重定向
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
InputStream imageStream;
try {//获取图片流
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
if (!shouldBeProcessed(conn)) {//如果状态码不是200,那么关闭流
IoUtils.closeSilently(imageStream);
throw new IOException("Image request failed with response code " + conn.getResponseCode());
}
//重新封装流
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
至此,就从网络获取到图片流了,然后转换为bitmap,然后进行本地缓存,如果设置了diskCacheSize
或者diskCacheFileCount
,只要满足任意一个条件,那么就会用LruDiskCache
实现,否者用UnlimitedDiskCache
实现,先看看UnlimitedDiskCache
吧,其实该类UnlimitedDiskCache
,就是BaseDiskCache
,这个对本地缓存没有大小限制,所以看看BaseDiskCache
的实现:
//根据图片uri,来保存uri
@Override
public boolean save(String imageUri, Bitmap bitmap) throws IOException {
File imageFile = getFile(imageUri);//获取缓存文件
File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);//临时文件
OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
boolean savedSuccessfully = false;
try {//将bitmap保存到临时文件
savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
} finally {
IoUtils.closeSilently(os);
//如果保存成功,但重命名没有成功则保存失败
if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
savedSuccessfully = false;
}
//如果没有保存成功,则删除临时文件
if (!savedSuccessfully) {
tmpFile.delete();
}
}
bitmap.recycle();
return savedSuccessfully;
}
/** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
//根据uri获取缓存图片文件
protected File getFile(String imageUri) {
String fileName = fileNameGenerator.generate(imageUri);//生成文件名
File dir = cacheDir;//缓存目录
if (!cacheDir.exists() && !cacheDir.mkdirs()) {//如果缓存目录不存在,那么就用备用缓存目录
if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
dir = reserveCacheDir;
}
}
return new File(dir, fileName);
}
主要就是这两个方法了,还是比较简单。
至于LruDiskCache
类的源码,内部其实是由JakeWharton的DiskLruCache
实现的,所以单独另开一篇分析此类,本文分析到此为止。