ImageLoader简单使用

ImageLoader的二级分包主要由cache、core和utils组成,三者分别负责缓存、核心加载类和工具类。
在核心加载类core的子分包中,包括显示、下载、进度监听及属性配置等。

外部调用:

ImageLoader.getInstance().displayImage(url, imageShow);

这里底层代码由displayImage方法负责:

public void displayImage(String uri,ImageAware imageAware, DisplayImageOptions options,ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
}

ImageAware:ImageAware这个类会将ImageView转换成ImageViewAware, ImageViewAware主要是做什么的呢?该类主要是将ImageView进行一个包装,将ImageView的强引用变成弱引用,当内存不足的时候,可以更好的回收ImageView对象,还有就是获取ImageView的宽度和高度。这使得我们可以根据ImageView的宽高去对图片进行一个裁剪,减少内存的使用。

通过memoryCacheKey判断内存缓存中是否有bitmap。

设置图片缓存时要做的事

为了能够选择一个合适的缓存大小给缓存类, 有以下多个因素应该放入考虑范围内,例如:
- 你的设备可以为每个应用程序分配多大的内存?
- 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
- 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
- 图片的尺寸和大小,还有每张图片会占据多少内存空间。
- 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
- 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。

摘自:Android高效加载大图、多图解决方案,有效避免程序OOM

Listview快速滑动不加载图片策略

在PauseOnScrollListener类中,通过监听状态OnScrollListener.SCROLL_STATE_FLING执行imagleloader.pause方法,
最后调用ImageLoaderEngine类的原子变量pause。

LoadAndDisplayImageTask类中:根据原子变量pause的状态对象锁控制阻塞。

/** @return <b>true</b> - if task should be interrupted; <b>false</b> - otherwise */private boolean waitIfPaused() {AtomicBoolean pause = engine.getPause();if (pause.get()) {synchronized (engine.getPauseLock()) {if (pause.get()) {L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);try {engine.getPauseLock().wait();} catch (InterruptedException e) {L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);return true;}L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);}}}return isTaskNotActual();}

PauseOnScrollListener类中:监听滑动状态,并根据scroll状态改变ImageLoaderEngine类的原子变量pause值。

    @Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {switch (scrollState) {case OnScrollListener.SCROLL_STATE_IDLE:imageLoader.resume();break;case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:if (pauseOnScroll) {imageLoader.pause();}break;case OnScrollListener.SCROLL_STATE_FLING:if (pauseOnFling) {imageLoader.pause();}break;}if (externalListener != null) {externalListener.onScrollStateChanged(view, scrollState);}}

AtomicBoolean是java.util.concurrent.atomic包下的原子变量,这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入。

ReentrantLock锁机制

典型写法:

Lock lock = new ReentrantLock();
lock.lock();
try {// update object state
}finally {lock.unlock();
}

Lock是基于JDK层面实现的,通常需要在finally中进行锁的释放,如果忘记就会造成死锁。
ReentrantLock实现Lock接口,所有的同步操作都是依靠AbstractQueuedSynchronizer(队列同步器)实现。

API 如下:public void java.util.concurrent.locks.ReentrantLock.lock()

功能:获取锁。
如果该锁没有被另一个线程保持,则获取该锁并立即返回,并将锁的保持计数器设置为1.
如果当前线程已经保持该锁,则将保持计数加1,并且该方法立即返回。
如果该锁被另一个线程保持,则出于线程调度的目的,禁用该线程,并且在获得锁之前,该线程一直处于休眠状态,此时锁保持计数被设置为1.

公平锁(FairSync)与非公平锁(NonfairSync):如果获取一个锁是按照请求的顺序得到的,那么就是公平锁,否则就是非公平的。

  • 公平锁保证一个阻塞的线程最终能够获得锁,因为是有序的,所以总是可以按照请求的顺序获得锁。
  • 不公平锁意味着后请求锁的线程可能在其前面排列的休眠线程恢复前拿到锁,这样就有可能提高并发的性能。这是因为通常情况下挂起的线程重新开始与它真正开始运行,二者之间会产生严重的延时。因此非公平锁就可以利用这段时间完成操作。这是非公平锁在某些时候比公平锁性能要好的原因之一。

详细的介绍我们在另一篇博客里阐述。todo

内存缓存策略

LruMemoryCache

这个类是通过LinkedHashMap有序的保存着强引用bitmap,是Android-Universal-Image-Loader框架默认的缓存类。

特点
- 采用强引用方式保存bitmap;
- 如果超出最大缓存字节限制,则会循环遍历并删除map的第一位(即删除最早缓存的,因此要有序);

在开源库中,DefaultConfigurationFactory类下的createMemoryCache方法配置缓存类的初始化。

    /*** Creates default implementation of {@link MemoryCache} - {@link LruMemoryCache}<br />* Default cache size = 1/8 of available app memory.*/public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {if (memoryCacheSize == 0) {ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);//获取可使用的最大内存,单位MBint memoryClass = am.getMemoryClass();if (hasHoneycomb() && isLargeHeap(context)) {memoryClass = getLargeMemoryClass(am);}//将最大内存的1/8转变成字节单位。memoryCacheSize = 1024 * 1024 * memoryClass / 8;}return new LruMemoryCache(memoryCacheSize);}

1MB = 1024KB = 1024* 1024B(字节)。1B = 8bit(8位,即8位二进制数)。

“删除最早缓存的”代码如下:

/*** 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.*/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;}//这是在size > maxSize的情况下,通过Map.entrySet使用iterator遍历key和value,每次都是先获取第一个。remove之后循环下一次。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);}}}

在size > maxSize的情况下,通过Map.entrySet使用iterator遍历key和value,每次都是先获取第一个。remove之后循环下一次。

UsingFreqLimitedMemoryCache

这个类是通过对已存储的bitmap的使用率进行比对,从而移除最小使用率的bitmap,达到控制内存的目的。

特点:
- 采用强引用和弱引用相结合的方式存储bitmap,并存储使用次数Integer;
- put时使用次数置零,get时使用次数+1;
- 在超出内存上限时,遍历map比较出最小使用次数的bitmap,然后移除;

UsingFreqLimitedMemoryCache继承了父类LimitedMemoryCache,LimitedMemoryCache继承父类BaseMemoryCache。
UsingFreqLimitedMemoryCache中的数据结构是:

/*** Contains strong references to stored objects (keys) and last object usage date (in milliseconds). If hard cache* size will exceed limit then object with the least frequently usage is deleted (but it continue exist at* {@link #softMap} and can be collected by GC at any time)*/private final Map<Bitmap, Integer> usingCounts = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());

map中的key是bitmap,表示存储的位图;value是Integer,表示使用的次数。
Collections.synchronizedMap,解决了线程安全性问题。 通过将基本的功能从线程安全性中分离开来,允许需要同步的用户可以拥有同步,而不需要同步的用户则不必为同步付出代价。
LimitedMemoryCache中的数据结构是:

private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>());

此类主要实现一些获取可用内存字节和移除使用率最低的抽象方法。(本来我认为此类可舍弃,该类的逻辑代码可在BaseMemoryCache中进行,但是看到LRULimitedMemoryCache类之后,觉得我的思想设计扩展性很不强哦)
BaseMemoryCache中的数据结构是:

private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>());

这里是将value对应的bitmap放在引用的泛型中,并在UsingFreqLimitedMemoryCache类中实现抽象方法createReference()。

因为WeakReference< T >等都是以此形式继承Reference< T >,如此便于扩展。

UsingFreqLimitedMemoryCache中:

Override
protected Reference<Bitmap> createReference(Bitmap value) {return new WeakReference<Bitmap>(value);
}

因此,存储的bitmap是以弱引用的形式存储在hashmap中。

put:

Overridepublic boolean put(String key, Bitmap value) {if (super.put(key, value)) {usingCounts.put(value, 0);return true;} else {return false;}}

这里在对map进行put值的时候,会默认将表示使用次数的Integer设置为0;而在get的时候会将其+1;
在第二行里子类调用了父类的put方法,其中包含了对removeNext()的调用,是为了处理当存储超出上限时移除使用率最小的值。

get:

    @Overridepublic Bitmap get(String key) {Bitmap value = super.get(key);// Increment usage count for value if value is contained in hardCaheif (value != null) {Integer usageCount = usingCounts.get(value);if (usageCount != null) {usingCounts.put(value, usageCount + 1);}}return value;}

removeNext():

    @Overrideprotected 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;}

这里,lastValueUsage表示本次(最后、最新)循环内的使用次数值,minUsageCount不为null时表示上次循环时赋的值。
如果检索出更小使用次数时,则将该循环内的entry.getKey()和entry.getValue()赋值给leastUsedValue和minUsageCount。

LRULimitedMemoryCache

此类和UsingFreqLimitedMemoryCache都是继承父类LimitedMemoryCache,意味着上层的逻辑处理是一样的。

特点:
- 采用强引用和弱引用相结合的方式存储bitmap;
- 在超出内存上限时,遍历map比较出最近最少使用的的bitmap,然后移除;

可以理解为只是将LruMemoryCache中的bitmap由强引用转化为弱引用。

此类的数据结构是:

    /** Cache providing Least-Recently-Used logic */private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true));

该类没有统计使用率的Integer的value,但有LinkedHashMap组成的有序的结构体,LinkedHashMap是一个双向循环链表。
后续会有章节详述Java数据结构及原理,此处略。
removeNext:

    @Overrideprotected Bitmap removeNext() {Bitmap mostLongUsedValue = null;synchronized (lruCache) {//移除最早的那个,因为数据结构是LinkedHashMap,存储是有序的。Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();if (it.hasNext()) {Entry<String, Bitmap> entry = it.next();mostLongUsedValue = entry.getValue();it.remove();}}return mostLongUsedValue;}

既然LRULimitedMemoryCache和UsingFreqLimitedMemoryCache都是继承父类LimitedMemoryCache,我们看看父类的方法吧。

在LimitedMemoryCache中,

    @Overridepublic boolean put(String key, Bitmap value) {boolean putSuccessfully = false;// Try to add value to hard cacheint valueSize = getSize(value);int sizeLimit = getSizeLimit();int curCacheSize = cacheSize.get();if (valueSize < sizeLimit) {while (curCacheSize + valueSize > sizeLimit) {Bitmap removedValue = removeNext();//硬盘缓存移除使用频率最低的bitmapif (hardCache.remove(removedValue)) {curCacheSize = cacheSize.addAndGet(-getSize(removedValue));}}//硬盘缓存添加hardCache.add(value);cacheSize.addAndGet(valueSize);putSuccessfully = true;}// Add value to soft cachesuper.put(key, value);return putSuccessfully;}

在当前已缓存的字节+此次要缓存的bitmap的字节之和大于缓存字节上限时,清除子类指定的bitmap。

FIFOLimitedMemoryCache

此类和UsingFreqLimitedMemoryCache都是继承父类LimitedMemoryCache,意味着上层的逻辑处理是一样的。

特点:
- 采用强引用和弱引用相结合的方式存储bitmap;
- 在超出内存上限时,遍历map比较出最早存储的bitmap,然后移除;

此类的数据结构与之前的不同:

private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());

LinkedList列表先进先出的方式存储bitmap。

Overrideprotected Bitmap removeNext() {return queue.remove(0);}

超出内存存储上限时的处理也是直接移除先进去(第一个)的bitmap。

LargestLimitedMemoryCache

此类和UsingFreqLimitedMemoryCache都是继承父类LimitedMemoryCache,意味着上层的逻辑处理是一样的。

特点:
- 采用强引用和弱引用相结合的方式存储bitmap;
- 在超出内存上限时,遍历map比较出内存占用最大的bitmap,然后移除;

此类的数据结构是:

    /*** Contains strong references to stored objects (keys) and sizes of the objects. If hard cache* size will exceed limit then object with the largest size is deleted (but it continue exist at* {@link #softMap} and can be collected by GC at any time)*/private final Map<Bitmap, Integer> valueSizes = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());

疑问:既然都是继承父类LimitedMemoryCache,也都采用强引用和弱引用相结合的方式实现存储。那么三者最后是将bitmap以强引用bitmap、弱引用bitmap、LinkedList中bitmap这其中哪种方式存储呢?

答:是以子类中createReference(Bitmap value)方法指定的引用类型存储的,目前三者都是以WeakReference弱引用的形式存储在基类BaseMemoryCache定义的HashMap中。
依据:子类中的get和put方法都是调用基类BaseMemoryCache的方法,而子类自己的数据结构以及父类LimitedMemoryCache的是以一种便于操作数据的数据结构中存在的(因为弱引用的生命周期不定)。

LimitedAgeMemoryCache

特点:
- 采用强引用bitmap的key、vaule形式存储bitmap;
- get获取值的时候检测HashMap内的Long类型的时间戳是否超出最大时限,若超出则删除。

结构体如下:

private final Map<String, Long> loadingDates = Collections.synchronizedMap(new HashMap<String, Long>());

get

    @Overridepublic Bitmap get(String key) {Long loadingDate = loadingDates.get(key);Bitmap bitmap = cache.get(key);//在获取值的时候检测时间是否超出缓存时限,若超出则移除。if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {cache.remove(key);loadingDates.remove(key);}return bitmap;}

这里我做了修改,增加了Bitmap bitmap = cache.get(key);

WeakMemoryCache

此类直接继承BaseMemoryCache实现弱引用bitmap存储。

特点:
- 采用弱引用bitmap方式存储在HashMap中。

类结构如下:

/*** Memory cache with {@linkplain WeakReference weak references} to {@linkplain Bitmap bitmaps}<br />* <br />* <b>NOTE:</b> This cache uses only weak references for stored Bitmaps.** @author Sergey Tarasevich (nostra13[at]gmail[dot]com)* @since 1.5.3*/
public class WeakMemoryCache extends BaseMemoryCache {@Overrideprotected Reference<Bitmap> createReference(Bitmap value) {return new WeakReference<Bitmap>(value);}
}

这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉。

LruCache

先看下构造方法:

   /*** LruCache的构造方法:需要传入最大缓存个数*/public LruCache(int maxSize) {// 最大缓存个数小于0,会抛出IllegalArgumentExceptionif (maxSize <= 0) {throw new IllegalArgumentException("maxSize <= 0");}this.maxSize = maxSize;/** 初始化LinkedHashMap* 第一个参数:initialCapacity,初始大小* 第二个参数:loadFactor,负载因子=0.75f* 第三个参数:accessOrder=true,基于访问顺序;accessOrder=false,基于插入顺序*/this.map = new LinkedHashMap<K, V>(0, 0.75f, true);}

这里设置了最大缓存的字节数,一般是16M*1024*1024B,然后初始化了LinkedHashMap的参数,其中accessOrder=true,表示基于访问顺序进行排序的。

new LinkedHashMap

  /*** 给对应key缓存value,该value将被移动到队头。*/public final V put(K key, V value) {if (key == null || value == null) {throw new NullPointerException("key == null || value == null");}V previous;synchronized (this) {// 记录 put 的次数putCount++;// 拿到键值对,计算出在容量中的相对长度,然后加上size += safeSizeOf(key, value);/** 放入 key value* 如果 之前存在key 则返回 之前key 的value* 记录在 previous*/previous = map.put(key, value);// 如果存在冲突if (previous != null) {// 计算出 冲突键值 在容量中的相对长度,然后减去size -= safeSizeOf(key, previous);}}// 如果上面发生冲突if (previous != null) {/** previous值被剔除了,此次添加的 value 已经作为key的 新值* 告诉 自定义 的 entryRemoved 方法*/entryRemoved(false, key, previous, value);}trimToSize(maxSize);return previous;}

和以往的缓存类相似。在同步方法块内,先计算要put的value的大小(字节),size累加,然后map.put(),该方法的返回值若不为null,表明替换掉同一个key的旧的value值了。并且size减去旧值的大小(字节)。

同步块下,有个entryRemoved方法,这个方法默认没有实现,暂做剔除的自定义处理。

每次put 新值的时候就要检测是否超出最大缓存空间。这个步骤是在trimToSize方法内进行。

trimToSize如下:

 /*** 删除最旧的数据直到剩余的数据的总数以下要求的大小。*/public void trimToSize(int maxSize) {/** 这是一个死循环,* 1.只有 扩容 的情况下能立即跳出* 2.非扩容的情况下,map的数据会一个一个删除,直到map里没有值了,就会跳出*/while (true) {K key;V value;synchronized (this) {// 在重新调整容量大小前,本身容量就为空的话,会出异常的。if (size < 0 || (map.isEmpty() && size != 0)) {throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");}// 如果是 扩容 或者 map为空了,就会中断,因为扩容不会涉及到丢弃数据的情况if (size <= maxSize || map.isEmpty()) {break;}//剩下的情况就是 if (size > maxSize && !map.isEmpty())即存储已超出最大缓存空间Map.Entry<K, V> toEvict = map.entrySet().iterator().next();key = toEvict.getKey();value = toEvict.getValue();map.remove(key);// 拿到键值对,计算出在容量中的相对长度,然后减去。size -= safeSizeOf(key, value);// 添加一次收回次数evictionCount++;}/** 将最后一次删除的最少访问数据回调出去*/entryRemoved(true, key, value, null);}}

这里检测插入的值是否超出最大缓存空间,如果超出了就会执行移除操作。

while循环中遍历进行,符合条件(超出最大缓存空间时)就会执行 map.remove(key)。由于LinkedHashMap是按照访问顺序排序的,所以此缓存策略是按照“最近最少使用”算法(在链表的尾部是最近刚刚使用的结点,在链表的头部是是最近最少使用的结点)进行剔除的。

为了实现多线程中的同步,对缓存的操作是放在同步块中进行的。

我们再看下get方法:

 /*** 根据key查询缓存,如果存在于缓存或者被create方法创建了。* 如果值返回了,那么它将被移动到双向循环链表的的尾部。* 如果如果没有缓存的值,则返回null。*/public final V get(K key) {if (key == null) {throw new NullPointerException("key == null");}V mapValue;synchronized (this) {// LinkHashMap 如果设置按照访问顺序的话,这里每次get都会重整数据顺序mapValue = map.get(key);// 计算 命中次数if (mapValue != null) {hitCount++;return mapValue;}// 计算 丢失次数missCount++;}/** 官方解释:* 尝试创建一个值,这可能需要很长时间,并且Map可能在create()返回的值时有所不同。如果在create()执行的时* 候,一个冲突的值被添加到Map,我们在Map中删除这个值,释放被创造的值。*/V createdValue = create(key);if (createdValue == null) {return null;}/**************************** 不覆写create方法走不到下面 ****************************//** 正常情况走不到这里* 走到这里的话 说明 实现了自定义的 create(K key) 逻辑* 因为默认的 create(K key) 逻辑为null*/synchronized (this) {// 记录 create 的次数createCount++;// 将自定义create创建的值,放入LinkedHashMap中,如果key已经存在,会返回 之前相同key 的值mapValue = map.put(key, createdValue);// 如果之前存在相同key的value,即有冲突。if (mapValue != null) {/** 有冲突* 所以 撤销 刚才的 操作* 将 之前相同key 的值 重新放回去*/map.put(key, mapValue);} else {// 拿到键值对,计算出在容量中的相对长度,然后加上size += safeSizeOf(key, createdValue);}}// 如果上面 判断出了 将要放入的值发生冲突if (mapValue != null) {/** 刚才create的值被删除了,原来的 之前相同key 的值被重新添加回去了* 告诉 自定义 的 entryRemoved 方法*/entryRemoved(false, key, createdValue, mapValue);return mapValue;} else {// 上面 进行了 size += 操作 所以这里要重整长度trimToSize(maxSize);return createdValue;}}

通篇的代码逻辑很复杂,简单说就是如果get不到值,可以在create(key)里自定义一个value,然后put到map中。如果此时检测到map中有该key的旧值,就撤销put之前create的值。同样的,对于所有要剔除的操作都要走entryRemoved方法。

注意:网上下载的源码里的汉语注释,不敢恭维。

DiskLruCache

DiskLruCache构造方法:
DiskLruCache类是因为是常量类,不能通过构造方法实例化,这里通过open方法获取DiskLruCache实例。

    /*** Opens the cache in {@code directory}, creating a cache if none exists* there.** @param directory a writable directory,for example," /sdcard/Android/data/<application package>/cache"* @param valueCount the number of values per cache entry. Must be positive.* @param maxSize the maximum number of bytes this cache should use to store* @param maxFileCount the maximum file count this cache should store* @throws IOException if reading or writing the cache directory fails*/public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount)throws IOException {...}

open()方法接收四个参数,第一个参数指定的是数据的缓存地址,第二个参数指定当前应用程序的版本号,第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1,第四个参数指定最多可以缓存多少字节的数据。

这里缓存的位置通常存放在 /sdcard/Android/data//cache 这个路径下面,如果没有SD卡或被移除了,可以采用如下代码设置缓存地址。

/*** 获取本地文件* @param uniqueName 子目录* @return*/public File getDiskCacheDir(String uniqueName) {String cachePath;if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())|| !Environment.isExternalStorageRemovable()) {cachePath = App.getAppContext().getExternalCacheDir().getPath();} else {cachePath = App.getAppContext().getCacheDir().getPath();}return new File(cachePath + File.separator + uniqueName);}

对于版本号appVersion这个参数,每当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。

外部实例化DiskLruCache如下:

DiskLruCache mDiskLruCache = null;
try {File cacheDir = getDiskCacheDir(context, "bitmap");if (!cacheDir.exists()) {cacheDir.mkdirs();}mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {e.printStackTrace();
}

DiskLruCache写入:
DiskLruCache写入的操作是借助DiskLruCache.Editor这个类完成的,需要调用DiskLruCache的edit()方法来获取实例。

DiskLruCache.Editor editor = mDiskLruCache.edit(key);  

由于缓存的key要和缓存的value一一对应,这里通常是将URL通过MD5编码后作key。

MD5编码如下:

  /*** MD5 算法* @param key* @return*/public String hashKeyForDisk(String key) {String cacheKey;try {final MessageDigest mDigest = MessageDigest.getInstance("MD5");mDigest.update(key.getBytes());cacheKey = bytesToHexString(mDigest.digest());} catch (NoSuchAlgorithmException e) {cacheKey = String.valueOf(key.hashCode());}return cacheKey;}private String bytesToHexString(byte[] bytes) {StringBuilder sb = new StringBuilder();for (int i = 0; i < bytes.length; i++) {String hex = Integer.toHexString(0xFF & bytes[i]);if (hex.length() == 1) {sb.append('0');}sb.append(hex);}return sb.toString();}

具体编码算法,后续再研究。

获取editor实例后,通过newOutputStream()方法可获得一个输出流OutputStream。然后将此输出流传入网络下载中,即可实现在文件下载的过程中,将资源缓存到本地。

完整的缓存过程如下:

new Thread(new Runnable() {  @Override  public void run() {  try {  String imageUrl = "http://a3.topitme.com/8/2d/b7/1128528404720b72d8o.jpg";  String key = hashKeyForDisk(imageUrl);  DiskLruCache.Editor editor = mDiskLruCache.edit(key);  if (editor != null) {  OutputStream outputStream = editor.newOutputStream(0);  if (downloadImg(imageUrl, outputStream)) {  editor.commit();  } else {  editor.abort();  }  }  mDiskLruCache.flush();  } catch (IOException e) {  e.printStackTrace();  }  }
}).start();

newOutputStream()方法接收一个index参数,由于前面在设置valueCount的时候指定的是1,所以这里index传0就可以了(即索引为0)。
这里调用一下commit()方法进行提交才能使写入生效,调用abort()方法的话则表示放弃此次写入。

mDiskLruCache.flush()方法是用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。前面在讲解写入缓存操作的时候我有调用过一次这个方法,但其实并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间。比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。

DiskLruCache读取:

因为写入缓存的时候是通过MD5编码之后的key作为唯一性标识的,读取的时候也是首先要将URL转码成Key。

String imageUrl = "http://a3.topitme.com/8/2d/b7/1128528404720b72d8o.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); 

这里通过获得DiskLruCache.Snapshot实例,然后调用其getInputStream()就可获得输入流,进而转发成对应的文件,如bitmap。这里getInputStream(0)的参数和newOutputStream(0)的参数是一一对应的。

完整的读取代码如下:

try {  String imageUrl = "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg";  String key = hashKeyForDisk(imageUrl);  DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);  if (snapShot != null) {  InputStream is = snapShot.getInputStream(0);  Bitmap bitmap = BitmapFactory.decodeStream(is);  mImage.setImageBitmap(bitmap);  }
} catch (IOException e) {  e.printStackTrace();
}  

我们在DiskLruCache源码中,会看到其也用到了LinkedHashMap这个结构体

private final LinkedHashMap<String, Entry> lruEntries =new LinkedHashMap<String, Entry>(0, 0.75f, true);

这也是基于访问顺序的“最近最少使用算法”,其中的value是Entry类,表示文件的实体类。因此DiskLruCache和LruCache底层思想都是相同的,只是存储的对象有所不同。

LRU算法(Least Recently Used )

缓存主要由LruCache这个类实现,LRU是Least Recently Used 近期最少使用算法,多用于清理长时间未使用的资源。

通过查看LinkedHashMap源码可知:

 /*** Constructs an empty <tt>LinkedHashMap</tt> instance with the* specified initial capacity, load factor and ordering mode.** @param  initialCapacity the initial capacity* @param  loadFactor      the load factor* @param  accessOrder     the ordering mode - <tt>true</tt> for*         access-order, <tt>false</tt> for insertion-order* @throws IllegalArgumentException if the initial capacity is negative*         or the load factor is nonpositive*/public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}

boolean值 accessOrder参数为true表示按照使用顺序排列,为false表示按照插入顺序排列。

当accessOrder 设置为 true时,每当我们更新(即调用put方法)或访问(即调用get方法)map中的结点时,LinkedHashMap内部都会将这个结点移动到链表的尾部,因此,在链表的尾部是最近刚刚使用的结点,在链表的头部是是最近最少使用的结点,当我们的缓存空间不足时,就应该持续把链表头部结点移除掉,直到有剩余空间放置新结点。

引用

Android-Universal-Image-Loader
LruCache 源码解析
DiskLruCache

Android-Universal-Image-Loader源码知识笔记相关推荐

  1. android 三方_面试官送你一份Android热门三方库源码面试宝典及学习笔记

    前言 众所周知,优秀源码的阅读与理解是最能提升自身功力的途径,如果想要成为一名优秀的Android工程师,那么Android中优秀三方库源码的分析和理解则是必备技能.就拿比较热门的图片加载框架Glid ...

  2. Android Dialer,Mms,Contacts源码修改笔记,移动端混合开发经验

    ②在AndroidManifest.xml中修改相应Activity的theme <activity android:name=".HomeActivity" android ...

  3. Android TV TIF源码阅读笔记

                                   Android TV TIF源码阅读笔记 1.SystemSever.java if (mPackageManager.hasSystem ...

  4. android源码阅读笔记1-配置源码路径/阅读源码方法讨论

    开始之前 android studio中配置android源码路径 android studio中有源码的路径,你只需要打开SDK Manager下载源码然后重启android studio即可查看源 ...

  5. android universal image loader 缓冲原理详解

    1. 功能介绍 1.1 Android Universal Image Loader Android Universal Image Loader 是一个强大的.可高度定制的图片缓存,本文简称为UIL ...

  6. Android contacts 联系人 通讯录 源码 完全解析

    Android contacts 联系人 通讯录 源码 完全解析 1简介 2软件架构 3各功能模块分析 1联系人数据的显示 1联系人列表显示 2联系人详细信息数据的显示 2联系人数据的编辑和存储 1编 ...

  7. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  8. iOS WebviewJavascriptBridge 源码研读笔记

    这两天接近元旦,事情稍微少些,有些时间,索性写点什么,就从最擅长的iOS混合开发写起了,由于iOS开发经验不到四年吧,期间还搞了一年半的前端,有些知识可能还是积累的不足,能力不足,水平有限,可能有谬误 ...

  9. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http:// ...

最新文章

  1. (四)Asp.net web api中的坑-【api的返回值】
  2. 企业IT运维的“安全终结者”-堡垒机指南
  3. Java 动态代理原理
  4. 前端发给后台的参数中含有中文
  5. 优化 WordPress 后台设置教程
  6. 买茶叶想到的哪个比较便宜 x1/y1 x2/y2 x代表多少钱 y代表 多少克 无聊的试炼...
  7. 21. Kotlin学习笔记 (一) 基本语法
  8. Can't use Subversion command line client: svn Probably the path to Subversion executable is wrong
  9. 【Java】JDBC基础使用教程
  10. 搜集的思源黑体相关的资料
  11. uniapp 小程序列表懒加载
  12. 基于IMDb数据集的情感分析(TF-IDF与机器学习实现)
  13. 【车道线检测论文阅读笔记——经典论文粗读汇总】
  14. 互联网高手教你如何搜集你想要的信息
  15. B. Ternary Sequence
  16. 对MYSQL的explain中extra using where的理解
  17. 2019届校招实习生惨痛经历
  18. 程序员年底好找工作吗?
  19. 编程规范(阿里巴巴)
  20. No module named ‘torchvision.models.feature_extraction‘

热门文章

  1. 《统计学习方法(第2版)》李航 第19章 马尔可夫蒙特卡罗法 MCMC 思维导图笔记 及 课后全部习题答案(步骤详细, 包含Metropolis算法,吉布斯算法代码实现)第十九章
  2. 助眠好物分享,睡眠不好的人一定要了解的东西
  3. Flutter ios14版本启动或页面闪退
  4. OceanBase 从0到1数据库内核实战教程学习笔记 - 8.MiniOB Drop Table 解析
  5. JavaScript:打印五行五列星星
  6. PS 滤镜算法原理——曝光过度
  7. 计算遥感影像变异系数以及像元值变化趋势的小软件
  8. 轻量2核4G5M腾讯云服务器性能评测(必看)
  9. 自建机房跟IDC运营机房差异比较
  10. CSS中如何去掉li标签前面的小圆点