概述
我要说的就是鼎鼎大名的Universal Image Loader,UIL是非常强大的一款图片加载框架,它不仅支持本地图片加载也支持网络图片加载还支持Android自身的drawable文件夹,asset文件夹里面的图片文件加载,也支持视频文件的缩略图加载。可以说是一款非常全面而强大的图片加载框架。说到这里你是不是也对这个框架非常好奇呢?
首先我们来看看UIL的代码结构。
我们可以看到UIL的代码结构主要分为两大部分,一部分是cache缓存部分,一部分是core主要用于UIL下载,加载和展示图片功能,当然也暴露了许多定制接口供给开发者使用。
Cache硬盘缓存
现在就来看看cache的硬盘缓存部分的源码。
大致上硬盘缓存部分有两种DiskCache,一种是基于BaseDiskCache的LimitedAgeDiskCache和UnlimitedDiskCache,另一种是基于DiskLruCache的LruDiskCache。基于BaseDiskCache的DiskCache没有使用LRU算法,基于DiskLruCache的有使用到LRU算法,以上的DiskCache都实现了DiskCache接口。
public interface DiskCache {
/**
* Returns root directory of disk cache
*
* @return Root directory of disk cache
*/
File getDirectory();
/**
* Returns file of cached image
*
* @param imageUri Original image URI
* @return File of cached image or <b>null</b> if image wasn't cached
*/
File get(String imageUri);
/**
* Saves image stream in disk cache.
* Incoming image stream shouldn't be closed in this method.
*
* @param imageUri Original image URI
* @param imageStream Input stream of image (shouldn't be closed in this method)
* @param listener Listener for saving progress, can be ignored if you don't use
* {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
* progress listener} in ImageLoader calls
* @return <b>true</b> - if image was saved successfully; <b>false</b> - if image wasn't saved in disk cache.
* @throws java.io.IOException
*/
boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;
/**
* Saves image bitmap in disk cache.
*
* @param imageUri Original image URI
* @param bitmap Image bitmap
* @return <b>true</b> - if bitmap was saved successfully; <b>false</b> - if bitmap wasn't saved in disk cache.
* @throws IOException
*/
boolean save(String imageUri, Bitmap bitmap) throws IOException;
/**
* Removes image file associated with incoming URI
*
* @param imageUri Image URI
* @return <b>true</b> - if image file is deleted successfully; <b>false</b> - if image file doesn't exist for
* incoming URI or image file can't be deleted.
*/
boolean remove(String imageUri);
/** Closes disk cache, releases resources. */
void close();
/** Clears disk cache. */
void clear();
}
基于BaseDiskCache部分
可以先来看看BaseDiskCache的代码
public BaseDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
if (cacheDir == null) {
throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
}
if (fileNameGenerator == null) {
throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
}
this.cacheDir = cacheDir;
this.reserveCacheDir = reserveCacheDir;
this.fileNameGenerator = fileNameGenerator;
}
这是BaseDiskCache的构造方法,里面使用了cacheDir,reserveCacheDir,fileNameGenerator三个参数,cacheDir是第一缓存路径,如无意外的话我们就是使用cacheDir作为缓存路径,而reserveCacheDir是备用缓存路径,是在假如第一缓存路径无法使用的情况下,启用备用缓存路径,fileNameGenerator是FileNameGenerator,默认是HashCodeFileNameGenerator,通过文件名的HashCode生成缓存文件名。
@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
File imageFile = getFile(imageUri);
File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
boolean loaded = false;
try {
OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
try {
loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
} finally {
IoUtils.closeSilently(os);
}
} finally {
if (loaded && !tmpFile.renameTo(imageFile)) {
loaded = false;
}
if (!loaded) {
tmpFile.delete();
}
}
return loaded;
}
该Save方法实现的是将InputStream通过IO流生成到一个.tmp后缀的缓存文件。当然缓存文件还是和uri相关联。
@Override
public boolean remove(String imageUri) {
return getFile(imageUri).delete();
}
通过remove方法我们可以看出,通过相关的uri,对其文件删除,本质上是使用了File的接口。
UnlimitedDiskCache基本上就是继承于BaseDiskCache,没有任何改动,所以看看LimitedAgeDiskCache,也是继承于BaseDiskCache,但是有两个特有的成员变量。
private final long maxFileAge;
private final Map<File, Long> loadingDates = Collections.synchronizedMap(new HashMap<File, Long>());
一个是maxFileAge,用于记录LimitedAgeDiskCache的日期限制,单位为秒。还有一个是loadingDates,是一个Map用于记录File和Date之间的联系。
Save方法里面都用到了rememberUsage(String uri);的方法,事实上就是在Save一个文件的时候,把该文件的修改日期设置上当前时间,并且把文件记录添加到loadingDates里面。
private void rememberUsage(String imageUri) {
File file = getFile(imageUri);
long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
loadingDates.put(file, currentTime);
}
在通过get(String uri);的方法的时候,对get的File进行检查,如果超过日期限制就删除该文件。
@Override
public File get(String imageUri) {
File file = super.get(imageUri);
if (file != null && file.exists()) {
boolean cached;
Long loadingDate = loadingDates.get(file);
if (loadingDate == null) {
cached = false;
loadingDate = file.lastModified();
} else {
cached = true;
}
if (System.currentTimeMillis() - loadingDate > maxFileAge) {
file.delete();
loadingDates.remove(file);
} else if (!cached) {
loadingDates.put(file, loadingDate);
}
}
return file;
}
DiskLruCache简介
private void initCache(File cacheDir, File reserveCacheDir, long cacheMaxSize, int cacheMaxFileCount)
throws IOException {
try {
cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize, cacheMaxFileCount);
} catch (IOException e) {
L.e(e);
if (reserveCacheDir != null) {
initCache(reserveCacheDir, null, cacheMaxSize, cacheMaxFileCount);
}
if (cache == null) {
throw e; //new RuntimeException("Can't initialize disk cache", e);
}
}
}
@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
if (editor == null) {
return false;
}
OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);
boolean copied = false;
try {
copied = IoUtils.copyStream(imageStream, os, listener, bufferSize);
} finally {
IoUtils.closeSilently(os);
if (copied) {
editor.commit();
} else {
editor.abort();
}
}
return copied;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
private void trimToFileCount() throws IOException {
while (fileCount > maxFileCount) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}