欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從源代碼分析Android Universal ImageLoader的緩存處理機(jī)制

 更新時(shí)間:2016年01月13日 10:10:09   作者:陳哈哈  
這篇文章主要介紹了從源代碼分析Android Universal ImageLoader的緩存處理機(jī)制 的相關(guān)資料,需要的朋友可以參考下

通過(guò)本文帶大家一起看過(guò)UIL這個(gè)國(guó)內(nèi)外大牛都追捧的圖片緩存類(lèi)庫(kù)的緩存處理機(jī)制??戳薝IL中的緩存實(shí)現(xiàn),才發(fā)現(xiàn)其實(shí)這個(gè)東西不難,沒(méi)有太多的進(jìn)程調(diào)度,沒(méi)有各種內(nèi)存讀取控制機(jī)制、沒(méi)有各種異常處理。反正UIL中不單代碼寫(xiě)的簡(jiǎn)單,連處理都簡(jiǎn)單。但是這個(gè)類(lèi)庫(kù)這么好用,又有這么多人用,那么非常有必要看看他是怎么實(shí)現(xiàn)的。先了解UIL中緩存流程的原理圖。

原理示意圖

主體有三個(gè),分別是UI,緩存模塊和數(shù)據(jù)源(網(wǎng)絡(luò))。它們之間的關(guān)系如下:


① UI:請(qǐng)求數(shù)據(jù),使用唯一的Key值索引Memory Cache中的Bitmap。

② 內(nèi)存緩存:緩存搜索,如果能找到Key值對(duì)應(yīng)的Bitmap,則返回?cái)?shù)據(jù)。否則執(zhí)行第三步。

③ 硬盤(pán)存儲(chǔ):使用唯一Key值對(duì)應(yīng)的文件名,檢索SDCard上的文件。

④ 如果有對(duì)應(yīng)文件,使用BitmapFactory.decode*方法,解碼Bitmap并返回?cái)?shù)據(jù),同時(shí)將數(shù)據(jù)寫(xiě)入緩存。如果沒(méi)有對(duì)應(yīng)文件,執(zhí)行第五步。

⑤ 下載圖片:?jiǎn)?dòng)異步線(xiàn)程,從數(shù)據(jù)源下載數(shù)據(jù)(Web)。

⑥ 若下載成功,將數(shù)據(jù)同時(shí)寫(xiě)入硬盤(pán)和緩存,并將Bitmap顯示在UI中。

接下來(lái),我們回顧一下UIL中緩存的配置(具體的見(jiàn)《UNIVERSAL IMAGE LOADER.PART 2》)。重點(diǎn)關(guān)注注釋部分,我們可以根據(jù)自己需要配置內(nèi)存、磁盤(pán)緩存的實(shí)現(xiàn)。

File cacheDir = StorageUtils.getCacheDirectory(context,
"UniversalImageLoader/Cache");
ImageLoaderConfiguration config = new
ImageLoaderConfiguration .Builder(getApplicationContext())
.maxImageWidthForMemoryCache()
.maxImageHeightForMemoryCache()
.httpConnectTimeout()
.httpReadTimeout()
.threadPoolSize()
.threadPriority(Thread.MIN_PRIORITY + )
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new UsingFreqLimitedCache()) // 你可以傳入自己的內(nèi)存緩存
.discCache(new UnlimitedDiscCache(cacheDir)) // 你可以傳入自己的磁盤(pán)緩存
.defaultDisplayImageOptions(DisplayImageOptions.createSimple())
.build(); 

UIL中的內(nèi)存緩存策略

1. 只使用的是強(qiáng)引用緩存
•LruMemoryCache(這個(gè)類(lèi)就是這個(gè)開(kāi)源框架默認(rèn)的內(nèi)存緩存類(lèi),緩存的是bitmap的強(qiáng)引用,下面我會(huì)從源碼上面分析這個(gè)類(lèi))

2.使用強(qiáng)引用和弱引用相結(jié)合的緩存有

UsingFreqLimitedMemoryCache(如果緩存的圖片總量超過(guò)限定值,先刪除使用頻率最小的bitmap)
•LRULimitedMemoryCache(這個(gè)也是使用的lru算法,和LruMemoryCache不同的是,他緩存的是bitmap的弱引用)
•FIFOLimitedMemoryCache(先進(jìn)先出的緩存策略,當(dāng)超過(guò)設(shè)定值,先刪除最先加入緩存的bitmap)
•LargestLimitedMemoryCache(當(dāng)超過(guò)緩存限定值,先刪除最大的bitmap對(duì)象)
•LimitedAgeMemoryCache(當(dāng) bitmap加入緩存中的時(shí)間超過(guò)我們?cè)O(shè)定的值,將其刪除)

3.只使用弱引用緩存

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

我們直接選擇UIL中的默認(rèn)配置緩存策略進(jìn)行分析。

ImageLoaderConfiguration config = ImageLoaderConfiguration.createDefault(context);
ImageLoaderConfiguration.createDefault(…)這個(gè)方法最后是調(diào)用Builder.build()方法創(chuàng)建默認(rèn)的配置參數(shù)的。默認(rèn)的內(nèi)存緩存實(shí)現(xiàn)是LruMemoryCache,磁盤(pán)緩存是UnlimitedDiscCache。

LruMemoryCache解析

LruMemoryCache:一種使用強(qiáng)引用來(lái)保存有數(shù)量限制的Bitmap的cache(在空間有限的情況,保留最近使用過(guò)的Bitmap)。每次Bitmap被訪(fǎng)問(wèn)時(shí),它就被移動(dòng)到一個(gè)隊(duì)列的頭部。當(dāng)Bitmap被添加到一個(gè)空間已滿(mǎn)的cache時(shí),在隊(duì)列末尾的Bitmap會(huì)被擠出去并變成適合被GC回收的狀態(tài)。
注意:這個(gè)cache只使用強(qiáng)引用來(lái)保存Bitmap。

LruMemoryCache實(shí)現(xiàn)MemoryCache,而MemoryCache繼承自MemoryCacheAware。

public interface MemoryCache extends MemoryCacheAware<String, Bitmap>

下面給出繼承關(guān)系圖


LruMemoryCache.get(…)

我相信接下去你看到這段代碼的時(shí)候會(huì)跟我一樣驚訝于代碼的簡(jiǎn)單,代碼中除了異常判斷,就是利用synchronized進(jìn)行同步控制。

/**
* Returns the Bitmap for {@code 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);
}
} 

我們會(huì)好奇,這不是就簡(jiǎn)簡(jiǎn)單單將Bitmap從map中取出來(lái)嗎?但LruMemoryCache聲稱(chēng)保留在空間有限的情況下保留最近使用過(guò)的Bitmap。不急,讓我們細(xì)細(xì)觀(guān)察一下map。他是一個(gè)LinkedHashMap<String, Bitmap>型的對(duì)象。

LinkedHashMap中的get()方法不僅返回所匹配的值,并且在返回前還會(huì)將所匹配的key對(duì)應(yīng)的entry調(diào)整在列表中的順序(LinkedHashMap使用雙鏈表來(lái)保存數(shù)據(jù)),讓它處于列表的最后。當(dāng)然,這種情況必須是在LinkedHashMap中accessOrder==true的情況下才生效的,反之就是get()方法不會(huì)改變被匹配的key對(duì)應(yīng)的entry在列表中的位置。

@Override public V get(Object key) {


/*
* This method is overridden to eliminate the need for a polymorphic
* invocation in superclass at the expense of code duplication.
*/
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null)
return null;
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
// Replace with Collections.secondaryHash when the VM is fast enough (http://b/).
int hash = secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - )];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
}
return null;
}

代碼第11行的makeTail()就是調(diào)整entry在列表中的位置,其實(shí)就是雙向鏈表的調(diào)整。它判斷accessOrder。到現(xiàn)在我們就清楚LruMemoryCache使用LinkedHashMap來(lái)緩存數(shù)據(jù),在LinkedHashMap.get()方法執(zhí)行后,LinkedHashMap中entry的順序會(huì)得到調(diào)整。那么我們?cè)趺幢WC最近使用的項(xiàng)不會(huì)被剔除呢?接下去,讓我們看看LruMemoryCache.put(...)。

LruMemoryCache.put(...)

注意到代碼第8行中的size+= sizeOf(key, value),這個(gè)size是什么呢?我們注意到在第19行有一個(gè)trimToSize(maxSize),trimToSize(...)這個(gè)函數(shù)就是用來(lái)限定LruMemoryCache的大小不要超過(guò)用戶(hù)限定的大小,cache的大小由用戶(hù)在LruMemoryCache剛開(kāi)始初始化的時(shí)候限定。

@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);
//map.put()的返回值如果不為空,說(shuō)明存在跟key對(duì)應(yīng)的entry,put操作只是更新原有key對(duì)應(yīng)的entry
Bitmap previous = map.put(key, value);
if (previous != null) {
size -= sizeOf(key, previous);
}
}
trimToSize(maxSize);
return true;
}

其實(shí)不難想到,當(dāng)Bitmap緩存的大小超過(guò)原來(lái)設(shè)定的maxSize時(shí)應(yīng)該是在trimToSize(...)這個(gè)函數(shù)中做到的。這個(gè)函數(shù)做的事情也簡(jiǎn)單,遍歷map,將多余的項(xiàng)(代碼中對(duì)應(yīng)toEvict)剔除掉,直到當(dāng)前cache的大小等于或小于限定的大小。

private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {
if (size < || (map.isEmpty() && size != )) {
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);
}
}
}

這時(shí)候我們會(huì)有一個(gè)以為,為什么遍歷一下就可以將使用最少的bitmap緩存給剔除,不會(huì)誤刪到最近使用的bitmap緩存嗎?首先,我們要清楚,LruMemoryCache定義的最近使用是指最近用get或put方式操作到的bitmap緩存。其次,之前我們直到LruMemoryCache的get操作其實(shí)是通過(guò)其內(nèi)部字段LinkedHashMap.get(...)實(shí)現(xiàn)的,當(dāng)LinkedHashMap的accessOrder==true時(shí),每一次get或put操作都會(huì)將所操作項(xiàng)(圖中第3項(xiàng))移動(dòng)到鏈表的尾部(見(jiàn)下圖,鏈表頭被認(rèn)為是最少使用的,鏈表尾被認(rèn)為是最常使用的。),每一次操作到的項(xiàng)我們都認(rèn)為它是最近使用過(guò)的,當(dāng)內(nèi)存不夠的時(shí)候被剔除的優(yōu)先級(jí)最低。需要注意的是一開(kāi)始的LinkedHashMap鏈表是按插入的順序構(gòu)成的,也就是第一個(gè)插入的項(xiàng)就在鏈表頭,最后一個(gè)插入的就在鏈表尾。假設(shè)只要剔除圖中的1,2項(xiàng)就能讓LruMemoryCache小于原先限定的大小,那么我們只要從鏈表頭遍歷下去(從1→最后一項(xiàng))那么就可以剔除使用最少的項(xiàng)了。

至此,我們就知道了LruMemoryCache緩存的整個(gè)原理,包括他怎么put、get、剔除一個(gè)元素的的策略。接下去,我們要開(kāi)始分析默認(rèn)的磁盤(pán)緩存策略了。

UIL中的磁盤(pán)緩存策略

像新浪微博、花瓣這種應(yīng)用需要加載很多圖片,本來(lái)圖片的加載就慢了,如果下次打開(kāi)的時(shí)候還需要再一次下載上次已經(jīng)有過(guò)的圖片,相信用戶(hù)的流量會(huì)讓他們的叫罵聲很響亮。對(duì)于圖片很多的應(yīng)用,一個(gè)好的磁盤(pán)緩存直接決定了應(yīng)用在用戶(hù)手機(jī)的留存時(shí)間。我們自己實(shí)現(xiàn)磁盤(pán)緩存,要考慮的太多,幸好UIL提供了幾種常見(jiàn)的磁盤(pán)緩存策略,當(dāng)然如果你覺(jué)得都不符合你的要求,你也可以自己去擴(kuò)展

•FileCountLimitedDiscCache(可以設(shè)定緩存圖片的個(gè)數(shù),當(dāng)超過(guò)設(shè)定值,刪除掉最先加入到硬盤(pán)的文件)
•LimitedAgeDiscCache(設(shè)定文件存活的最長(zhǎng)時(shí)間,當(dāng)超過(guò)這個(gè)值,就刪除該文件)
•TotalSizeLimitedDiscCache(設(shè)定緩存bitmap的最大值,當(dāng)超過(guò)這個(gè)值,刪除最先加入到硬盤(pán)的文件)
•UnlimitedDiscCache(這個(gè)緩存類(lèi)沒(méi)有任何的限制)

在UIL中有著比較完整的存儲(chǔ)策略,根據(jù)預(yù)先指定的空間大小,使用頻率(生命周期),文件個(gè)數(shù)的約束條件,都有著對(duì)應(yīng)的實(shí)現(xiàn)策略。最基礎(chǔ)的接口DiscCacheAware和抽象類(lèi)BaseDiscCache

UnlimitedDiscCache解析

UnlimitedDiscCache實(shí)現(xiàn)disk cache接口,是ImageLoaderConfiguration中默認(rèn)的磁盤(pán)緩存處理。用它的時(shí)候,磁盤(pán)緩存的大小是不受限的。

接下來(lái)我們來(lái)看看實(shí)現(xiàn)UnlimitedDiscCache的源代碼,通過(guò)源代碼我們發(fā)現(xiàn)他其實(shí)就是繼承了BaseDiscCache,這個(gè)類(lèi)內(nèi)部沒(méi)有實(shí)現(xiàn)自己獨(dú)特的方法,也沒(méi)有重寫(xiě)什么,那么我們就直接看BaseDiscCache這個(gè)類(lèi)。在分析這個(gè)類(lèi)之前,我們先想想自己實(shí)現(xiàn)一個(gè)磁盤(pán)緩存需要做多少麻煩的事情:

1、圖片的命名會(huì)不會(huì)重。你沒(méi)有辦法知道用戶(hù)下載的圖片原始的文件名是怎么樣的,因此很可能因?yàn)槲募孛麑⒂杏玫膱D片給覆蓋掉了。

2、當(dāng)應(yīng)用卡頓或網(wǎng)絡(luò)延遲的時(shí)候,同一張圖片反復(fù)被下載。

3、處理圖片寫(xiě)入磁盤(pán)可能遇到的延遲和同步問(wèn)題。

BaseDiscCache構(gòu)造函數(shù)

首先,我們看一下BaseDiscCache的構(gòu)造函數(shù):

cacheDir:文件緩存目錄

reserveCacheDir:備用的文件緩存目錄,可以為null。它只有當(dāng)cacheDir不能用的時(shí)候才有用。
fileNameGenerator:文件名生成器。為緩存的文件生成文件名。

public BaseDiscCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
if (cacheDir == null) {
throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
}
if (fileNameGenerator == null) {
throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
}
this.cacheDir = cacheDir;
this.reserveCacheDir = reserveCacheDir;
this.fileNameGenerator = fileNameGenerator;
}

我們可以看到一個(gè)fileNameGenerator,接下來(lái)我們來(lái)了解UIL具體是怎么生成不重復(fù)的文件名的。UIL中有3種文件命名策略,這里我們只對(duì)默認(rèn)的文件名策略進(jìn)行分析。默認(rèn)的文件命名策略在DefaultConfigurationFactory.createFileNameGenerator()。它是一個(gè)HashCodeFileNameGenerator。真的是你意想不到的簡(jiǎn)單,就是運(yùn)用String.hashCode()進(jìn)行文件名的生成。

public class HashCodeFileNameGenerator implements FileNameGenerator {
@Override
public String generate(String imageUri) {
return String.valueOf(imageUri.hashCode());
}
}

BaseDiscCache.save()

分析完了命名策略,再看一下BaseDiscCache.save(...)方法。注意到第2行有一個(gè)getFile()函數(shù),它主要用于生成一個(gè)指向緩存目錄中的文件,在這個(gè)函數(shù)里面調(diào)用了剛剛介紹過(guò)的fileNameGenerator來(lái)生成文件名。注意第3行的tmpFile,它是用來(lái)寫(xiě)入bitmap的臨時(shí)文件(見(jiàn)第8行),然后就把這個(gè)文件給刪除了。大家可能會(huì)困惑,為什么在save()函數(shù)里面沒(méi)有判斷要寫(xiě)入的bitmap文件是否存在的判斷,我們不由得要看看UIL中是否有對(duì)它進(jìn)行判斷。還記得我們?cè)凇稄拇a分析Android-Universal-Image-Loader的圖片加載、顯示流程》介紹的,UIL加載圖片的一般流程是先判斷內(nèi)存中是否有對(duì)應(yīng)的Bitmap,再判斷磁盤(pán)(disk)中是否有,如果沒(méi)有就從網(wǎng)絡(luò)中加載。最后根據(jù)原先在UIL中的配置判斷是否需要緩存Bitmap到內(nèi)存或磁盤(pán)中。也就是說(shuō),當(dāng)需要調(diào)用BaseDiscCache.save(...)之前,其實(shí)已經(jīng)判斷過(guò)這個(gè)文件不在磁盤(pán)中。

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 {
IoUtils.closeSilently(imageStream);
if (loaded && !tmpFile.renameTo(imageFile)) {
loaded = false;
}
if (!loaded) {
tmpFile.delete();
}
}
return loaded;
}

BaseDiscCache.get()

BaseDiscCache.get()方法內(nèi)部調(diào)用了BaseDiscCache.getFile(...)方法,讓我們來(lái)分析一下這個(gè)在之前碰過(guò)的函數(shù)。 第2行就是利用fileNameGenerator生成一個(gè)唯一的文件名。第3~8行是指定緩存目錄,這時(shí)候你就可以清楚地看到cacheDir和reserveCacheDir之間的關(guān)系了,當(dāng)cacheDir不可用的時(shí)候,就是用reserveCachedir作為緩存目錄了。

最后返回一個(gè)指向文件的對(duì)象,但是要注意當(dāng)File類(lèi)型的對(duì)象指向的文件不存在時(shí),file會(huì)為null,而不是報(bào)錯(cuò)。

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

總結(jié)

現(xiàn)在,我們已經(jīng)分析了UIL的緩存機(jī)制。其實(shí)從UIL的緩存機(jī)制的實(shí)現(xiàn)并不是很復(fù)雜,雖然有各種緩存機(jī)制,但是簡(jiǎn)單地說(shuō):內(nèi)存緩存其實(shí)就是利用Map接口的對(duì)象在內(nèi)存中進(jìn)行緩存,可能有不同的存儲(chǔ)機(jī)制。磁盤(pán)緩存其實(shí)就是將文件寫(xiě)入磁盤(pán)。

相關(guān)文章

最新評(píng)論