背景
在我写下这篇博客的时候,我还是一名二本学校计算机专业大四的应届毕业生,自学Android开发快两年了,这两年的时间里面,其实真的是感慨万千,两年的时间里面,Android的很多事情也都见识过了,其实Android对于新手入门在就业方面是相当不友好的事情。都说第一个吃螃蟹的人最好,是的,Android从2012年那段时间火起来,那时候Android开发会个ListView都被大厂强着要,时过境迁,现在随着大批中小创业公司的倒台,微信一个App的小程序都垄断绝大部分应用,再加上在大前端 WebApp的出现,混合开发,跨平台等开发的出现,现在很多初级中级Android程序员真没有就业出路,一个岗位企业面对成百上千的开发者应聘,当然也就挑挑拣拣,面试的题目也算是越来越难,从Java基础到多线程,到开源框架的源码设计,等等都会考察到,没办法上了这条贼船,也只能坐下去了,下面先带大家了解第一个开源框架ImageLoader 相信这个开源框架应该是最常用也是用到最多的开源框架了。
一、源码框架
关于这个开源框架的项目架构我直接上图更加清晰,可以看到分为内存相关,工具类相关,和核心代码相关
二、内存缓存和磁盘缓存的相关
可以看到分为内存缓存和磁盘缓存
三、内存缓存相关的类与操作
下面是内存缓存的每一个类所代表的意思和作用,我们随后将逐个进行每个类每个方法的讲解。
(1) MemoryCahce 接口
接口中定义了正删改查等方法,详情看图
(2)baseMemoryCache实现接口的一个基类
这个基类定义了 一个弱引用还有就是实现增删改查的方法
(3)LimitedMemoryCache
这个类继承了BaseMemoryCache这个类 也就是说也是使用弱引用,限制最大内存为16M,重点在于,添加缓存的逻辑,详细看代码 详情看注解
public abstract class LimitedMemoryCache extends BaseMemoryCache {
private static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16 ;
private static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024 ;
private final int sizeLimit;
private final AtomicInteger cacheSize;
/**
* Contains strong references to stored objects. Each next object is added last. If hard cache size will exceed
* limit then first object is deleted (but it continue exist at {@link #softMap} and can be collected by GC at any
* time)
*/
private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>());
/** @param sizeLimit Maximum size for cache (in bytes) */
public LimitedMemoryCache (int sizeLimit) {
this .sizeLimit = sizeLimit;
cacheSize = new AtomicInteger();
if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {
}
}
@Override
public boolean put (String key, Bitmap value) {
boolean putSuccessfully = false ;
int valueSize = getSize(value);
int sizeLimit = getSizeLimit();
int curCacheSize = cacheSize.get();
if (valueSize < sizeLimit) {
while (curCacheSize + valueSize > sizeLimit) {
Bitmap removedValue = removeNext();
if (hardCache.remove(removedValue)) {
curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
}
}
hardCache.add(value);
cacheSize.addAndGet(valueSize);
putSuccessfully = true ;
}
super .put(key, value);
return putSuccessfully;
}
@Override
public Bitmap remove (String key) {
Bitmap value = super .get(key);
if (value != null ) {
if (hardCache.remove(value)) {
cacheSize.addAndGet(-getSize(value));
}
}
return super .remove(key);
}
@Override
public void clear () {
hardCache.clear();
cacheSize.set(0 );
super .clear();
}
protected int getSizeLimit () {
return sizeLimit;
}
protected abstract int getSize (Bitmap value);
protected abstract Bitmap removeNext ();
}
(4)WeakMemoryCache 只使用弱引用 因为他重写了父类BaseMemoryCache 的createReference 方法 而父类在放入缓存的时候会调用这个方法这样就把强引用弄成弱引用,在内存不够的时候自动被回收,各个方法的详情看注解
public class WeakMemoryCache extends BaseMemoryCache {
@Override
protected Reference<Bitmap> createReference (Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}
(5)UsingFreqLimitedMemoryCache
LimitedMemoryCache这个是一个最近使用最少的缓存策略用一个Map
public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache {
private final Map<Bitmap, Integer> usingCounts = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());
public UsingFreqLimitedMemoryCache (int sizeLimit) {
super (sizeLimit);
}
@Override
public boolean put (String key, Bitmap value) {
if (super .put(key, value)) {
usingCounts.put(value, 0 );
return true ;
} else {
return false ;
}
}
@Override
public Bitmap get (String key) {
Bitmap value = super .get(key);
if (value != null ) {
Integer usageCount = usingCounts.get(value);
if (usageCount != null ) {
usingCounts.put(value, usageCount + 1 );
}
}
return value;
}
@Override
public Bitmap remove (String key) {
Bitmap value = super .get(key);
if (value != null ) {
usingCounts.remove(value);
}
return super .remove(key);
}
@Override
public void clear () {
usingCounts.clear();
super .clear();
}
@Override
protected int getSize (Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext () {
Integer minUsageCount = null ;
Bitmap leastUsedValue = null ;
Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet();
synchronized (usingCounts) {
for (Entry<Bitmap, Integer> entry : entries) {
if (leastUsedValue == null ) {
leastUsedValue = entry.getKey();
minUsageCount = entry.getValue();
} else {
Integer lastValueUsage = entry.getValue();
if (lastValueUsage < minUsageCount) {
minUsageCount = lastValueUsage;
leastUsedValue = entry.getKey();
}
}
}
}
usingCounts.remove(leastUsedValue);
return leastUsedValue;
}
@Override
protected Reference<Bitmap> createReference (Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}
(6)LruMemoryCache最近最少使用内存策略
也是默认的缓存策略,在下一章可能会提到配置参数的时候,其实他就说实现了一个MemoryCache接口 实现增删改查的方法,初始化了一个强引用 LinkedHashMap 这个数据结构的特点就说会记录最近使用的频率具体的方法实现看代码注解
public class LruMemoryCache implements MemoryCache {
private final LinkedHashMap<String, Bitmap> 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 ) {
throw new IllegalArgumentException("maxSize <= 0" );
}
this .maxSize = maxSize;
this .map = new LinkedHashMap<String, Bitmap>(0 , 0.75 f, true );
}
/**
根据键值直接从map中拿到对应大小
*/
@Override
public final Bitmap get (String key) {
if (key == null ) {
throw new NullPointerException("key == null" );
}
synchronized (this ) {
return map.get(key);
}
}
@Override
public final boolean put (String key, Bitmap value) {
if (key == null || value == null ) {
throw new NullPointerException("key == null || value == null" );
}
synchronized (this ) {
size += sizeOf(key, value);
Bitmap previous = map.put(key, value);
if (previous != null ) {
size -= sizeOf(key, previous);
}
}
trimToSize(maxSize);
return true ;
}
/**此函数计算是否超出最大限量,是则删除队尾元素
*/
private void trimToSize (int maxSize) {
while (true ) {
String key;
Bitmap value;
synchronized (this ) {
if (size < 0 || (map.isEmpty() && size != 0 )) {
throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!" );
}
if (size <= maxSize || map.isEmpty()) {
break ;
}
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
if (toEvict == null ) {
break ;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= sizeOf(key, value);
}
}
}
@Override
public final Bitmap remove (String key) {
if (key == null ) {
throw new NullPointerException("key == null" );
}
synchronized (this ) {
Bitmap previous = map.remove(key);
if (previous != null ) {
size -= sizeOf(key, previous);
}
return previous;
}
}
@Override
public Collection<String> keys () {
synchronized (this ) {
return new HashSet<String>(map.keySet());
}
}
@Override
public void clear () {
trimToSize(-1 );
}
/**算出大小
*/
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);
}
}
(7)LRULimitedMemoryCache这个和刚刚LruMemoryCache,基本相似只不过,它继承的是LimitedMemoryCache,使用的是弱引用,这里就不多说了直接看代码详细方法细节看注解
public class LRULimitedMemoryCache extends LimitedMemoryCache {
private static final int INITIAL_CAPACITY = 10 ;
private static final float LOAD_FACTOR = 1.1 f;
/** Cache providing Least-Recently-Used logic */
private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true ));
/** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
public LRULimitedMemoryCache (int maxSize) {
super (maxSize);
}
@Override
public boolean put (String key, Bitmap value) {
if (super .put(key, value)) {
lruCache.put(key, value);
return true ;
} else {
return false ;
}
}
@Override
public Bitmap get (String key) {
lruCache.get(key);
return super .get(key);
}
@Override
public Bitmap remove (String key) {
lruCache.remove(key);
return super .remove(key);
}
@Override
public void clear () {
lruCache.clear();
super .clear();
}
@Override
protected int getSize (Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext () {
Bitmap mostLongUsedValue = null ;
synchronized (lruCache) {
Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
if (it.hasNext()) {
Entry<String, Bitmap> entry = it.next();
mostLongUsedValue = entry.getValue();
it.remove();
}
}
return mostLongUsedValue;
}
/**
* 重写导致弱引用
* @param value
* @return
*/
@Override
protected Reference<Bitmap> createReference (Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}
(8)LimitedAgeMemoryCache
传入一个接口和一个生命值,把时间到的缓存给删除掉,详细看代码方法注解
/*******************************************************************************
* Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package leakcanary.imageloader.cache.memory.impl;
import android.graphics.Bitmap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import leakcanary.imageloader.cache.memory.MemoryCache;
/**当 bitmap加入缓存中的时间超过我们设定的值,将其删除
*/
public class LimitedAgeMemoryCache implements MemoryCache {
private final MemoryCache cache;
private final long maxAge;
private final Map<String, Long> loadingDates = Collections.synchronizedMap(new HashMap<String, Long>());
/**初始化
*/
public LimitedAgeMemoryCache (MemoryCache cache, long maxAge) {
this .cache = cache;
this .maxAge = maxAge * 1000 ;
}
/**
放入调用接口的保存方法 ,然后记录最新的方法
*/
@Override
public boolean put (String key, Bitmap value) {
boolean putSuccesfully = cache.put(key, value);
if (putSuccesfully) {
loadingDates.put(key, System.currentTimeMillis());
}
return putSuccesfully;
}
@Override
public Bitmap get (String key) {
Long loadingDate = loadingDates.get(key);
if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {
cache.remove(key);
loadingDates.remove(key);
}
return cache.get(key);
}
@Override
public Bitmap remove (String key) {
loadingDates.remove(key);
return cache.remove(key);
}
@Override
public Collection<String> keys () {
return cache.keys();
}
@Override
public void clear () {
cache.clear();
loadingDates.clear();
}
}
(9)LargestLimitedMemoryCache
LimitedMemoryCache继承LimitedMemoryCache,其实就是拿一个集合 Map
public class LargestLimitedMemoryCache extends LimitedMemoryCache {
private final Map<Bitmap, Integer> valueSizes = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());
public LargestLimitedMemoryCache (int sizeLimit) {
super (sizeLimit);
}
@Override
public boolean put (String key, Bitmap value) {
if (super .put(key, value)) {
valueSizes.put(value, getSize(value));
return true ;
} else {
return false ;
}
}
@Override
public Bitmap remove (String key) {
Bitmap value = super .get(key);
if (value != null ) {
valueSizes.remove(value);
}
return super .remove(key);
}
@Override
public void clear () {
valueSizes.clear();
super .clear();
}
@Override
protected int getSize (Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext () {
Integer maxSize = null ;
Bitmap largestValue = null ;
Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet();
synchronized (valueSizes) {
for (Entry<Bitmap, Integer> entry : entries) {
if (largestValue == null ) {
largestValue = entry.getKey();
maxSize = entry.getValue();
} else {
Integer size = entry.getValue();
if (size > maxSize) {
maxSize = size;
largestValue = entry.getKey();
}
}
}
}
valueSizes.remove(largestValue);
return largestValue;
}
@Override
protected Reference<Bitmap> createReference (Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}
(10)FIFOLimitedMemoryCache先进先出缓存策略
其实就维护了一个List queue
/*******************************************************************************
* Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package leakcanary.imageloader.cache.memory.impl;
import android.graphics.Bitmap;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import leakcanary.imageloader.cache.memory.LimitedMemoryCache;
/**
* 先进先出缓存策略
* Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to
* exceed size limit. When cache reaches limit size then cache clearing is processed by FIFO principle.<br />
* <br />
* <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of
* Bitmaps (depends on cache size), weak references - for all other cached Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public class FIFOLimitedMemoryCache extends LimitedMemoryCache {
private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());
public FIFOLimitedMemoryCache (int sizeLimit) {
super (sizeLimit);
}
@Override
public boolean put (String key, Bitmap value) {
if (super .put(key, value)) {
queue.add(value);
return true ;
} else {
return false ;
}
}
@Override
public Bitmap remove (String key) {
Bitmap value = super .get(key);
if (value != null ) {
queue.remove(value);
}
return super .remove(key);
}
@Override
public void clear () {
queue.clear();
super .clear();
}
@Override
protected int getSize (Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext () {
return queue.remove(0 );
}
@Override
protected Reference<Bitmap> createReference (Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}
好了所有内存缓存策略的每个方法和实现原理都说了现在看磁盘缓存了
三、磁盘缓存的操作与架构
我们先来看一下磁盘缓存的每个类的架构,后面将针对每个类来进行分析
(1)DiskCache 一个接口定义了文件的各种操作 详细看代码注解
public interface DiskCache {
/**
拿到磁盘的目录路劲
*/
File getDirectory();
/**
根据uri得到文件
*/
File get(String imageUri);
/**
保存图片和url
*/
boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;
/**
保存图片
*/
boolean save(String imageUri, Bitmap bitmap) throws IOException;
/**
根据uri删除.
*/
boolean remove(String imageUri);
/** 关闭资源 */
void close();
/** 清空*/
void clear();
}
(2)FileNameGenerator
根据Uri生成唯一文件命名的接口,下面分别是使用HashCode实现和MD5实现
public interface FileNameGenerator {
/** 生成由URI定义的图像的唯一文件名。 */
String generate(String imageUri);
}
/**
使用hashCode的方式生成唯一文件名
*/
public class HashCodeFileNameGenerator implements FileNameGenerator {
@Override
public String generate (String imageUri) {
return String.valueOf(imageUri.hashCode());
}
}
/**
*根据uri 根据MD5的方式生成为唯一文件名
*/
public class Md5FileNameGenerator implements FileNameGenerator {
private static final String HASH_ALGORITHM = "MD5" ;
private static final int RADIX = 10 + 26 ;
@Override
public String generate (String imageUri) {
byte [] md5 = getMD5(imageUri.getBytes());
BigInteger bi = new BigInteger(md5).abs();
return bi.toString(RADIX);
}
private byte [] getMD5 (byte [] data) {
byte [] hash = null ;
try {
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
digest.update(data);
hash = digest.digest();
} catch (NoSuchAlgorithmException e) {
}
return hash;
}
}
(2)BaseDiskCache 磁盘缓存类的一个比较重要的方法 下面两个子类基本是实现父类的方法
public abstract class BaseDiskCache implements DiskCache {
public static final int DEFAULT_BUFFER_SIZE = 32 * 1024 ;
/** {@value */
public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
/** {@value */
public static final int DEFAULT_COMPRESS_QUALITY = 100 ;
private static final String ERROR_ARG_NULL = " argument must be not null" ;
private static final String TEMP_IMAGE_POSTFIX = ".tmp" ;
protected final File cacheDir;
protected final File reserveCacheDir;
protected final FileNameGenerator fileNameGenerator;
protected int bufferSize = DEFAULT_BUFFER_SIZE;
protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
protected int compressQuality = DEFAULT_COMPRESS_QUALITY;
/** 一个参数 传地址*/
public BaseDiskCache (File cacheDir) {
this (cacheDir, null );
}
/**文件地址 和备用地址 如果为空就传用HashCode生成命名
*/
public BaseDiskCache (File cacheDir, File reserveCacheDir) {
this (cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator());
}
/**
可以传递三个参数的
*/
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;
}
@Override
public File getDirectory () {
return cacheDir;
}
@Override
public File get (String imageUri) {
return getFile(imageUri);
}
@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;
}
@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 {
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;
}
@Override
public boolean remove (String imageUri) {
return getFile(imageUri).delete();
}
@Override
public void close () {
}
@Override
public void clear () {
File[] files = cacheDir.listFiles();
if (files != null ) {
for (File f : files) {
f.delete();
}
}
}
/**
* 将传过来的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);
}
public void setBufferSize (int bufferSize) {
this .bufferSize = bufferSize;
}
public void setCompressFormat (Bitmap.CompressFormat compressFormat) {
this .compressFormat = compressFormat;
}
public void setCompressQuality (int compressQuality) {
this .compressQuality = compressQuality;
}
}
(4)UnlimitedDiskCache
没有限制的磁盘缓存方法,基本上是调用父类的方法就好了
public class UnlimitedDiskCache extends BaseDiskCache {
public UnlimitedDiskCache (File cacheDir) {
super (cacheDir);
}
public UnlimitedDiskCache (File cacheDir, File reserveCacheDir) {
super (cacheDir, reserveCacheDir);
}
public UnlimitedDiskCache (File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
super (cacheDir, reserveCacheDir, fileNameGenerator);
}
}
(5)LimitedAgeDiskCache有限制的磁盘缓存策略 需要传多一个生命值维护一个 Map
public class LimitedAgeDiskCache extends BaseDiskCache {
private final long maxFileAge;
private final Map<File, Long> loadingDates = Collections.synchronizedMap(new HashMap<File, Long>());
public LimitedAgeDiskCache (File cacheDir, long maxAge) {
this (cacheDir, null , DefaultConfigurationFactory.createFileNameGenerator(), maxAge);
}
public LimitedAgeDiskCache (File cacheDir, File reserveCacheDir, long maxAge) {
this (cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxAge);
}
public LimitedAgeDiskCache (File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long maxAge) {
super (cacheDir, reserveCacheDir, fileNameGenerator);
this .maxFileAge = maxAge * 1000 ;
}
@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;
}
@Override
public boolean save (String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
boolean saved = super .save(imageUri, imageStream, listener);
rememberUsage(imageUri);
return saved;
}
@Override
public boolean save (String imageUri, Bitmap bitmap) throws IOException {
boolean saved = super .save(imageUri, bitmap);
rememberUsage(imageUri);
return saved;
}
@Override
public boolean remove (String imageUri) {
loadingDates.remove(getFile(imageUri));
return super .remove(imageUri);
}
@Override
public void clear () {
super .clear();
loadingDates.clear();
}
private void rememberUsage (String imageUri) {
File file = getFile(imageUri);
long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
loadingDates.put(file, currentTime);
}
}
四、总结
(1)ImageLoad的缓存类大概就就介绍这些,其实缓存的原理主要是要理解实现的过程,对于使用的步骤可以结合这个图去记忆。
① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap。
② 内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步。
③ 硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件。
④ 如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步。
⑤ 下载图片:启动异步线程,从数据源下载数据(Web)。
⑥ 若下载成功,将数据同时写入硬盘和缓存,并将Bitmap显示在UI中
(2)内存缓存默认LruMemoryCache ,初始化代码可以如下传入你需要的缓存配置
ImageLoaderConfiguration config = new
ImageLoaderConfiguration .Builder (getApplicationContext())
.maxImageWidthForMemoryCache (800 )
.maxImageHeightForMemoryCache (480 )
.httpConnectTimeout (5000 )
.httpReadTimeout (20000 )
.threadPoolSize (5 )
.threadPriority (Thread.MIN _PRIORITY + 3 )
.denyCacheImageMultipleSizesInMemory ()
.memoryCache (new UsingFreqLimitedCache(2000000 )) // 你可以传入自己的内存缓存
.discCache (new UnlimitedDiscCache(cacheDir)) // 你可以传入自己的磁盘缓存
.defaultDisplayImageOptions (DisplayImageOptions.createSimple ())
.build ()