Universal-Image-Loader(2)

10.MemoryCache

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

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

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

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

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

下面是MemoryCache繼承結(jié)構(gòu)圖,可以幫助我們理解整個(gè)MemoryCache的框架

MemoryCache繼承結(jié)構(gòu)圖.PNG

下面就來詳細(xì)介紹幾個(gè)常用的MemoryCache以及它們的工作流程。

LruMemoryCache

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

public interface MemoryCache {

    boolean put(String key, Bitmap value);

    Bitmap get(String key);

    Bitmap remove(String key);

    Collection<String> keys();

    void clear();
}

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

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

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

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

這里L(fēng)RULimitedMemoryCache的繼承結(jié)構(gòu)有3層(除去MemoryCache接口),每層結(jié)構(gòu)都有自己的作用,因此沒有清楚了解繼承結(jié)構(gòu)的話會(huì)導(dǎo)致思維混亂,無法理解LRULimitedMemoryCache的工作原理,所以先來看一下繼承結(jié)構(gòu)。

LimitedMemoryCache繼承結(jié)構(gòu)圖.PNG

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

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

下面分別介紹這三層結(jié)構(gòu)。
1.BaseMemoryCache
首先先介紹BaseMemoryCache,因?yàn)長imitedMemoryCache繼承于它,下面是它的實(shí)現(xiàn)源碼。

關(guān)注的重點(diǎn)有:

  • BaseMemoryCache最主要的就是靠Map成員Map<String, Reference<Bitmap>> softMap實(shí)現(xiàn)的,如數(shù)據(jù)的緩存和獲取就是通過該softMap的put和get實(shí)現(xiàn)的
  • Map中的value是Bitmap的Reference對象而不是Bitmap,而該Reference通常被傳入WeakReference,因此造成的結(jié)果是,softMap中的value會(huì)隨時(shí)被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的作用就是實(shí)現(xiàn)防止緩存超過閾值的大小,所以關(guān)注該類的關(guān)注點(diǎn)在如何實(shí)現(xiàn)限制緩存大小。

由源碼可以看出:

  • 允許最大緩存為16MB
  • 內(nèi)部有一個(gè)List用于以強(qiáng)引用的方式存儲(chǔ)Bitmap
  • 沒有g(shù)et方法,即get方法是直接調(diào)用的是BaseMemoryCache的get方法
  • 最主要的是put方法,這是實(shí)現(xiàn)限制緩存的關(guān)鍵,在put方法中,判斷添加后的緩存大小是否超過閾值,如果超過則調(diào)用抽象方法removeNext()獲取緩存中應(yīng)該被刪除的數(shù)據(jù),比如LRU的removeNext()返回的是最近最少被使用的數(shù)據(jù),F(xiàn)IFO的返回的是最早插入的數(shù)據(jù)。最后還要調(diào)用super.put(key, value)來讓BaseMemoryCache中的softMap存儲(chǔ)數(shù)據(jù),因?yàn)榍懊嬲f過數(shù)據(jù)的存儲(chǔ)和獲取是由BaseMemoryCache提供的
  • 內(nèi)部類List的作用是確保緩存大小的正確變化。因?yàn)橐_保removeNext()刪除的數(shù)據(jù)是之前緩存的數(shù)據(jù),如果List刪除removeNext()返回的數(shù)據(jù)成功了證明緩存被刪除了,此時(shí)緩存大小才會(huì)變化,才能使緩存反應(yīng)真實(shí)的變化
  • 在put方法中緩存數(shù)據(jù)超過閾值時(shí),removeNext()會(huì)刪除子類比如LRU、FIFO中的緩存數(shù)據(jù),List也會(huì)刪除數(shù)據(jù),但注意BaseMemoryCache中的softMap并不會(huì)刪除數(shù)據(jù),不必?fù)?dān)心softMap中的緩存量過大,因?yàn)閃eakReference的對象會(huì)隨時(shí)被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);
        }
    }

    /**
     * 實(shí)現(xiàn)緩存大小限制的關(guā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ù)后大于限制大小時(shí)則刪除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ù)的存取獲取以及緩存大小的限制在前面兩層結(jié)構(gòu)已經(jīng)實(shí)現(xiàn)了,此時(shí)LRULimitedMemoryCache以及FIFOLimitedMemoryCache等類的工作就簡單很多了,只需要制定相應(yīng)的刪除規(guī)則removeNext()就行了,而刪除指定的數(shù)據(jù)使得類本身也需要用一個(gè)數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)數(shù)據(jù),畢竟你沒有數(shù)據(jù)怎么確定要?jiǎng)h除的數(shù)據(jù)是哪個(gè)啊是吧,下面是源碼。

關(guān)注的重點(diǎn):

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

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

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

11.DiskCache

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

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

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

DiskCache繼承結(jié)構(gòu)圖.PNG

LruDiskCache

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

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ù)的寫入和讀取其實(shí)都是通過DiskLruCache實(shí)現(xiàn)的。下面是使用LruDiskCache需要關(guān)注的地方。

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

總的來說,其實(shí)LruDiskCache就是對DiskLruCache做了一層封裝,實(shí)際的數(shù)據(jù)的操作方法還是通過DiskLruCache來實(shí)現(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中還有另外兩個(gè)具體磁盤緩存類LimitedAgeDiskCache和UnlimitedDiskCache,它們的不同點(diǎn)只是一個(gè)還刪除緩存文件的過時(shí)文件,一個(gè)不限制緩存大小。這里只介紹他們的共同父類:BaseDiskCache。

在BaseDiskCache中并沒有使用特殊的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù),直接就是通過對文件類的操作來達(dá)成使用文件緩存的目的。注意該類并沒有限制緩存大小,下面就簡單看一下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;
    }


}

磁盤緩存小結(jié)

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

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

12.ImageLoader

之前的章節(jié)主要介紹了ImageLoader在加載圖片時(shí)主要用到的一些類,比如配置類ImageLoaderConfiguration、線程池管理者以及主要任務(wù)執(zhí)行者ImageLoaderEngine、加載和顯示圖片任務(wù)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的可選項(xiàng)和一些默認(rèn)項(xiàng),可以看出一些加載圖片的必要默認(rèn)項(xiàng)已經(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默認(rèn)是false,因此很有必要在程序中手動(dòng)將它們將設(shè)置成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的實(shí)例,因?yàn)镮mageLoader采用單例模式,因此ImageLoader的實(shí)例是通過IamgeLoader的靜態(tài)方法getInstance()獲取的。
displayImage()有很多種重載方法,這里只展示一個(gè),后面的注釋是所有displayImage()的版本。
由displayImage的參數(shù)中可以看出最主要的兩個(gè)參數(shù)就是imageUri和imageView,也就是要顯示的圖片的uri地址和顯示圖片的控件,這里也體現(xiàn)出了ImageLoader的最本質(zhì)的工作,那就是將圖片從uri中加載到控件中。

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


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

/*
//所有displayImage的方法,前面一部分是針對ImageAware的,后面一部分是針對ImageView的,也就是我們在開發(fā)中所使用到的,其實(shí)實(shí)現(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的使用方法就結(jié)束了,是不是很簡單,只需要3步,甚至如果使用的都是默認(rèn)的配置,那只需要初始化ImageLoader并調(diào)用displayImage就可以了。下面具體分析ImageLoader的工作流程。

ImageLoader工作流程

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

public class ImageLoader
{
    ...

    //這些是工作時(shí)所需要的類成員
    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:也是用于圖片加載時(shí)的回調(diào)
     */
    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
            {
                ...
                //將uri轉(zhuǎn)換成key
                String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
                //從內(nèi)存中獲取相應(yīng)key的圖片
                Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
                //如果不為空說明從內(nèi)存中獲取到了圖片
                if (bmp != null && !bmp.isRecycled()) {
                    //判斷是否需要加工處理,如果是則封裝圖片的信息和需求成Info類,然后通過一個(gè)任務(wù)類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));

                        //如果此時(shí)運(yùn)行在子線程中就直接運(yùn)行,否則使用線程池執(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)絡(luò)中加載該圖片
                else {
                    //設(shè)置加載前的默認(rèn)圖片
                    if (options.shouldShowImageOnLoading()) {
                        imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
                    } else if (options.isResetViewBeforeLoading()) {
                        imageAware.setImageDrawable(null);
                    }

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

            }

    ...
}

//同步加載圖片并顯示的任務(wù)
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)絡(luò)中獲取圖片
            bmp = tryLoadBitmap();
            ...
            if (bmp != null && options.isCacheInMemory()) {
                //將圖片存進(jìn)內(nèi)存緩存中
                configuration.memoryCache.put(memoryCacheKey, bmp);
            }
            ...
        }
        ...
        //前面加載完圖片接著通過執(zhí)行DisplayBitmapTask顯示圖片到控件中
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);

    }

    //同步從磁盤、網(wǎng)絡(luò)中加載圖片
    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)絡(luò)中獲取并存至磁盤中
            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)絡(luò)中加載圖片(通過ImageDownloader),并按照需求壓縮(通過ImageDecoder),最后放入磁盤緩存中
    private boolean tryCacheImageOnDisk() throws TaskCancelledException 
    {
        boolean loaded;

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

        return loaded;
    }

}

//異步顯示圖片的任務(wù),里面主要是調(diào)用了圖片在加載過程中的各種回調(diào),最后通過displayer.display將圖片顯示到控件中,注意該任務(wù)要運(yùn)行在主線程當(dāng)中
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);
        }
    }

}

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

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

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

UIL_Load&Display Task Flow.PNG

總結(jié)UIL的關(guān)注點(diǎn)

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

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

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

0.ImageLoader中使用的設(shè)計(jì)模式

單例模式

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

建造者模式

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

學(xué)到的知識(shí)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容