【Java&Android開源庫程式碼剖析】のAndroid-Universal-Image-Loader-part1
做Android app開發的同學應該都聽說過或者用過nostra13的Android-Universal-Image-Loader開源庫,它在圖片非同步載入、快取和顯示等方面提供了強大靈活的框架。之前介紹過的android-smart-image-view開源庫跟它比起來,真是小巫見大巫了,不僅在功能上,而且在程式碼量上都差別很大。當然我們在選取開源庫的時候並不是功能越強大越好,一切都要看具體需求,只選取能夠滿足需求的就行,Less Is More。
Android-Universal-Image-Loader可以到https://github.com/nostra13/Android-Universal-Image-Loader上面獲取,至於它的使用方法,作者寫了3篇博文進行了詳細的介紹
(http://www.intexsoft.com/blog/item/68-universal-image-loader-part-1.html),本文就不再贅述了。首先,讓我們先瞄一下Image-Loader庫的整體包結構:
可以看到,包結構基本上也是根據功能來命名的,即圖片非同步載入、快取和顯示以及一些工具類。這篇文章我們先來分析下圖片的記憶體快取實現原理,記憶體快取在介紹smart-image-view開源庫時已經接觸過,只不過當時的實現很簡單,Bitmap以軟引用的方式直接放到CurrentHashMap中,沒有任何過期刪除策略,也沒有限制快取大小等等。Image-Loader庫將這些都考慮在內了,是一個較完整的記憶體快取實現,使用者可以根據需要選擇已經實現的策略,也可以定製自己專案中需要的策略。ImageLoader庫大量使用了面向介面設計,面向介面設計方式專注於物件所提供的服務或模組的職責,而不是它們的實現。它明確地將規範檢視從實現檢視中剝離出來。記憶體快取實現程式碼在memory和memory.impl這兩個包中,前者就是規範檢視,後者是實現檢視。memory包中有MemoryCacheAware介面和BaseMemoryCache和LimitedMemoryCache兩個抽象類,加上memory.impl包中的WeakMemoryCache類,類圖如下所示:
MemoryCacheAware介面以泛型方式定義了實現快取所需的基礎規約,包括寫快取、讀快取、刪除快取和遍歷快取等,如下所示:
- /**
- * Interface for memory cache
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.0.0
- */
- public interface MemoryCacheAware<K, V> {
- /**
- * Puts value into cache by key
- *
- * @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into
- * cache
- */
- boolean put(K key, V value);
- /** Returns value by key. If there is no value for key then null will be returned. */
- V get(K key);
- /** Removes item by key */
- void remove(K key);
- /** Returns all keys of cache */
- Collection<K> keys();
- /** Remove all items from cache */
- void clear();
- }
介面定義好,一般都會提供一個介面的基礎實現類,這個類需要實現介面中的方法,並提供子類可以公用的操作,由smart-image-view這篇文章我們知道,在Android中圖片最終表現為Bitmap的例項,為了Bitmap及時釋放,一般記憶體快取中不會直接存放Bitmap的強引用,而是使用弱引用SoftReference,但在Java中,還存在另外兩種引用型別,即WeakReference和PhantomReference,考慮到通用性,在記憶體快取的抽象基類BaseMemoryCache中將使用上面三種引用的父類Reference。記憶體快取依然使用雜湊表來實現。程式碼如下:
- /** Stores not strong references to objects */
- private final Map<K, Reference<V>> softMap = Collections.synchronizedMap(new HashMap<K, Reference<V>>());
回顧我們在smart-image-view庫中的雜湊表定義:
- private ConcurrentHashMap<String, SoftReference<Bitmap>> memoryCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>();;
可以發現最大的區別是一個使用Collections.synchronizedMap,一個使用ConcurrentHashMap,兩者都是實現執行緒安全的HashMap,區別在哪裡呢?感興趣的可以自己看JDK原始碼,或者百度之,這裡只給出結論:ConcurrentHashMap的讀寫效能要比Collections.synchronizedMap高,儘量使用前者。下面就看下BaseMemoryCache的程式碼吧:
- /**
- * Base memory cache. Implements common functionality for memory cache. Provides object references (
- * {@linkplain Reference not strong}) storing.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.0.0
- */
- public abstract class BaseMemoryCache<K, V> implements MemoryCacheAware<K, V> {
- /** Stores not strong references to objects */
- private final Map<K, Reference<V>> softMap = Collections.synchronizedMap(new HashMap<K, Reference<V>>());
- @Override
- public V get(K key) {
- V result = null;
- Reference<V> reference = softMap.get(key);
- if (reference != null) {
- result = reference.get();
- }
- return result;
- }
- @Override
- public boolean put(K key, V value) {
- softMap.put(key, createReference(value));
- return true;
- }
- @Override
- public void remove(K key) {
- softMap.remove(key);
- }
- @Override
- public Collection<K> keys() {
- synchronized (softMap) {
- return new HashSet<K>(softMap.keySet());
- }
- }
- @Override
- public void clear() {
- softMap.clear();
- }
- /** Creates {@linkplain Reference not strong} reference of value */
- protected abstract Reference<V> createReference(V value);
- }
基本上就是HashMap的操作,由於具體Reference的實現需要看到底用的哪種引用,因此,這裡將createReference定義成抽象函式,讓BaseMemoryCache的子類去定製實現。
BaseMemoryCache的一個最簡單的子類實現是WeakMemoryCache類,它僅僅是實現了createReference抽象方法,返回Bitmap物件的弱引用:
- public class WeakMemoryCache extends BaseMemoryCache<String, Bitmap> {
- @Override
- protected Reference<Bitmap> createReference(Bitmap value) {
- return new WeakReference<Bitmap>(value);
- }
- }
BaseMemoryCache的另一個子類是LimitedMemoryCache,它也是抽象類,定義了實現記憶體快取限制策略的公共操作。既然要限制快取大小,那麼首先需要定義預設的快取最大值,同時需要維護一個表示當前快取已佔用大小的變數,考慮到多執行緒問題,這個變數值得增減必須保證是原子的,因此,該變數型別選用AtomicInteger。如下所示:
- 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;
同時,為了計算已新增到快取中的資料大小,需要新增一個指向強引用的資料結構來進行記錄,而不是使用BaseMemoryCache中的softMap雜湊表,因為softMap中存放的是Reference類,裡面的資料可能會被GC回收,用來統計已佔用大小時不準確。指向資料強引用的資料結構選用LinkedList,定義如下,同樣採用執行緒安全版本。
- /**
- * 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<V> hardCache = Collections.synchronizedList(new LinkedList<V>());
往LimitedMemoryCache中新增資料時,首先要計算出當前這個資料的大小(getSize()),然後加上已佔用快取大小後,跟快取最大值相比較,超過快取最大值時,就需要根據快取清理策略(removeNext())刪除以前的一些資料,直到新增資料後,總佔用大小在最大值之內為止。上面提到的getSize()函式和removeNext函式需要子類定製,所以定義成了抽象函式。這是典型的模板方法模式的應用,模板方法模式定義一個操作中演算法的步驟,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。下面的put函式中實現的就是演算法的步驟,而getSize()和removeNext()函式則是子類可重定義的特定步驟。相關程式碼如下所示:
- @Override
- public boolean put(K key, V 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) {
- V removedValue = removeNext();
- 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;
- }
- protected abstract int getSize(V value);
- protected abstract V removeNext();
接下來就介紹LimitedMemoryCache的幾個子類的具體實現,它們分別是FIFOLimitedMemoryCache、LargestLimitedMemoryCache、LRULimitedMemoryCache和UsingFreqLimitedMemoryCache。類圖如下:
【FIFOLimitedMemoryCache類】
FIFO演算法意思是在快取超過最大值時,快取清理策略是將最老的資料清理掉,因此使用佇列這樣的資料結構就可以很好的實現,在Java中,LinkedList類可以實現這樣的功能。
- private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());
而父類LimitedMemoryCache中也定義了類似的資料結構hardCache專門用於計算快取的資料大小,只不過它是私有的,子類不能使用。但這樣就會造成在FIFOLimitedMemoryCache類中存在兩個類似的資料結構,記憶體佔用會變大,事實上,FIFOLimitedMemoryCache中的queue完全可以直接使用父類的hardCache,作者之所以沒有直接使用,應該是考慮到程式碼整體類層次結構清晰的問題,畢竟hardCache專門是用於計算快取大小的,子類實現方式很多種,很多子類並不需要直接使用hardCache,所以單獨一個FIFOLimitedMemoryCache以空間來換取程式碼結構的清晰也是可以理解的。寫快取和清快取程式碼如下:
- @Override
- public boolean put(String key, Bitmap value) {
- if (super.put(key, value)) {
- queue.add(value);
- return true;
- } else {
- return false;
- }
- }
- @Override
- public void remove(String key) {
- Bitmap value = super.get(key);
- if (value != null) {
- queue.remove(value);
- }
- super.remove(key);
- }
- @Override
- public void clear() {
- queue.clear();
- super.clear();
- }
前面說到的父類總共有3個抽象函式,需要子類予以實現,程式碼如下:
- @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);
- }
getSize函式中可以看到計算Bitmap佔用位元組大小的典型方法。removeNext函式中移除佇列queue頭部的資料,這個資料是最先進入佇列的資料。而Bitmap的Reference使用的是弱引用,它和軟引用的區別是,弱引用物件擁有更短暫的生命週期,在垃圾回收器執行緒掃描它所管轄的記憶體區域過程中,只要發現只具有弱引用的物件,那麼不管當前記憶體空間是否足夠,都會回收弱引用物件佔用的記憶體。這樣可以更好的防止出現OutOfMemoryError錯誤。
【LargestLimitedMemoryCache類】
當快取超出最大值限制時,清理策略是將佔用空間最大的資料清理掉。因此,需要一個維護Bitmap物件到它佔用大小的對映,這裡使用的還是HashMap:
- /**
- * 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> valueSizes = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());
同理,關鍵程式碼還是在父類幾個抽象函式的實現,其中getSize()和createReference()函式實現和FIFOLimitedMemoryCache類一樣,removeNext函式實現如下:
- @Override
- protected Bitmap removeNext() {
- Integer maxSize = null;
- Bitmap largestValue = null;
- Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet();
- synchronized (valueSizes) {
- for (Entry<Bitmap, Integer> entry : entries) {
- if (largestValue == null) {
- largestValue = entry.getKey();
- maxSize = entry.getValue();
- } else {
- Integer size = entry.getValue();
- if (size > maxSize) {
- maxSize = size;
- largestValue = entry.getKey();
- }
- }
- }
- }
- valueSizes.remove(largestValue);
- return largestValue;
- }
基本原理是遍歷valueSizes這個雜湊表,比較並得到佔用空間最大的Bitmap物件,然後刪除它即可。這裡要注意的一點是遍歷時要加入synchronized同步機制。
【LRULimitedMemoryCache類】
LRU即Least Recently Used,快取清理策略是將最近最少使用的Bitmap物件刪除掉。按Java中剛好有這樣一個資料結構可以實現這個策略,它就是LinkedHashMap類。
- 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));
LinkedHashMap是HashMap的子類,注意它的建構函式第三個引數accessOrder,它定義了LinkedHashMap的排序模式,accessOrder為true時,表示LinkedHashMap中資料排序按照訪問的順序,當為false時,表示資料排序按照資料插入的順序。而我們要實現LRU,就需要把accessOrder設定為true,同時,在讀快取時不能像FIFOLimitedMemoryCache類和LargestLimitedMemoryCache類一樣使用父類BaseMemoryCache的get方法,而是應該重寫該方法如下所示:
- @Override
- public Bitmap get(String key) {
- lruCache.get(key); // call "get" for LRU logic
- return super.get(key);
- }
當accessOrder設定為true時,LinkedHashMap就維護了記錄的訪問順序,這時使用Iterator 遍歷LinkedHashMap時,先得到的記錄肯定就是最近最不常使用的那個記錄了,LRU演算法水到渠成:
- @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;
- }
【UsingFreqLimitedMemoryCache類】
和LargestLimitedMemoryCache類實現類似,只不過一個是將佔用空間最大的記錄剔除,一個是將訪問次數最少的記錄剔除,所用資料結構自然類似:
- /**
- * 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>());
因為要記錄訪問次數,所以需要重寫父類的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函式實現原理是遍歷usingCounts雜湊表中的記錄,比較它們的訪問次數,並選取訪問次數最少的一個刪除之,程式碼如下所示,同LargestLimitedMemoryCache類,要注意使用synchronized關鍵字同步保護。
- @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();
- }
- }
- }
- }
- usingCounts.remove(leastUsedValue);
- return leastUsedValue;
- }
最後,在memory.impl包中還有幾個類是直接實現MemoryCacheAware介面的,我們先來看下他們的類結構圖:
從類圖可以看到,FuzzyKeyMemoryCache類和LimitedAgeMemoryCache類都實現了MemoryCacheAware介面,同時聚合了MemoryCacheAware型別的物件。熟悉設計模式的同學應該能夠一眼看出這個就是裝飾者模式的應用。裝飾者(Decorator)模式用於動態地給一個物件新增一些額外的職責,就增加功能而言,Decorator模式相比生成子類更為靈活。在Decorator模式的實現中,為了能夠實現和原來使用被裝飾物件的程式碼的無縫結合,裝飾者類需要實現與被裝飾物件相同的介面,同時在裝飾者類中轉調被裝飾物件的功能,在轉調的前後新增新的功能。就我們的程式碼來說,被裝飾物件就是實現了MemoryCacheAware介面的類物件,裝飾者物件就是FuzzyKeyMemoryCache類和LimitedAgeMemoryCache類的物件。更詳細的關於Decorator模式的瞭解查閱設計模式的書籍。
【FuzzyKeyMemoryCache類】
在之前實現記憶體快取的對映時,是一個key對應一個value,而這個裝飾者類提供的額外功能是:允許多個key對應同一個value,後面插入的key-value對會覆蓋以前存在的key-value對。這個類主要用於Image-Loader庫內部使用。在FuzzyKeyMemoryCache類的實現中,使用Comparator類實現多個key是否相等的判斷,核心程式碼在put函式中:
- /**
- * Decorator for {@link MemoryCacheAware}. Provides special feature for cache: some different keys are considered as
- * equals (using {@link Comparator comparator}). And when you try to put some value into cache by key so entries with
- * "equals" keys will be removed from cache before.<br />
- * <b>NOTE:</b> Used for internal needs. Normally you don't need to use this class.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.0.0
- */
- public class FuzzyKeyMemoryCache<K, V> implements MemoryCacheAware<K, V> {
- private final MemoryCacheAware<K, V> cache;
- private final Comparator<K> keyComparator;
- public FuzzyKeyMemoryCache(MemoryCacheAware<K, V> cache, Comparator<K> keyComparator) {
- this.cache = cache;
- this.keyComparator = keyComparator;
- }
- @Override
- public boolean put(K key, V value) {
- // Search equal key and remove this entry
- synchronized (cache) {
- K keyToRemove = null;
- for (K cacheKey : cache.keys()) {
- if (keyComparator.compare(key, cacheKey) == 0) {
- keyToRemove = cacheKey;
- break;
- }
- }
- if (keyToRemove != null) {
- cache.remove(keyToRemove);
- }
- }
- return cache.put(key, value);
- }
- @Override
- public V get(K key) {
- return cache.get(key);
- }
- @Override
- public void remove(K key) {
- cache.remove(key);
- }
- @Override
- public void clear() {
- cache.clear();
- }
- @Override
- public Collection<K> keys() {
- return cache.keys();
- }
- }
【LimitedAgeMemoryCache類】
在前面介紹過的MemoryCacheAware介面實現類中,有時可能需要新增這樣一個額外的功能:當快取的物件存在超過一定時間時,將其清理掉,LimitedAgeMemoryCache這個裝飾者類就是實現這樣一個功能。首先,實現一個快取物件key到已存活時間的對映表,在獲取快取物件時,判斷該物件是否超過最大存活時間,如果是則將其移除。程式碼如下所示:
- /**
- * Decorator for {@link MemoryCacheAware}. Provides special feature for cache: if some cached object age exceeds defined
- * value then this object will be removed from cache.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see MemoryCacheAware
- * @since 1.3.1
- */
- public class LimitedAgeMemoryCache<K, V> implements MemoryCacheAware<K, V> {
- private final MemoryCacheAware<K, V> cache;
- private final long maxAge;
- private final Map<K, Long> loadingDates = Collections.synchronizedMap(new HashMap<K, Long>());
- /**
- * @param cache Wrapped memory cache
- * @param maxAge Max object age <b>(in seconds)</b>. If object age will exceed this value then it'll be removed from
- * cache on next treatment (and therefore be reloaded).
- */
- public LimitedAgeMemoryCache(MemoryCacheAware<K, V> cache, long maxAge) {
- this.cache = cache;
- this.maxAge = maxAge * 1000; // to milliseconds
- }
- @Override
- public boolean put(K key, V value) {
- boolean putSuccesfully = cache.put(key, value);
- if (putSuccesfully) {
- loadingDates.put(key, System.currentTimeMillis());
- }
- return putSuccesfully;
- }
- @Override
- public V get(K key) {
- Long loadingDate = loadingDates.get(key);
- if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {
- cache.remove(key);
- loadingDates.remove(key);
- }
- return cache.get(key);
- }
- @Override
- public void remove(K key) {
- cache.remove(key);
- loadingDates.remove(key);
- }
- @Override
- public Collection<K> keys() {
- return cache.keys();
- }
- @Override
- public void clear() {
- cache.clear();
- loadingDates.clear();
- }
- }
——歡迎轉載,請註明出處 http://blog.csdn.net/asce1885 ,未經本人同意請勿用於商業用途,謝謝——
相關文章
- 【Java&Android開源庫程式碼剖析】のandroid-smart-image-viewJavaAndroidView
- PHP非字母數字の程式碼PHP
- 開源電路分享のFalling Star Board
- 開源小程式原始碼原始碼
- 鴻蒙 OS 程式碼正式開源!!鴻蒙
- Boost原始碼剖析開篇原始碼
- OkHttp 開源庫使用與原始碼解析HTTP原始碼
- Android開源庫——EventBus原始碼解析Android原始碼
- 【移動開發】Checkout開源庫原始碼解析移動開發原始碼
- openGauss不僅程式碼開源
- ArrayList原始碼剖析與程式碼實測原始碼
- 開源公開課丨 ChengYing 安裝原理剖析
- iOS 開源庫系列 Aspects核心原始碼分析iOS原始碼
- 優秀開源庫SDWebImage原始碼淺析Web原始碼
- Android簡易手勢密碼開源庫Android密碼
- 開源閉源專案程式碼質量對比
- ZStack原始碼剖析之二次開發——在Utility上堆程式碼原始碼
- 【開源社】您會為開源專案貢獻程式碼嗎?
- EE4J程式碼開啟開源之旅
- 一刻社群程式碼開源啦
- 如何研究開源專案的程式碼?
- 開源介面庫
- 開源無程式碼 / 低程式碼平臺 NocoBase 0.20:支援多資料來源
- 【全開源】AJAX家政系統原始碼小程式前後端開源原始碼原始碼後端
- 商湯開源最大目標跟蹤庫PySOT,程式碼已正式上線!
- Java&Android開發-淺析ServiceLoader類JavaAndroid
- 【程式碼優化】List.remove() 剖析優化REM
- 開源專案剖析之apache-common-poolApache
- CodeGuide 300+文件、100+程式碼庫,一個指導程式設計師寫程式碼的,Github 倉庫開源啦!GUIIDE程式設計師Github
- 最新脫單盲盒系統 程式程式碼全開源
- 自己做一個ChatGPT微信小程式(程式碼開源)ChatGPT微信小程式
- ZStack原始碼剖析之核心庫鑑賞——Defer原始碼
- 如何更新開源版來客電商程式碼?
- 個人 Laravel 論壇專案 (程式碼開源)Laravel
- 谷歌程式碼評審指南已經開源谷歌
- OracleSugar ORM框架的誕生,程式碼開源OracleORM框架
- github釋出開源專案程式碼教程Github
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼