Android-Universal-Image-Loader源碼知識(shí)筆記

ImageLoader簡(jiǎn)單使用

ImageLoader的二級(jí)分包主要由cache、core和utils組成,三者分別負(fù)責(zé)緩存、核心加載類和工具類。
在核心加載類core的子分包中,包括顯示、下載、進(jìn)度監(jiān)聽及屬性配置等。

外部調(diào)用:

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

這里底層代碼由displayImage方法負(fù)責(zé):

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

ImageAware:ImageAware這個(gè)類會(huì)將ImageView轉(zhuǎn)換成ImageViewAware, ImageViewAware主要是做什么的呢?該類主要是將ImageView進(jìn)行一個(gè)包裝,將ImageView的強(qiáng)引用變成弱引用,當(dāng)內(nèi)存不足的時(shí)候,可以更好的回收ImageView對(duì)象,還有就是獲取ImageView的寬度和高度。這使得我們可以根據(jù)ImageView的寬高去對(duì)圖片進(jìn)行一個(gè)裁剪,減少內(nèi)存的使用。

通過memoryCacheKey判斷內(nèi)存緩存中是否有bitmap。

設(shè)置圖片緩存時(shí)要做的事

為了能夠選擇一個(gè)合適的緩存大小給緩存類, 有以下多個(gè)因素應(yīng)該放入考慮范圍內(nèi),例如:

  • 你的設(shè)備可以為每個(gè)應(yīng)用程序分配多大的內(nèi)存?
  • 設(shè)備屏幕上一次最多能顯示多少張圖片?有多少圖片需要進(jìn)行預(yù)加載,因?yàn)橛锌赡芎芸煲矔?huì)顯示在屏幕上?
  • 你的設(shè)備的屏幕大小和分辨率分別是多少?一個(gè)超高分辨率的設(shè)備(例如 Galaxy Nexus) 比起一個(gè)較低分辨率的設(shè)備(例如 Nexus S),在持有相同數(shù)量圖片的時(shí)候,需要更大的緩存空間。
  • 圖片的尺寸和大小,還有每張圖片會(huì)占據(jù)多少內(nèi)存空間。
  • 圖片被訪問的頻率有多高?會(huì)不會(huì)有一些圖片的訪問頻率比其它圖片要高?如果有的話,你也許應(yīng)該讓一些圖片常駐在內(nèi)存當(dāng)中,或者使用多個(gè)LruCache 對(duì)象來區(qū)分不同組的圖片。
  • 你能維持好數(shù)量和質(zhì)量之間的平衡嗎?有些時(shí)候,存儲(chǔ)多個(gè)低像素的圖片,而在后臺(tái)去開線程加載高像素的圖片會(huì)更加的有效。

摘自:Android高效加載大圖、多圖解決方案,有效避免程序OOM

Listview快速滑動(dòng)不加載圖片策略

在PauseOnScrollListener類中,通過監(jiān)聽狀態(tài)OnScrollListener.SCROLL_STATE_FLING執(zhí)行imagleloader.pause方法,
最后調(diào)用ImageLoaderEngine類的原子變量pause。

LoadAndDisplayImageTask類中:根據(jù)原子變量pause的狀態(tài)對(duì)象鎖控制阻塞。

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

PauseOnScrollListener類中:監(jiān)聽滑動(dòng)狀態(tài),并根據(jù)scroll狀態(tài)改變ImageLoaderEngine類的原子變量pause值。

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

AtomicBoolean是java.util.concurrent.atomic包下的原子變量,這個(gè)包里面提供了一組原子類。其基本的特性就是在多線程環(huán)境下,當(dāng)有多個(gè)線程同時(shí)執(zhí)行這些類的實(shí)例包含的方法時(shí),具有排他性,即當(dāng)某個(gè)線程進(jìn)入方法,執(zhí)行其中的指令時(shí),不會(huì)被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執(zhí)行完成,才由JVM從等待隊(duì)列中選擇一個(gè)另一個(gè)線程進(jìn)入。

ReentrantLock鎖機(jī)制

典型寫法:

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

Lock是基于JDK層面實(shí)現(xiàn)的,通常需要在finally中進(jìn)行鎖的釋放,如果忘記就會(huì)造成死鎖。
ReentrantLock實(shí)現(xiàn)Lock接口,所有的同步操作都是依靠AbstractQueuedSynchronizer(隊(duì)列同步器)實(shí)現(xiàn)。

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

功能:獲取鎖。
如果該鎖沒有被另一個(gè)線程保持,則獲取該鎖并立即返回,并將鎖的保持計(jì)數(shù)器設(shè)置為1.
如果當(dāng)前線程已經(jīng)保持該鎖,則將保持計(jì)數(shù)加1,并且該方法立即返回。
如果該鎖被另一個(gè)線程保持,則出于線程調(diào)度的目的,禁用該線程,并且在獲得鎖之前,該線程一直處于休眠狀態(tài),此時(shí)鎖保持計(jì)數(shù)被設(shè)置為1.

公平鎖(FairSync)與非公平鎖(NonfairSync):如果獲取一個(gè)鎖是按照請(qǐng)求的順序得到的,那么就是公平鎖,否則就是非公平的。

  • 公平鎖保證一個(gè)阻塞的線程最終能夠獲得鎖,因?yàn)槭怯行虻?,所以總是可以按照?qǐng)求的順序獲得鎖。
  • 不公平鎖意味著后請(qǐng)求鎖的線程可能在其前面排列的休眠線程恢復(fù)前拿到鎖,這樣就有可能提高并發(fā)的性能。這是因?yàn)橥ǔG闆r下掛起的線程重新開始與它真正開始運(yùn)行,二者之間會(huì)產(chǎn)生嚴(yán)重的延時(shí)。因此非公平鎖就可以利用這段時(shí)間完成操作。這是非公平鎖在某些時(shí)候比公平鎖性能要好的原因之一。

詳細(xì)的介紹我們?cè)诹硪黄┛屠镪U述。todo

內(nèi)存緩存策略

LruMemoryCache

這個(gè)類是通過LinkedHashMap有序的保存著強(qiáng)引用bitmap,是Android-Universal-Image-Loader框架默認(rèn)的緩存類。

特點(diǎn)

  • 采用強(qiáng)引用方式保存bitmap;
  • 如果超出最大緩存字節(jié)限制,則會(huì)循環(huán)遍歷并刪除map的第一位(即刪除最早緩存的,因此要有序);

在開源庫中,DefaultConfigurationFactory類下的createMemoryCache方法配置緩存類的初始化。

    /**
     * Creates default implementation of {@link MemoryCache} - {@link LruMemoryCache}<br />
     * Default cache size = 1/8 of available app memory.
     */
    public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
        if (memoryCacheSize == 0) {
            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            //獲取可使用的最大內(nèi)存,單位MB
            int memoryClass = am.getMemoryClass();
            if (hasHoneycomb() && isLargeHeap(context)) {
                memoryClass = getLargeMemoryClass(am);
            }
            //將最大內(nèi)存的1/8轉(zhuǎn)變成字節(jié)單位。
            memoryCacheSize = 1024 * 1024 * memoryClass / 8;
        }
        return new LruMemoryCache(memoryCacheSize);
    }

1MB = 1024KB = 1024* 1024B(字節(jié))。1B = 8bit(8位,即8位二進(jìn)制數(shù))。

“刪除最早緩存的”代碼如下:

/**
     * Remove the eldest entries until the total of remaining entries is at or below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
     */
    private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                //這是在size > maxSize的情況下,通過Map.entrySet使用iterator遍歷key和value,每次都是先獲取第一個(gè)。remove之后循環(huán)下一次。
                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= sizeOf(key, value);
            }
        }
    }

在size > maxSize的情況下,通過Map.entrySet使用iterator遍歷key和value,每次都是先獲取第一個(gè)。remove之后循環(huán)下一次。

UsingFreqLimitedMemoryCache

這個(gè)類是通過對(duì)已存儲(chǔ)的bitmap的使用率進(jìn)行比對(duì),從而移除最小使用率的bitmap,達(dá)到控制內(nèi)存的目的。

特點(diǎn):

  • 采用強(qiáng)引用和弱引用相結(jié)合的方式存儲(chǔ)bitmap,并存儲(chǔ)使用次數(shù)Integer;
  • put時(shí)使用次數(shù)置零,get時(shí)使用次數(shù)+1;
  • 在超出內(nèi)存上限時(shí),遍歷map比較出最小使用次數(shù)的bitmap,然后移除;

UsingFreqLimitedMemoryCache繼承了父類LimitedMemoryCache,LimitedMemoryCache繼承父類BaseMemoryCache。
UsingFreqLimitedMemoryCache中的數(shù)據(jù)結(jié)構(gòu)是:

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

map中的key是bitmap,表示存儲(chǔ)的位圖;value是Integer,表示使用的次數(shù)。
Collections.synchronizedMap,解決了線程安全性問題。 通過將基本的功能從線程安全性中分離開來,允許需要同步的用戶可以擁有同步,而不需要同步的用戶則不必為同步付出代價(jià)。
LimitedMemoryCache中的數(shù)據(jù)結(jié)構(gòu)是:

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

此類主要實(shí)現(xiàn)一些獲取可用內(nèi)存字節(jié)和移除使用率最低的抽象方法。(本來我認(rèn)為此類可舍棄,該類的邏輯代碼可在BaseMemoryCache中進(jìn)行,但是看到LRULimitedMemoryCache類之后,覺得我的思想設(shè)計(jì)擴(kuò)展性很不強(qiáng)哦)
BaseMemoryCache中的數(shù)據(jù)結(jié)構(gòu)是:

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

這里是將value對(duì)應(yīng)的bitmap放在引用的泛型中,并在UsingFreqLimitedMemoryCache類中實(shí)現(xiàn)抽象方法createReference()。

因?yàn)閃eakReference< T >等都是以此形式繼承Reference< T >,如此便于擴(kuò)展。

UsingFreqLimitedMemoryCache中:

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

因此,存儲(chǔ)的bitmap是以弱引用的形式存儲(chǔ)在hashmap中。

put:

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

這里在對(duì)map進(jìn)行put值的時(shí)候,會(huì)默認(rèn)將表示使用次數(shù)的Integer設(shè)置為0;而在get的時(shí)候會(huì)將其+1;
在第二行里子類調(diào)用了父類的put方法,其中包含了對(duì)removeNext()的調(diào)用,是為了處理當(dāng)存儲(chǔ)超出上限時(shí)移除使用率最小的值。

get:

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

removeNext():

    @Override
    protected Bitmap removeNext() {
        Integer minUsageCount = null;
        Bitmap leastUsedValue = null;
        Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet();
        synchronized (usingCounts) {
            for (Entry<Bitmap, Integer> entry : entries) {
                if (leastUsedValue == null) {
                    leastUsedValue = entry.getKey();
                    minUsageCount = entry.getValue();
                } else {
                    Integer lastValueUsage = entry.getValue();
                    if (lastValueUsage < minUsageCount) {
                        minUsageCount = lastValueUsage;
                        leastUsedValue = entry.getKey();
                    }
                }
            }
        }
        //檢索完畢之后,移除使用頻率最低的那個(gè)值。
        usingCounts.remove(leastUsedValue);
        return leastUsedValue;
    }

這里,lastValueUsage表示本次(最后、最新)循環(huán)內(nèi)的使用次數(shù)值,minUsageCount不為null時(shí)表示上次循環(huán)時(shí)賦的值。
如果檢索出更小使用次數(shù)時(shí),則將該循環(huán)內(nèi)的entry.getKey()和entry.getValue()賦值給leastUsedValue和minUsageCount。

LRULimitedMemoryCache

此類和UsingFreqLimitedMemoryCache都是繼承父類LimitedMemoryCache,意味著上層的邏輯處理是一樣的。

特點(diǎn):

  • 采用強(qiáng)引用和弱引用相結(jié)合的方式存儲(chǔ)bitmap;
  • 在超出內(nèi)存上限時(shí),遍歷map比較出最近最少使用的的bitmap,然后移除;

可以理解為只是將LruMemoryCache中的bitmap由強(qiáng)引用轉(zhuǎn)化為弱引用。

此類的數(shù)據(jù)結(jié)構(gòu)是:

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

該類沒有統(tǒng)計(jì)使用率的Integer的value,但有LinkedHashMap組成的有序的結(jié)構(gòu)體,LinkedHashMap是一個(gè)雙向循環(huán)鏈表。
后續(xù)會(huì)有章節(jié)詳述Java數(shù)據(jù)結(jié)構(gòu)及原理,此處略。
removeNext:

    @Override
    protected Bitmap removeNext() {
        Bitmap mostLongUsedValue = null;
        synchronized (lruCache) {
            //移除最早的那個(gè),因?yàn)閿?shù)據(jù)結(jié)構(gòu)是LinkedHashMap,存儲(chǔ)是有序的。
            Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
            if (it.hasNext()) {
                Entry<String, Bitmap> entry = it.next();
                mostLongUsedValue = entry.getValue();
                it.remove();
            }
        }
        return mostLongUsedValue;
    }

既然LRULimitedMemoryCache和UsingFreqLimitedMemoryCache都是繼承父類LimitedMemoryCache,我們看看父類的方法吧。

在LimitedMemoryCache中,

    @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) {
            while (curCacheSize + valueSize > sizeLimit) {
                Bitmap removedValue = removeNext();
                //硬盤緩存移除使用頻率最低的bitmap
                if (hardCache.remove(removedValue)) {
                    curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
                }
            }
            //硬盤緩存添加
            hardCache.add(value);
            cacheSize.addAndGet(valueSize);

            putSuccessfully = true;
        }
        // Add value to soft cache
        super.put(key, value);
        return putSuccessfully;
    }

在當(dāng)前已緩存的字節(jié)+此次要緩存的bitmap的字節(jié)之和大于緩存字節(jié)上限時(shí),清除子類指定的bitmap。

FIFOLimitedMemoryCache

此類和UsingFreqLimitedMemoryCache都是繼承父類LimitedMemoryCache,意味著上層的邏輯處理是一樣的。

特點(diǎn):

  • 采用強(qiáng)引用和弱引用相結(jié)合的方式存儲(chǔ)bitmap;
  • 在超出內(nèi)存上限時(shí),遍歷map比較出最早存儲(chǔ)的bitmap,然后移除;

此類的數(shù)據(jù)結(jié)構(gòu)與之前的不同:

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

LinkedList列表先進(jìn)先出的方式存儲(chǔ)bitmap。

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

超出內(nèi)存存儲(chǔ)上限時(shí)的處理也是直接移除先進(jìn)去(第一個(gè))的bitmap。

LargestLimitedMemoryCache

此類和UsingFreqLimitedMemoryCache都是繼承父類LimitedMemoryCache,意味著上層的邏輯處理是一樣的。

特點(diǎn):

  • 采用強(qiáng)引用和弱引用相結(jié)合的方式存儲(chǔ)bitmap;
  • 在超出內(nèi)存上限時(shí),遍歷map比較出內(nèi)存占用最大的bitmap,然后移除;

此類的數(shù)據(jù)結(jié)構(gòu)是:

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

疑問:既然都是繼承父類LimitedMemoryCache,也都采用強(qiáng)引用和弱引用相結(jié)合的方式實(shí)現(xiàn)存儲(chǔ)。那么三者最后是將bitmap以強(qiáng)引用bitmap、弱引用bitmap、LinkedList中bitmap這其中哪種方式存儲(chǔ)呢?

答:是以子類中createReference(Bitmap value)方法指定的引用類型存儲(chǔ)的,目前三者都是以WeakReference弱引用的形式存儲(chǔ)在基類BaseMemoryCache定義的HashMap中。
依據(jù):子類中的get和put方法都是調(diào)用基類BaseMemoryCache的方法,而子類自己的數(shù)據(jù)結(jié)構(gòu)以及父類LimitedMemoryCache的是以一種便于操作數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)中存在的(因?yàn)槿跻玫纳芷诓欢ǎ?/p>

LimitedAgeMemoryCache

特點(diǎn):

  • 采用強(qiáng)引用bitmap的key、vaule形式存儲(chǔ)bitmap;
  • get獲取值的時(shí)候檢測(cè)HashMap內(nèi)的Long類型的時(shí)間戳是否超出最大時(shí)限,若超出則刪除。

結(jié)構(gòu)體如下:

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

get

    @Override
    public Bitmap get(String key) {
        Long loadingDate = loadingDates.get(key);
        Bitmap bitmap = cache.get(key);
        //在獲取值的時(shí)候檢測(cè)時(shí)間是否超出緩存時(shí)限,若超出則移除。
        if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {
            cache.remove(key);
            loadingDates.remove(key);
        }
        return bitmap;
    }

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

WeakMemoryCache

此類直接繼承BaseMemoryCache實(shí)現(xiàn)弱引用bitmap存儲(chǔ)。

特點(diǎn):

  • 采用弱引用bitmap方式存儲(chǔ)在HashMap中。

類結(jié)構(gòu)如下:

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

這個(gè)類緩存bitmap的總大小沒有限制,唯一不足的地方就是不穩(wěn)定,緩存的圖片容易被回收掉。

LruCache

先看下構(gòu)造方法:

   /**
     * LruCache的構(gòu)造方法:需要傳入最大緩存?zhèn)€數(shù)
     */
    public LruCache(int maxSize) {
        // 最大緩存?zhèn)€數(shù)小于0,會(huì)拋出IllegalArgumentException
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        /*
         * 初始化LinkedHashMap
         * 第一個(gè)參數(shù):initialCapacity,初始大小
         * 第二個(gè)參數(shù):loadFactor,負(fù)載因子=0.75f
         * 第三個(gè)參數(shù):accessOrder=true,基于訪問順序;accessOrder=false,基于插入順序
         */
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

這里設(shè)置了最大緩存的字節(jié)數(shù),一般是16M10241024B,然后初始化了LinkedHashMap的參數(shù),其中accessOrder=true,表示基于訪問順序進(jìn)行排序的。

new LinkedHashMap<K, V> 中的鍵值對(duì)是通過類的泛型傳入的。比如圖片加載,可以用new LinkedHashMap<String, Bitmap>作為鍵值對(duì)。

在put方法里

  /**
     * 給對(duì)應(yīng)key緩存value,該value將被移動(dòng)到隊(duì)頭。
     */
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            // 記錄 put 的次數(shù)
            putCount++;
            // 拿到鍵值對(duì),計(jì)算出在容量中的相對(duì)長(zhǎng)度,然后加上
            size += safeSizeOf(key, value);
            /*
             * 放入 key value
             * 如果 之前存在key 則返回 之前key 的value
             * 記錄在 previous
             */
            previous = map.put(key, value);
            // 如果存在沖突
            if (previous != null) {
                // 計(jì)算出 沖突鍵值 在容量中的相對(duì)長(zhǎng)度,然后減去
                size -= safeSizeOf(key, previous);
            }
        }

        // 如果上面發(fā)生沖突
        if (previous != null) {
            /*
             * previous值被剔除了,此次添加的 value 已經(jīng)作為key的 新值
             * 告訴 自定義 的 entryRemoved 方法
             */
            entryRemoved(false, key, previous, value);
        }
        trimToSize(maxSize);
        return previous;
    }

和以往的緩存類相似。在同步方法塊內(nèi),先計(jì)算要put的value的大?。ㄗ止?jié)),size累加,然后map.put(),該方法的返回值若不為null,表明替換掉同一個(gè)key的舊的value值了。并且size減去舊值的大?。ㄗ止?jié))。

同步塊下,有個(gè)entryRemoved方法,這個(gè)方法默認(rèn)沒有實(shí)現(xiàn),暫做剔除的自定義處理。

每次put 新值的時(shí)候就要檢測(cè)是否超出最大緩存空間。這個(gè)步驟是在trimToSize方法內(nèi)進(jìn)行。

trimToSize如下:

 /**
     * 刪除最舊的數(shù)據(jù)直到剩余的數(shù)據(jù)的總數(shù)以下要求的大小。
     */
    public void trimToSize(int maxSize) {
        /*
         * 這是一個(gè)死循環(huán),
         * 1.只有 擴(kuò)容 的情況下能立即跳出
         * 2.非擴(kuò)容的情況下,map的數(shù)據(jù)會(huì)一個(gè)一個(gè)刪除,直到map里沒有值了,就會(huì)跳出
         */
        while (true) {
            K key;
            V value;
            synchronized (this) {
                // 在重新調(diào)整容量大小前,本身容量就為空的話,會(huì)出異常的。
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(
                            getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }
                // 如果是 擴(kuò)容 或者 map為空了,就會(huì)中斷,因?yàn)閿U(kuò)容不會(huì)涉及到丟棄數(shù)據(jù)的情況
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                //剩下的情況就是 if (size > maxSize && !map.isEmpty())即存儲(chǔ)已超出最大緩存空間

                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                // 拿到鍵值對(duì),計(jì)算出在容量中的相對(duì)長(zhǎng)度,然后減去。
                size -= safeSizeOf(key, value);
                // 添加一次收回次數(shù)
                evictionCount++;
            }
            /*
             * 將最后一次刪除的最少訪問數(shù)據(jù)回調(diào)出去
             */
            entryRemoved(true, key, value, null);
        }
    }

這里檢測(cè)插入的值是否超出最大緩存空間,如果超出了就會(huì)執(zhí)行移除操作。

while循環(huán)中遍歷進(jìn)行,符合條件(超出最大緩存空間時(shí))就會(huì)執(zhí)行 map.remove(key)。由于LinkedHashMap是按照訪問順序排序的,所以此緩存策略是按照“最近最少使用”算法(在鏈表的尾部是最近剛剛使用的結(jié)點(diǎn),在鏈表的頭部是是最近最少使用的結(jié)點(diǎn))進(jìn)行剔除的。

為了實(shí)現(xiàn)多線程中的同步,對(duì)緩存的操作是放在同步塊中進(jìn)行的。

我們?cè)倏聪耮et方法:

 /**
     * 根據(jù)key查詢緩存,如果存在于緩存或者被create方法創(chuàng)建了。
     * 如果值返回了,那么它將被移動(dòng)到雙向循環(huán)鏈表的的尾部。
     * 如果如果沒有緩存的值,則返回null。
     */
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            // LinkHashMap 如果設(shè)置按照訪問順序的話,這里每次get都會(huì)重整數(shù)據(jù)順序
            mapValue = map.get(key);
            // 計(jì)算 命中次數(shù)
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            // 計(jì)算 丟失次數(shù)
            missCount++;
        }

        /*
         * 官方解釋:
         * 嘗試創(chuàng)建一個(gè)值,這可能需要很長(zhǎng)時(shí)間,并且Map可能在create()返回的值時(shí)有所不同。如果在create()執(zhí)行的時(shí)
         * 候,一個(gè)沖突的值被添加到Map,我們?cè)贛ap中刪除這個(gè)值,釋放被創(chuàng)造的值。
         */
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        /***************************
         * 不覆寫create方法走不到下面 *
         ***************************/

        /*
         * 正常情況走不到這里
         * 走到這里的話 說明 實(shí)現(xiàn)了自定義的 create(K key) 邏輯
         * 因?yàn)槟J(rèn)的 create(K key) 邏輯為null
         */
        synchronized (this) {
            // 記錄 create 的次數(shù)
            createCount++;
            // 將自定義create創(chuàng)建的值,放入LinkedHashMap中,如果key已經(jīng)存在,會(huì)返回 之前相同key 的值
            mapValue = map.put(key, createdValue);

            // 如果之前存在相同key的value,即有沖突。
            if (mapValue != null) {
                /*
                 * 有沖突
                 * 所以 撤銷 剛才的 操作
                 * 將 之前相同key 的值 重新放回去
                 */
                map.put(key, mapValue);
            } else {
                // 拿到鍵值對(duì),計(jì)算出在容量中的相對(duì)長(zhǎng)度,然后加上
                size += safeSizeOf(key, createdValue);
            }
        }

        // 如果上面 判斷出了 將要放入的值發(fā)生沖突
        if (mapValue != null) {
            /*
             * 剛才create的值被刪除了,原來的 之前相同key 的值被重新添加回去了
             * 告訴 自定義 的 entryRemoved 方法
             */
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            // 上面 進(jìn)行了 size += 操作 所以這里要重整長(zhǎng)度
            trimToSize(maxSize);
            return createdValue;
        }
    }

通篇的代碼邏輯很復(fù)雜,簡(jiǎn)單說就是如果get不到值,可以在create(key)里自定義一個(gè)value,然后put到map中。如果此時(shí)檢測(cè)到map中有該key的舊值,就撤銷put之前create的值。同樣的,對(duì)于所有要剔除的操作都要走entryRemoved方法。

注意:網(wǎng)上下載的源碼里的漢語注釋,不敢恭維。

DiskLruCache

DiskLruCache構(gòu)造方法:
DiskLruCache類是因?yàn)槭浅A款悾荒芡ㄟ^構(gòu)造方法實(shí)例化,這里通過open方法獲取DiskLruCache實(shí)例。

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

open()方法接收四個(gè)參數(shù),第一個(gè)參數(shù)指定的是數(shù)據(jù)的緩存地址,第二個(gè)參數(shù)指定當(dāng)前應(yīng)用程序的版本號(hào),第三個(gè)參數(shù)指定同一個(gè)key可以對(duì)應(yīng)多少個(gè)緩存文件,基本都是傳1,第四個(gè)參數(shù)指定最多可以緩存多少字節(jié)的數(shù)據(jù)。

這里緩存的位置通常存放在 /sdcard/Android/data/<application package>/cache 這個(gè)路徑下面,如果沒有SD卡或被移除了,可以采用如下代碼設(shè)置緩存地址。

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

對(duì)于版本號(hào)appVersion這個(gè)參數(shù),每當(dāng)版本號(hào)改變,緩存路徑下存儲(chǔ)的所有數(shù)據(jù)都會(huì)被清除掉,因?yàn)镈iskLruCache認(rèn)為當(dāng)應(yīng)用程序有版本更新的時(shí)候,所有的數(shù)據(jù)都應(yīng)該從網(wǎng)上重新獲取。

外部實(shí)例化DiskLruCache如下:

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

DiskLruCache寫入:
DiskLruCache寫入的操作是借助DiskLruCache.Editor這個(gè)類完成的,需要調(diào)用DiskLruCache的edit()方法來獲取實(shí)例。

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

由于緩存的key要和緩存的value一一對(duì)應(yīng),這里通常是將URL通過MD5編碼后作key。

MD5編碼如下:

  /**
     * MD5 算法
     * @param key
     * @return
     */
    public String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

具體編碼算法,后續(xù)再研究。

獲取editor實(shí)例后,通過newOutputStream()方法可獲得一個(gè)輸出流OutputStream。然后將此輸出流傳入網(wǎng)絡(luò)下載中,即可實(shí)現(xiàn)在文件下載的過程中,將資源緩存到本地。

完整的緩存過程如下:

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

newOutputStream()方法接收一個(gè)index參數(shù),由于前面在設(shè)置valueCount的時(shí)候指定的是1,所以這里index傳0就可以了(即索引為0)。
這里調(diào)用一下commit()方法進(jìn)行提交才能使寫入生效,調(diào)用abort()方法的話則表示放棄此次寫入。

mDiskLruCache.flush()方法是用于將內(nèi)存中的操作記錄同步到日志文件(也就是journal文件)當(dāng)中。DiskLruCache能夠正常工作的前提就是要依賴于journal文件中的內(nèi)容。前面在講解寫入緩存操作的時(shí)候我有調(diào)用過一次這個(gè)方法,但其實(shí)并不是每次寫入緩存都要調(diào)用一次flush()方法的,頻繁地調(diào)用并不會(huì)帶來任何好處,只會(huì)額外增加同步j(luò)ournal文件的時(shí)間。比較標(biāo)準(zhǔn)的做法就是在Activity的onPause()方法中去調(diào)用一次flush()方法就可以了。

DiskLruCache讀?。?/strong>

因?yàn)閷懭刖彺娴臅r(shí)候是通過MD5編碼之后的key作為唯一性標(biāo)識(shí)的,讀取的時(shí)候也是首先要將URL轉(zhuǎn)碼成Key。

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

這里通過獲得DiskLruCache.Snapshot實(shí)例,然后調(diào)用其getInputStream()就可獲得輸入流,進(jìn)而轉(zhuǎn)發(fā)成對(duì)應(yīng)的文件,如bitmap。這里getInputStream(0)的參數(shù)和newOutputStream(0)的參數(shù)是一一對(duì)應(yīng)的。

完整的讀取代碼如下:

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

我們?cè)贒iskLruCache源碼中,會(huì)看到其也用到了LinkedHashMap這個(gè)結(jié)構(gòu)體

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

這也是基于訪問順序的“最近最少使用算法”,其中的value是Entry類,表示文件的實(shí)體類。因此DiskLruCache和LruCache底層思想都是相同的,只是存儲(chǔ)的對(duì)象有所不同。

LRU算法(Least Recently Used )

緩存主要由LruCache這個(gè)類實(shí)現(xiàn),LRU是Least Recently Used 近期最少使用算法,多用于清理長(zhǎng)時(shí)間未使用的資源。

通過查看LinkedHashMap源碼可知:

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

boolean值 accessOrder參數(shù)為true表示按照使用順序排列,為false表示按照插入順序排列。

當(dāng)accessOrder 設(shè)置為 true時(shí),每當(dāng)我們更新(即調(diào)用put方法)或訪問(即調(diào)用get方法)map中的結(jié)點(diǎn)時(shí),LinkedHashMap內(nèi)部都會(huì)將這個(gè)結(jié)點(diǎn)移動(dòng)到鏈表的尾部,因此,在鏈表的尾部是最近剛剛使用的結(jié)點(diǎn),在鏈表的頭部是是最近最少使用的結(jié)點(diǎn),當(dāng)我們的緩存空間不足時(shí),就應(yīng)該持續(xù)把鏈表頭部結(jié)點(diǎn)移除掉,直到有剩余空間放置新結(jié)點(diǎn)。

引用

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

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

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

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