Universal-Image-Loader从使用到源码分析
来源:互联网 发布:东欧现状知乎 编辑:程序博客网 时间:2024/06/06 02:59
为什么我要写一个已经过时框架的解析框架呢?
那在我自己的项目里面其实也不用这个框架了,一般用的是Glide框架,那为什么我要写这篇文章呢,是因为由于我目前水平有限,对于Glide的源码难以参透,那与其在一个高大上的框架上钻牛角尖,不如把已有的框架进行一个全面的解析,因为图片解析框架无非就是如何高效的加载图片,若参透了之前的框架,那么对新的框架新的技术,也就可以了解其本质,从而更游刃有余的去学习新的技术。
UIL的简单使用
首先导入jar包,在Application的onCreate方法中对UIL进行一些参数配置,如果不想配置,也可以直接使用默认的参数配置。
那么最常用的加载图片方法:
private void showImage() { ImageLoader loader = ImageLoader.getInstance(); //最常用的方法// loader.displayImage(url,iv); //同样我们配置些参数 DisplayImageOptions options = new DisplayImageOptions.Builder() //缓存到内存中 .cacheInMemory(true) //缓存到磁盘中 .cacheOnDisk(true) .build(); loader.displayImage(url,iv,options); }
那么以上就是UIL的基本使用了。
内存缓存
我们一般去加载大量的图片的时候,都会做缓存策略,缓存又分为内存缓存和硬盘缓存。UIL默认使用的内存缓存是LruCache这个类,LRU是Least Recently Used 近期最少使用算法,我们可以给LruCache设定一个缓存图片的最大值,它会自动帮我们管理好缓存的图片总大小是否超过我们设定的值, 超过就删除近期最少使用的图片,而作为一个强大的图片加载框架,Universal-Image-Loader自然也提供了多种图片的缓存策略,下面就来详细的介绍下。
那么首先来分析一下LruCache干了些什么事
package com.nostra13.universalimageloader.cache.memory.impl;import android.graphics.Bitmap;import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;import java.util.Collection;import java.util.HashSet;import java.util.LinkedHashMap;import java.util.Map;/** * 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.<br /> * <br /> * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.8.1 */public class LruMemoryCache implements MemoryCacheAware<String, Bitmap> { 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.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. */ @Override public final Bitmap get(String key) { if (key == 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. */ @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; } /** * 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; } 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); } } } /** Removes the entry for {@code key} if it exists. */ @Override public final void 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); } } } @Override public Collection<String> keys() { synchronized (this) { return new HashSet<String>(map.keySet()); } } @Override public void clear() { trimToSize(-1); // -1 will evict 0-sized elements } /** * Returns the size {@code Bitmap} in bytes. * <p/> * An entry's size must not change while it is in the cache. */ 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); }}
我们可以看到在LruMemoryCache的初始化方法中初始化了一个LinkedHashMap和一个MaxSize,LinkedHashMap传入了true,是访问优先的模式,MaxSize代表了我们想要在内存中缓存所有图片的最大比特。还定义了一个size表示当前的所有图片大小。那么在put方法中,首先调用了一个sizeof()方法,sizeof()方法是用来计算put进去的图片大小,那么size会加上这个sizeof()的值,并且将bitmap放入map中,如果map已经缓存过了,就要减去之前计算得出的size。之后就会调用一个trimtosize(maxsize)方法,这个方法是用来调整map中图片的个数,如果size<=maxsize,那么什么都不做,如果反之,就删除map中的第一个元素,并循环这个过程,直到size<=maxsize。这就是整个LruMemoryCache的原理,并不复杂。
那么我们可以看到最为核心的类是LinkedHashMap,那LinkedHashMap是如何实现Lru算法的呢?
LinkedHashMap解析
LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。
LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。
默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。 让我们来看一下源码
/** * 双向链表的表头元素。 */ private transient Entry<K,V> header; /** * LinkedHashMap的Entry元素。 * 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。 */ private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; …… } // 通过源代码可以看出,在LinkedHashMap的构造方法中,实际调用了父类HashMap的相关构造方法来构造一个底层存放的table数组,并默认是插入顺序public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } //LinkedHashMap重写了init()方法,来完成对header的初始化void init() { header = new Entry<K,V>(-1, null, null, null); header.before = header.after = header; } /* LinkedHashMap并未重写父类HashMap的put方法,而是重写了父类HashMap的put方法调用的子方法void recordAccess(HashMap m) ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。*/public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }//如果为访问优先,那么删除当前节点,添加到链表尾部void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } void addEntry(int hash, K key, V value, int bucketIndex) { // 调用create方法,将新元素以双向链表的的形式加入到映射中。 createEntry(hash, key, value, bucketIndex); // 删除最近最少使用元素的策略定义 Entry<K,V> eldest = header.after; //如果需要删除节点,那么就删除,如果不需要,则判断一下是否要扩容 if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } } void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; // 调用元素的addBrefore方法,将元素加入到哈希、双向链接列表。 e.addBefore(header); size++; } private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }
那么总结一下put方法,如果map中已经有元素存在,并且是访问优先,就删除当前链表上的节点,并将当前节点添加到链表的尾部。
如果不存在,就直接添加到链表的尾部,并判断是否要移除最老的元素,如果需要移除,那么就直接移除,如果不需要移除也判断是否需要扩容。
LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。
OK,对LinkedHashMap是如何实现LruCache算法也有些了解。
那么现在了解了些基本原理,我们来梳理一下UIL的工作流程。
- UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap。
- 内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步
- 硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件
- 如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步
- 下载图片:启动异步线程,从数据源下载数据(Web)
- 若下载成功,将数据同时写入硬盘和缓存,并将Bitmap显示在UI中
- Universal-Image-Loader从使用到源码分析
- Universal Image Loader源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Universal-Image-Loader系列2-源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- Android Universal Image Loader 源码分析
- interceptor(拦截器)与filter(过滤器)的区别
- Android6.0短信装逼生成器
- 学习Javascript闭包(Closure) 个人觉得非常浅显易懂
- 在线笔试-求二叉树中俩个节点的最低共同父节点
- 验证规则
- Universal-Image-Loader从使用到源码分析
- 加班是一把双刃剑
- 阿里云maven仓库配置
- 补码 原码 反码
- Ubuntu开机启动
- 《工业大数据》煎蛋模型
- linux下安装nginx
- SVM的交叉验证和网格收缩
- EasyUI所遇到的坑