Universal-Image-Loader(2)

10.MemoryCache

MemoryCache是實現(xiàn)內(nèi)存緩存的類,不管是內(nèi)存緩存還是磁盤緩存,對于ImageLoader來說都是核心功能,因為關系著圖片的加載速度,因此要深入了解UIL中緩存的工作原理。

回憶一下,之前學過的ImageLoader的緩存實現(xiàn),在之前的實現(xiàn)當中,利用的是LruCache來實現(xiàn)的,而LruCache又是通過accessOrder為true的LinkedHashMap來實現(xiàn)LRU算法的。

在UIL中,不光光有基于LRU算法的LRUMemoryCache,還有FIFOLimitedMemoryCache、LargestLimitedMemoryCache、LimitedAgeMemoryCache、LRULimitedMemoryCache、UsingFreqLimitedMemoryCache和WeakMemoryCache基于各種緩存規(guī)則的MemoryCache,它們實現(xiàn)的核心就是Map,一般LRU算法的都是基于accessOrder為true的LinkedHashMap,其他用的是HashMap,其中WeakMemoryCache的值用的是WeakReference。它們的保證同步方法是通過Collections.synchronizedList(Map...)獲取到同步隊列。UIL中默認配置的是LruMemoryCache.

MemoryCache還有兩個基礎抽象實現(xiàn)類BaseMemoryCache和LimitedMemoryCache,而LimitedMemoryCache又是繼承于BaseMemoryCache,所有類型的MemoryCache類都是實現(xiàn)MemoryCache接口或者繼承于LimitedMemoryCache和BaseMrmoryCache這三者的,大致可以分為是沒有緩存大小限制和有緩存大小限制的,兩者之間的區(qū)別就是,在添加新數(shù)據(jù)時如果緩存的大小超過大小限制閾值時是否刪除Map中的數(shù)據(jù);而如何刪除數(shù)據(jù)的規(guī)則又將有緩存大小限制的MemoryCache分為幾個類。下面是所有類型MemoryCache的分類表格。

MemoryCache子類 實現(xiàn)接口 Or 父類 有無大小限制 刪除規(guī)則
LruMemoryCache MemoryCache LRU最近最少使用
LimitedAgeMemoryCache MemoryCache 存在超過限制時間的
FuzzyKeyMemoryCache MemoryCache put的時候有等價key的
LRULimitedMemoryCache LimitedMemoryCache LRU最近最少使用
FIFOLimitedMemoryCache LlimitedMemoryCache FIFO先入先出
LargestLimitedMemoryCache LimitedMemoryCache Largest最大的
UsingFreqLimitedMemoryCache LimitedMemoryCache 使用次數(shù)最少的
WeakMemoryCache BaseMemoryCache

下面是MemoryCache繼承結構圖,可以幫助我們理解整個MemoryCache的框架

MemoryCache繼承結構圖.PNG

下面就來詳細介紹幾個常用的MemoryCache以及它們的工作流程。

LruMemoryCache

首先是所有類型MemoryCache的接口MemoryCache.java。
它的作用主要是向外提供接口,外界主要通過該接口添加、獲取數(shù)據(jù),不關心內(nèi)部的具體實現(xiàn)。
接口很簡單,就幾個基本的增刪查方法。

public interface MemoryCache {

    boolean put(String key, Bitmap value);

    Bitmap get(String key);

    Bitmap remove(String key);

    Collection<String> keys();

    void clear();
}

然后介紹主要實現(xiàn)MemoryCache接口的無限制的MemoryCache類。

首先是UIL默認使用的LruMemoryCache。
可以看出來,實現(xiàn)的原理跟LruCache是十分相似的。都是利用了accessOrder為true的LinkedHashMap來實現(xiàn)LRU算法,在超過容量之后將刪除Map中最近最少使用的數(shù)據(jù)。其他的操作大部分都是通過LinkedHashMap的同名操作實現(xiàn)的。

這里提前分析一下,既然都有容量限制,都是LRU算法,那么LruMemoryCache和LRULimitedMemoryCache有什么區(qū)別?
答:原理上是一樣的,只不過刪除的順序不一樣:LruMemoryCache在每次的put之后才調(diào)用了trimToSize()保證數(shù)據(jù)不超過限制大小;LRULimitedMemoryCache是確保數(shù)據(jù)不超過限制大小之后才添加進LinkedHashMap當中。
后面再詳細分析LRULimitedMemoryCache的具體實現(xiàn)原理,來看看實現(xiàn)和LruMemoryCache是有什么區(qū)別。

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.75f, true);
    }

    /**
     * Returns the Bitmap for 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 Bitmap for 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 key if it exists. */
    @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); // -1 will evict 0-sized elements
    }

    /**
     * Returns the size Bitmap in bytes.
     * 
     * 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);
    }
}

LRULimitedMemoryCache

直接實現(xiàn)MemoryCache接口的只需要了解LruMemoryCache就足夠了,本節(jié)主要介紹繼承與LimitedMemoryCache的LRULimitedMemoryCache和FIFOLimitedMemoryCache。

這里LRULimitedMemoryCache的繼承結構有3層(除去MemoryCache接口),每層結構都有自己的作用,因此沒有清楚了解繼承結構的話會導致思維混亂,無法理解LRULimitedMemoryCache的工作原理,所以先來看一下繼承結構。

LimitedMemoryCache繼承結構圖.PNG

從圖中可以看出,從BaseMemoryCache到LRULimitedMemoryCache一共有3層,所以問題就出來了,為什么要這么多層,像LruMemoryCache那樣直接實現(xiàn)MemoryCache不可以嗎?
當然可以,但是這樣寫就不符合類的單一職責要求,而且LRULimitedMemoryCache和FIFOLimitedMemoryCache這兩個類只是單單刪除規(guī)則不一樣,如果LRULimitedMemoryCache直接實現(xiàn)MemoryCache的話,那么FIFOLimitedMemoryCache也要實現(xiàn)該類,而且會有大部分的代碼和LRU是相同的,因此將共同的部分抽象出來,使得每個類的職責單一,降低耦合。

在這3層繼承結構中,每一層的工作是:
BaseMemoryCache:有一個Map的內(nèi)部類,該Map的作用是提供最底層的緩存功能,最終存儲數(shù)據(jù)和獲取數(shù)據(jù)實際都是BaseMemoryCache實現(xiàn)的,注意該Map的value并不是一個Bitmap類,而是一個Bitmap的Reference類,通常會傳入Bitmap的WeakReference,這樣的效果是Map中的value隨時都能夠GC回收;
LimitedMemoryCache:該類的作用是保證緩存大小不超過閾值。類內(nèi)部有一個List容器類用于強引用存儲Bitmap,通過該List類來保證正確提供容量限制的功能,即首先在put方法中判斷當前存儲的數(shù)據(jù)是否超過閾值,如果是則調(diào)用抽象方法removeNext()按照一定規(guī)則刪除,再利用List來刪除的Bitmap,如果List刪除成功則說明緩存過該Bitmap,然后再改變緩存大?。?br> LRULimitedMemoryCache和FIFOLimitedMemoryCache等其他類的LimitedMemoryCache:用于提供刪除規(guī)則即實現(xiàn)LimitedMemoryCache的removeNext(),內(nèi)部有用于實現(xiàn)各個規(guī)則的數(shù)據(jù)結構,比如LRU利用accessOrder為true的LinkedHashMap,F(xiàn)IFO利用的是一個LinkedList。

下面分別介紹這三層結構。
1.BaseMemoryCache
首先先介紹BaseMemoryCache,因為LimitedMemoryCache繼承于它,下面是它的實現(xiàn)源碼。

關注的重點有:

  • BaseMemoryCache最主要的就是靠Map成員Map<String, Reference<Bitmap>> softMap實現(xiàn)的,如數(shù)據(jù)的緩存和獲取就是通過該softMap的put和get實現(xiàn)的
  • Map中的value是Bitmap的Reference對象而不是Bitmap,而該Reference通常被傳入WeakReference,因此造成的結果是,softMap中的value會隨時被GC回收
  • put方法中利用抽象方法createReference()來創(chuàng)建Bitmap的引用對象
public abstract class BaseMemoryCache implements MemoryCache {

    /** Stores not strong references to objects */
    private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>());

    @Override
    public Bitmap get(String key) {
        Bitmap result = null;
        Reference<Bitmap> reference = softMap.get(key);
        if (reference != null) {
            result = reference.get();
        }
        return result;
    }

    @Override
    public boolean put(String key, Bitmap value) {
        softMap.put(key, createReference(value));
        return true;
    }

    @Override
    public Bitmap remove(String key) {
        Reference<Bitmap> bmpRef = softMap.remove(key);
        return bmpRef == null ? null : bmpRef.get();
    }

    @Override
    public Collection<String> keys() {
        synchronized (softMap) {
            return new HashSet<String>(softMap.keySet());
        }
    }

    @Override
    public void clear() {
        softMap.clear();
    }

    /** Creates {@linkplain Reference not strong} reference of value */
    protected abstract Reference<Bitmap> createReference(Bitmap value);
}

2.LimitedMemoryCache

之前已經(jīng)說過了LimitedMemoryCache的作用就是實現(xiàn)防止緩存超過閾值的大小,所以關注該類的關注點在如何實現(xiàn)限制緩存大小。

由源碼可以看出:

  • 允許最大緩存為16MB
  • 內(nèi)部有一個List用于以強引用的方式存儲Bitmap
  • 沒有get方法,即get方法是直接調(diào)用的是BaseMemoryCache的get方法
  • 最主要的是put方法,這是實現(xiàn)限制緩存的關鍵,在put方法中,判斷添加后的緩存大小是否超過閾值,如果超過則調(diào)用抽象方法removeNext()獲取緩存中應該被刪除的數(shù)據(jù),比如LRU的removeNext()返回的是最近最少被使用的數(shù)據(jù),F(xiàn)IFO的返回的是最早插入的數(shù)據(jù)。最后還要調(diào)用super.put(key, value)來讓BaseMemoryCache中的softMap存儲數(shù)據(jù),因為前面說過數(shù)據(jù)的存儲和獲取是由BaseMemoryCache提供的
  • 內(nèi)部類List的作用是確保緩存大小的正確變化。因為要確保removeNext()刪除的數(shù)據(jù)是之前緩存的數(shù)據(jù),如果List刪除removeNext()返回的數(shù)據(jù)成功了證明緩存被刪除了,此時緩存大小才會變化,才能使緩存反應真實的變化
  • 在put方法中緩存數(shù)據(jù)超過閾值時,removeNext()會刪除子類比如LRU、FIFO中的緩存數(shù)據(jù),List也會刪除數(shù)據(jù),但注意BaseMemoryCache中的softMap并不會刪除數(shù)據(jù),不必擔心softMap中的緩存量過大,因為WeakReference的對象會隨時被GC回收
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) {
            L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB);
        }
    }

    /**
     * 實現(xiàn)緩存大小限制的關鍵
     */
    @Override
    public boolean put(String key, Bitmap value) {
        boolean putSuccessfully = false;
        // Try to add value to hard cache
        int valueSize = getSize(value);
        int sizeLimit = getSizeLimit();
        int curCacheSize = cacheSize.get();
        if (valueSize < sizeLimit) {
            // 在添加數(shù)據(jù)后大于限制大小時則刪除removeNext()返回的Bitmap
            while (curCacheSize + valueSize > sizeLimit) {
                Bitmap removedValue = removeNext();
                if (hardCache.remove(removedValue)) {
                    curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
                }
            }
            hardCache.add(value);
            cacheSize.addAndGet(valueSize);

            putSuccessfully = true;
        }
        // 最后一定要將數(shù)據(jù)存到sofeMap中
        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();
}

3.LRULimitedMemoryCache

由上面可知,數(shù)據(jù)的存取獲取以及緩存大小的限制在前面兩層結構已經(jīng)實現(xiàn)了,此時LRULimitedMemoryCache以及FIFOLimitedMemoryCache等類的工作就簡單很多了,只需要制定相應的刪除規(guī)則removeNext()就行了,而刪除指定的數(shù)據(jù)使得類本身也需要用一個數(shù)據(jù)結構存儲數(shù)據(jù),畢竟你沒有數(shù)據(jù)怎么確定要刪除的數(shù)據(jù)是哪個啊是吧,下面是源碼。

關注的重點:

  • 用一個accessOrder為true的LinkedHashMap作為存儲數(shù)據(jù)的數(shù)據(jù)結構
  • put方法是先調(diào)用super.put(key, value)再儲存數(shù)據(jù)到本身。這很好理解嘛,前面說過的,數(shù)據(jù)的存儲和獲取是通過BaseMemoryCache實現(xiàn)的,因此只有高層成功存儲了數(shù)據(jù)自身才存儲數(shù)據(jù)
  • get方法與put方法相反,先是調(diào)用本身的get然后再返回父類的get,這里返回的是父類的get為什么還要多此一舉調(diào)用本身的get干什么?這里調(diào)用get主要是為了觸發(fā)accessOrder為true的LinkedHashMap的LRU算法,即把常用的數(shù)據(jù)移到隊列的尾部,然后隊頭剩下的就是不常用的
  • removeNext()直接刪除了LinkedHashMap隊列頭部的數(shù)據(jù),這些數(shù)據(jù)是最近最少使用的數(shù)據(jù),跟accessOrder為true的LinkedHashMap特性有關
  • 抽象方法getSize()用于計算傳入的Bitmap的大小,這跟緩存的存儲和刪除改變的緩存大小密切相關,該方法由子類實現(xiàn)
public class LRULimitedMemoryCache extends LimitedMemoryCache {

    private static final int INITIAL_CAPACITY = 10;
    private static final float LOAD_FACTOR = 1.1f;

    /** 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); // call "get" for LRU logic
        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;
    }

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

4.FIFOLimitedMemoryCache

看完上面之后,F(xiàn)IFOLimiedMemoryCache更容易理解了,只是removeNext()返回的數(shù)據(jù)跟LRU不一樣而已,具體看下面的源碼

  • 內(nèi)部使用的是LinkedList作為存儲數(shù)據(jù)的數(shù)據(jù)結構
  • put,get等方法除了調(diào)用super的put和get方法,本身直接調(diào)用LinkedList的put,get方法用于添加和獲取數(shù)據(jù)
  • removeNext()直接就是刪除隊列的頭部數(shù)據(jù),也就是FIFO原則
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);
    }
}

內(nèi)存緩存小結

緩存是ImageLoader中的一個核心功能,因此需要深刻的理解,重新看一下MemoryCache的框架圖,此時便會有了更深的體會。

MemoryCache繼承機構圖.PNG
  • MemoryCache:整個緩存內(nèi)存框架的最底層的接口,它是外界主要使用的接口
  • LruMemoryCache:直接實現(xiàn)了MemoryCache接口,里面用了accessOrder為true的LinkedHashMap進行數(shù)據(jù)的存取的容器,在每次put方法時會檢查緩存大小是否超出閾值,是的話則根據(jù)LRU算法刪除數(shù)據(jù)
  • BaseMemoryCache:實現(xiàn)了MemoryCache接口,內(nèi)部使用了HashMap<String, Reference<Bitmap>>作為數(shù)據(jù)存儲讀取的容器。需要注意的點是,傳入Map的Reference一般都是WeakReference,即該Bitmap可能隨時被GC回收
  • LimitedMemoryCache:繼承了BaseMemoryCache,該類主要是記錄已經(jīng)緩存的大小cacheSize,還利用List緩存添加過的Bitmap對象,實現(xiàn)了限制緩存大小的功能,即在put方法中發(fā)現(xiàn)添加的數(shù)據(jù)總數(shù)超過閾值16MB時,會調(diào)用抽象方法removeNext()取出要刪除的對象,然后利用內(nèi)部類List檢測該對象是否被緩存過,是則刪除List中的數(shù)據(jù)并改變cacheSize的大小
  • LRULimitedMemoryCache:繼承了LimitedMemoryCache,有前面可知,數(shù)據(jù)的存取以及容量的限制父類已經(jīng)實現(xiàn),該類只是為了提供在緩存超過緩存大小時應該刪除哪個數(shù)據(jù)的規(guī)則即removeNext()方法。內(nèi)部使用了accessOrder為true的LinkedHashMap作為數(shù)據(jù)的存儲(注意這些存儲的數(shù)據(jù)并不是拿來供外界使用的,而是為了確定下一個被刪除的數(shù)據(jù)),然后利用LinkedHashMap的LRU特性在removeNext中返回最近最少使用的數(shù)據(jù)
  • FIFOLimitedMemoryCache:跟LRU一樣,只是內(nèi)部用的是LinkedList實現(xiàn)數(shù)據(jù)的存儲,當然這些數(shù)據(jù)不是供外界使用的,然后再removeNext中返回LinkedList隊頭的數(shù)據(jù),也就是最早插入的數(shù)據(jù),這就是FIFO算法
  • 剩下的MemoryCache跟FIFO,LRU一樣,只是在removeNext中根據(jù)不同的規(guī)則提供了不一樣的被刪除的數(shù)據(jù)

11.DiskCache

ImageLoader中另一個緩存是磁盤緩存,首先還是先回憶一下之前學過的ImageLoader的DiskLruCache的工作原理是什么。

DiskLruCache工作原理:內(nèi)部使用了accessOrder為true的LinkedHashMap作為數(shù)據(jù)的索引,因為DiskLruCache是以文件的形式存儲數(shù)據(jù)的,因此LinkedHaspMap里面并不持有Bitmap對象,實際上持有的是一個Entry內(nèi)部類對象,該對象指向的是一個緩存文件,DiskLruCache對緩存數(shù)據(jù)的添加和獲取其實就是對該緩存文件的寫入和讀取。DiskLruCache對緩存文件的寫入和讀取分別是通過內(nèi)部類對象Editor和Snotshot的OutputStream和InputStream來實現(xiàn)數(shù)據(jù)的寫入和讀取。

接下來我們從源碼中了解DiskCache整個的框架,跟MemoryCache一樣,先從框架圖入手掌握整個繼承結構。
注意圖中還有一個DiskLruCache不屬于繼承結構,其實DiskLruCache就是之前學過的ImageLoader里面的DiskLruCache,但是UIL中也添加了該類,原因是LruDiskCache在內(nèi)部使用了DiskLruCache,簡單來說就是在DiskLruCache外封裝了一層。在ImageLoaderConfiguration中默認使用的LruDiskCache。

DiskCache繼承結構圖.PNG

LruDiskCache

LruDiskCache是直接實現(xiàn)DiskCache的類,首先來看一下DiskCache接口的方法。
可以看到需要實現(xiàn)的方法并不多,主要關注get和save方法。get方法返回的是文件類,即緩存文件,要清楚地意識到凡是磁盤緩存都是用文件來緩存數(shù)據(jù)的;save方法用于將數(shù)據(jù)存儲進與imageUri相對應的文件,其中參數(shù)有一個InputStream,該InputStream是圖片的輸入流,通過該輸入流將數(shù)據(jù)寫入文件當中。

public interface DiskCache {
    /**
     * Returns root directory of disk cache
     */
    File getDirectory();

    /**
     * Returns file of cached image
     */
    File get(String imageUri);

    /**
     * Saves image stream in disk cache.
     * Incoming image stream shouldn't be closed in this method.
     *
     */
    boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;

    boolean save(String imageUri, Bitmap bitmap) throws IOException;

    boolean remove(String imageUri);

    /** Closes disk cache, releases resources. */
    void close();

    void clear();
}


下面介紹LruDiskCache,相對于DiskLruCache簡單很多,原因是內(nèi)部使用DiskLruCache類作為成員,文件數(shù)據(jù)的寫入和讀取其實都是通過DiskLruCache實現(xiàn)的。下面是使用LruDiskCache需要關注的地方。

  • 初始化:直接通過構造方法便可創(chuàng)建,無需DiskLruCache那樣需要通過open,因為在構造方法內(nèi)部調(diào)用了open方法
  • get:因為使用DiskLruCache讀取數(shù)據(jù)是先獲取Snapshot然后,通過Snapshot的InputStream讀取文件數(shù)據(jù)的,在LruDiskCache中封裝了整個過程,直接在save方法內(nèi)部獲取Snapshot并返回文件
  • save:同get方法一樣,LurDiskCache封裝了通過DiskLruCache的Editor的OutputStream寫入數(shù)據(jù)的過程,直接在參數(shù)里面?zhèn)魅胛募妮斎肓鞅憧蓪崿F(xiàn)寫入數(shù)據(jù)的功能,并且在commit方法當中可以確保緩存大小不超過閾值
  • remove:通過DiskLruCache的remove即可

總的來說,其實LruDiskCache就是對DiskLruCache做了一層封裝,實際的數(shù)據(jù)的操作方法還是通過DiskLruCache來實現(xiàn)的。

public class LruDiskCache implements DiskCache {
    /** {@value */
    public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb
    /** {@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 ERROR_ARG_NEGATIVE = " argument must be positive number";

    protected DiskLruCache cache;
    private File reserveCacheDir;

    //用于文件的命名,有Hash和MD5兩種文件名
    protected final FileNameGenerator fileNameGenerator;

    protected int bufferSize = DEFAULT_BUFFER_SIZE;

    protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
    protected int compressQuality = DEFAULT_COMPRESS_QUALITY;

    public LruDiskCache(File cacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize) throws IOException {
        this(cacheDir, null, fileNameGenerator, cacheMaxSize, 0);
    }

    /**
     * @param cacheDir          Directory for file caching
     * @param reserveCacheDir   null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
     * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
     *                          Name generator} for cached files. Generated names must match the regex
     *                          <strong>[a-z0-9_-]{1,64}</strong>
     * @param cacheMaxSize      Max cache size in bytes. <b>0</b> means cache size is unlimited.
     * @param cacheMaxFileCount Max file count in cache. <b>0</b> means file count is unlimited.
     * @throws IOException if cache can't be initialized (e.g. "No space left on device")
     */
    public LruDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize,
            int cacheMaxFileCount) throws IOException {
        if (cacheDir == null) {
            throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
        }
        if (cacheMaxSize < 0) {
            throw new IllegalArgumentException("cacheMaxSize" + ERROR_ARG_NEGATIVE);
        }
        if (cacheMaxFileCount < 0) {
            throw new IllegalArgumentException("cacheMaxFileCount" + ERROR_ARG_NEGATIVE);
        }
        if (fileNameGenerator == null) {
            throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
        }

        if (cacheMaxSize == 0) {
            cacheMaxSize = Long.MAX_VALUE;
        }
        if (cacheMaxFileCount == 0) {
            cacheMaxFileCount = Integer.MAX_VALUE;
        }

        this.reserveCacheDir = reserveCacheDir;
        this.fileNameGenerator = fileNameGenerator;
        initCache(cacheDir, reserveCacheDir, cacheMaxSize, cacheMaxFileCount);
    }

    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 File getDirectory() {
        return cache.getDirectory();
    }

    @Override
    public File get(String imageUri) {
        DiskLruCache.Snapshot snapshot = null;
        try {
            snapshot = cache.get(getKey(imageUri));
            return snapshot == null ? null : snapshot.getFile(0);
        } catch (IOException e) {
            L.e(e);
            return null;
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
        }
    }

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

    @Override
    public boolean save(String imageUri, Bitmap bitmap) throws IOException {
        DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
        if (editor == null) {
            return false;
        }

        OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);
        boolean savedSuccessfully = false;
        try {
            savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
        } finally {
            IoUtils.closeSilently(os);
        }
        if (savedSuccessfully) {
            editor.commit();
        } else {
            editor.abort();
        }
        return savedSuccessfully;
    }

    @Override
    public boolean remove(String imageUri) {
        try {
            return cache.remove(getKey(imageUri));
        } catch (IOException e) {
            L.e(e);
            return false;
        }
    }

    @Override
    public void close() {
        try {
            cache.close();
        } catch (IOException e) {
            L.e(e);
        }
        cache = null;
    }

    @Override
    public void clear() {
        try {
            cache.delete();
        } catch (IOException e) {
            L.e(e);
        }
        try {
            initCache(cache.getDirectory(), reserveCacheDir, cache.getMaxSize(), cache.getMaxFileCount());
        } catch (IOException e) {
            L.e(e);
        }
    }

    private String getKey(String imageUri) {
        return fileNameGenerator.generate(imageUri);
    }

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

BaseDiskCache

UIL中還有另外兩個具體磁盤緩存類LimitedAgeDiskCache和UnlimitedDiskCache,它們的不同點只是一個還刪除緩存文件的過時文件,一個不限制緩存大小。這里只介紹他們的共同父類:BaseDiskCache。

在BaseDiskCache中并沒有使用特殊的數(shù)據(jù)結構來存儲數(shù)據(jù),直接就是通過對文件類的操作來達成使用文件緩存的目的。注意該類并沒有限制緩存大小,下面就簡單看一下BaseDiskCache的save和get方法。

public abstract class BaseDiskCache implements DiskCache {

    ...

        @Override
    public File get(String imageUri) {
        return getFile(imageUri);
    }

    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);
    }

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


}

磁盤緩存小結

相對于內(nèi)存緩存,UIL中的磁盤緩存的框架簡單的多,大致分為兩個磁盤緩存策略,一個是使用DiskLruCache作為底層實現(xiàn)的LruDiskCache,另一個是直接對文件類File進行操作。

  • LruDiskCache:底層采用DiskLruCache實現(xiàn),只不過是封裝了DiskLruCache較為復雜的數(shù)據(jù)操作方法
  • BaseDiskCache:不采取任何數(shù)據(jù),直接在實現(xiàn)方法里面使用方法類實現(xiàn)

12.ImageLoader

之前的章節(jié)主要介紹了ImageLoader在加載圖片時主要用到的一些類,比如配置類ImageLoaderConfiguration、線程池管理者以及主要任務執(zhí)行者ImageLoaderEngine、加載和顯示圖片任務LoadAndDisplayImageDisk、圖片加載顯示配置類DisplayImageOptions、圖片下載器ImageDownloader、圖片解碼器ImageDocoder、緩存內(nèi)存類MemoryCache和DiskCache等等。接下來分析ImageLoader的使用和具體工作流程,由于主要需要的類的分析過了,因此分析起來簡單了許多。

ImageLoader使用

ImageLoader的很簡單,主要分為3步

  1. 配置加載圖片的參數(shù)類ImageLoaderConfiguration并創(chuàng)建Imageloader對象
  2. 配置顯示圖片用的參數(shù)類DisplayImageOptions
  3. 使用displayImage()顯示圖片

第一步,配置并創(chuàng)建ImageLoader對象
后面注釋的是Configuration的可選項和一些默認項,可以看出一些加載圖片的必要默認項已經(jīng)可以讓ImageLoader正常工作了,比如taskExecutor、diskCache、memoryCache、downloader、decoder和defaultDisplayImageOptions等等。

//Set the ImageLoaderConfigutation
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(getApplicationContext())
        .threadPriority(Thread.NORM_PRIORITY - 2)
        .denyCacheImageMultipleSizesInMemory()
        .diskCacheFileNameGenerator(new Md5FileNameGenerator())
        .diskCacheSize(50 * 1024 * 1024)
        .tasksProcessingOrder(QueueProcessingType.LIFO)
        .writeDebugLogs()
        .build();

//Initial ImageLoader with ImageLoaderConfiguration
ImageLoader.getInstance().init(configuration);

/*
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
    .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
    .diskCacheExtraOptions(480, 800, null)
    .taskExecutor(...)// default
    .taskExecutorForCachedImages(...)
    .threadPoolSize(3) // default
    .threadPriority(Thread.NORM_PRIORITY - 2) // default
    .tasksProcessingOrder(QueueProcessingType.FIFO) // default
    .denyCacheImageMultipleSizesInMemory()
    .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
    .memoryCacheSize(2 * 1024 * 1024)
    .memoryCacheSizePercentage(13) // default
    .diskCache(new UnlimitedDiskCache(cacheDir)) // default
    .diskCacheSize(50 * 1024 * 1024)
    .diskCacheFileCount(100)
    .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
    .imageDownloader(new BaseImageDownloader(context)) // default
    .imageDecoder(new BaseImageDecoder()) // default
    .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
    .writeDebugLogs()
    .build();
/**/


第二步,配置顯示圖片用的參數(shù)類DislayImageOptions
從類名就能判斷出該類的作用是讓ImageLoader按需求顯示圖片,跟Configuration一樣,Options也有很多可選的配置參數(shù),并且一些顯示圖片的必要參數(shù)已經(jīng)被初始化了,比如displayer用于控件顯示圖片和handler傳回主線程操作。
但值得注意的是,在Options中,cacheInMemory和cacheOnDisk默認是false,因此很有必要在程序中手動將它們將設置成true,如下面代碼所示。

DisplayImageOptions options = new DisplayImageOptions.Builder()
        .cacheInMemory(true)    //緩存的這兩步很有必要
        .cacheOnDisk(true)
        .considerExifParams(true)
        .displayer(new CircleBitmapDisplayer(Color.WHITE, 5))
        .build();
/*
DisplayImageOptions options = new DisplayImageOptions.Builder()
    .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
    .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
    .showImageOnFail(R.drawable.ic_error) // resource or drawable
    .resetViewBeforeLoading(false)  // default
    .delayBeforeLoading(1000)
    .cacheInMemory(false) // default
    .cacheOnDisk(false) // default
    .preProcessor(...)
    .postProcessor(...)
    .extraForDownloader(...)
    .considerExifParams(false) // default
    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
    .bitmapConfig(Bitmap.Config.ARGB_8888) // default
    .decodingOptions(...)
    .displayer(new SimpleBitmapDisplayer()) // default
    .handler(new Handler()) // default
    .build();      
/**/    

第三步,使用displayImage()顯示圖片
調(diào)用displayImage()之前要獲取到ImageLoader的實例,因為ImageLoader采用單例模式,因此ImageLoader的實例是通過IamgeLoader的靜態(tài)方法getInstance()獲取的。
displayImage()有很多種重載方法,這里只展示一個,后面的注釋是所有displayImage()的版本。
由displayImage的參數(shù)中可以看出最主要的兩個參數(shù)就是imageUri和imageView,也就是要顯示的圖片的uri地址和顯示圖片的控件,這里也體現(xiàn)出了ImageLoader的最本質的工作,那就是將圖片從uri中加載到控件中。

注:不像前面Configuration和Optins配置一次就夠了,displayImage()方法在每次加載顯示圖片時都應該調(diào)用一次,因此通常該方法使用在ListView的Adapter的getView當中,因為getView中可以獲取到當前要顯示圖片的控件,并且列表滑動就會觸發(fā)getView方法,因此只需要在getView中將對應的ImageView傳送給displayImage就可以了


ImageLoader.getInstance().displayImage(imageUri, imageView, options, imageLoadingListener);

/*
//所有displayImage的方法,前面一部分是針對ImageAware的,后面一部分是針對ImageView的,也就是我們在開發(fā)中所使用到的,其實實現(xiàn)是利用了前面的方法

displayImage(String uri, ImageAware imageAware)
displayImage(String uri, ImageAware imageAware, ImageLoadingListener listener)
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options)
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageLoadingListener listener) 
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

displayImage(String uri, ImageView imageView)
displayImage(String uri, ImageView imageView, ImageSize targetImageSize)
displayImage(String uri, ImageView imageView, DisplayImageOptions options)
displayImage(String uri, ImageView imageView, ImageLoadingListener listener)
displayImage(String uri, ImageView imageView, DisplayImageOptions options,
        ImageLoadingListener listener)
displayImage(String uri, ImageView imageView, DisplayImageOptions options,
        ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
/**/

到此ImageLoader的使用方法就結束了,是不是很簡單,只需要3步,甚至如果使用的都是默認的配置,那只需要初始化ImageLoader并調(diào)用displayImage就可以了。下面具體分析ImageLoader的工作流程。

ImageLoader工作流程

從上面可以看出外界使用ImageLoader只需要調(diào)用displayImage()就可以實現(xiàn)圖片的加載和顯示,所以研究ImageLoader的工作流程其實就是分析ImageLoader的displayImage方法。由于displayImage()中調(diào)用的方法會貫穿整個UIL包,加上前面仔細分析了主要類的工作原理,因此下面只需分析主要的部分,便于理解整個UIL的工作流程。

public class ImageLoader
{
    ...

    //這些是工作時所需要的類成員
    private ImageLoaderConfiguration configuration;
    private ImageLoaderEngine engine;

    private ImageLoadingListener defaultListener = new SimpleImageLoadingListener();

    private volatile static ImageLoader instance;

    /**
     *  uri:圖片的uri
     *  imageAware:顯示圖片的控件
     *  options:顯示圖片的參數(shù)配置
     *  targetSize:要顯示的圖片的大小
     *  listener:用于加載圖片中的回調(diào)
     *  progressListener:也是用于圖片加載時的回調(diào)
     */
    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
            {
                ...
                //將uri轉換成key
                String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
                //從內(nèi)存中獲取相應key的圖片
                Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
                //如果不為空說明從內(nèi)存中獲取到了圖片
                if (bmp != null && !bmp.isRecycled()) {
                    //判斷是否需要加工處理,如果是則封裝圖片的信息和需求成Info類,然后通過一個任務類ProcessAndDisplayImageTask將圖片按照需求加載到控件中
                    if (options.shouldPostProcess()) {
                        ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                                options, listener, progressListener, engine.getLockForUri(uri));
                        ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                                defineHandler(options));

                        //如果此時運行在子線程中就直接運行,否則使用線程池執(zhí)行
                        if (options.isSyncLoading()) {
                            displayTask.run();
                        } else {
                            engine.submit(displayTask);
                        }
                    } 
                    //如果不需要經(jīng)過處理,則直接通過displayer的display將圖片顯示在控件中
                    else {
                        options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                        listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
                    }
                } 
                //為空說明需要內(nèi)存緩存中不存在該圖片,需要從磁盤和網(wǎng)絡中加載該圖片
                else {
                    //設置加載前的默認圖片
                    if (options.shouldShowImageOnLoading()) {
                        imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
                    } else if (options.isResetViewBeforeLoading()) {
                        imageAware.setImageDrawable(null);
                    }

                    //封裝圖片加載信息類,然后通過LoadAndDisplayImageTask執(zhí)行加載顯示圖片任務
                    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.run();
                    } else {
                        engine.submit(displayTask);
                    }
                }

            }

    ...
}

//同步加載圖片并顯示的任務
final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener
{
    ...

    @Override
    public void run()
    {
        ...
        //從內(nèi)存中提取圖片
        bmp = configuration.memoryCache.get(memoryCacheKey);
        if (bmp == null || bmp.isRecycled()) {
            //如果內(nèi)存中為null則從磁盤網(wǎng)絡中獲取圖片
            bmp = tryLoadBitmap();
            ...
            if (bmp != null && options.isCacheInMemory()) {
                //將圖片存進內(nèi)存緩存中
                configuration.memoryCache.put(memoryCacheKey, bmp);
            }
            ...
        }
        ...
        //前面加載完圖片接著通過執(zhí)行DisplayBitmapTask顯示圖片到控件中
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);

    }

    //同步從磁盤、網(wǎng)絡中加載圖片
    private Bitmap tryLoadBitmap() throws TaskCancelledException
    {
        Bitmap bitmap = null;
        ...
        //從磁盤緩存中獲取文件并解碼成圖片
        File imageFile = configuration.diskCache.get(uri);
        bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
        
        if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) 
        {
            //如果磁盤中獲取不到圖片則通過tryCacheImageOnDisk()從網(wǎng)絡中獲取并存至磁盤中
            if (options.isCacheOnDisk() && tryCacheImageOnDisk()) 
            {
                ...
                //再次從磁盤中獲取文件
                imageFile = configuration.diskCache.get(uri);
                if (imageFile != null) {
                    imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                }
            }   
            //再次將文件解碼成圖片
            bitmap = decodeImage(imageUriForDecoding);
        }
        ...
        return bitmap;
    }

    //從網(wǎng)絡中加載圖片(通過ImageDownloader),并按照需求壓縮(通過ImageDecoder),最后放入磁盤緩存中
    private boolean tryCacheImageOnDisk() throws TaskCancelledException 
    {
        boolean loaded;

        loaded = downloadImage();
        ...
        resizeAndSaveImage(width, height);
        ...

        return loaded;
    }

}

//異步顯示圖片的任務,里面主要是調(diào)用了圖片在加載過程中的各種回調(diào),最后通過displayer.display將圖片顯示到控件中,注意該任務要運行在主線程當中
final class DisplayBitmapTask implements Runnable {

    @Override
    public void run() {
        if (imageAware.isCollected()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else if (isViewWasReused()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else {
            L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);

            //最主要的是這步,顯示圖片
            displayer.display(bitmap, imageAware, loadedFrom);
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
        }
    }

}

上面就是整個UIL的工作流程,大致上可以分為兩步,同步加載圖片和異步顯示圖片,主要關注點在同步加載圖片這個過程。

  • 同步加載圖片:內(nèi)存緩存中獲取->磁盤緩存中獲取->網(wǎng)絡中獲取->存進磁盤緩存->磁盤緩存中獲取->存進內(nèi)存緩存->返回圖片
  • 異步顯示圖片:利用Handler將任務執(zhí)行在主線程當中,通過displayer將圖片顯示在控件當中

以下是加載顯示圖片的流程,幫助理解和記憶。

UIL_Load&Display Task Flow.PNG

總結UIL的關注點

UIL主要關注以下幾點:圖片從網(wǎng)絡加載的加載所使用的方式,內(nèi)存緩存的方式,磁盤緩存的方式以及整個UIL的工作流程。

網(wǎng)絡加載:采用HttpURLConnection進行網(wǎng)絡連接來獲取數(shù)據(jù)
內(nèi)存緩存:UIL中內(nèi)存緩存有好幾種用于內(nèi)存緩存的類,其中默認使用的是LruMemoryCache,它的實現(xiàn)原理跟Android自帶的LruCache差不多,都是利用accessOrder為true的LinkedHashMap實現(xiàn)的,其他的還有LRULimitedMemoryCache,FIFOLimitedMemoryCache等等,它們的公共特點就是緩存大小有限制,不同點是在緩存超過限制的時候刪除的規(guī)則不一樣
磁盤緩存:UIL中的磁盤緩存比內(nèi)存緩存簡單了好多,主要分為兩種實現(xiàn),一種是UIL中默認使用的LruDiskCache,它的底層實現(xiàn)是直接使用的DiskLruCache,只不過是做了一層封裝;另一種實現(xiàn)是直接對文件類File進行操作,其中有兩個具體實現(xiàn),一個是有緩存大小限制的,另一個是沒有限制的
UIL工作流程:就是內(nèi)存緩存->磁盤緩存->網(wǎng)絡3個流程,詳細在前一節(jié)有介紹

優(yōu)缺點
優(yōu)點:比較老的框架,穩(wěn)定,加載速度適中
缺點:不支持GIF圖片加載,緩存機制沒有和http的緩存很好的結合,完全是自己的一套緩存機制

0.ImageLoader中使用的設計模式

單例模式

public static ImageLoader getInstance() {
    if (instance == null) {
        synchronized (ImageLoader.class) {
            if (instance == null) {
                instance = new ImageLoader();
            }
        }
    }
    return instance;
}

建造者模式

在ImageLoaderConfiguration和DisplayImageOptions都是使用了建造者模式,原因是它們有很多可選的屬性可以被設置,使用建造者模式可以使得代碼更清晰和健壯。

學到的知識

看源碼的方式:先了解大致的流程,或者自己猜想工作流程,然后帶著大局觀去學習源碼,這樣有利于保持思路的清晰,而不至于在函數(shù)各種跳轉的過程中迷失了,導致興趣全無

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內(nèi)容