
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ì)更加的有效。
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)。