講到圖檔請求,主要涉及到網絡請求,記憶體緩存,硬碟緩存等原理和4大引用的問題,概括起來主要有以下幾個内容:
原理示意圖
主體有三個,分别是ui,緩存子產品和資料源(網絡)。它們之間的關系如下:
① ui:請求資料,使用唯一的key值索引memory cache中的bitmap。
② 記憶體緩存:緩存搜尋,如果能找到key值對應的bitmap,則傳回資料。否則執行第三步。
③ 硬碟存儲:使用唯一key值對應的檔案名,檢索sdcard上的檔案。
④ 如果有對應檔案,使用bitmapfactory.decode*方法,解碼bitmap并傳回資料,同時将資料寫入緩存。如果沒有對應檔案,執行第五步。
⑤ 下載下傳圖檔:啟動異步線程,從資料源下載下傳資料(web)。
⑥ 若下載下傳成功,将資料同時寫入硬碟和緩存,并将bitmap顯示在ui中。
uil中的記憶體緩存政策
1. 隻使用的是強引用緩存
lrumemorycache(這個類就是這個開源架構預設的記憶體緩存類,緩存的是bitmap的強引用,下面我會從源碼上面分析這個類)
2.使用強引用和弱引用相結合的緩存有
usingfreqlimitedmemorycache(如果緩存的圖檔總量超過限定值,先删除使用頻率最小的bitmap)
lrulimitedmemorycache(這個也是使用的lru算法,和lrumemorycache不同的是,他緩存的是bitmap的弱引用) fifolimitedmemorycache(先進先出的緩存政策,當超過設定值,先删除最先加入緩存的bitmap) largestlimitedmemorycache(當超過緩存限定值,先删除最大的bitmap對象)
limitedagememorycache(當 bitmap加入緩存中的時間超過我們設定的值,将其删除)
3.隻使用弱引用緩存
weakmemorycache(這個類緩存bitmap的總大小沒有限制,唯一不足的地方就是不穩定,緩存的圖檔容易被回收掉)
我們直接選擇uil中的預設配置緩存政策進行分析。
<code>1.</code><code>imageloaderconfiguration config = imageloaderconfiguration.createdefault(context);</code>
imageloaderconfiguration.createdefault(…)這個方法最後是調用builder.build()方法建立預設的配置參數的。預設的記憶體緩存實作是lrumemorycache,磁盤緩存是unlimiteddisccache。
是以android在以後的版本中建議使用lrumemorycache進行管理。
lrumemorycache:一種使用強引用來儲存有數量限制的bitmap的cache(在空間有限的情況,保留最近使用過的bitmap)。每次bitmap被通路時,它就被移動到一個隊列的頭部。當bitmap被添加到一個空間已滿的cache時,在隊列末尾的bitmap會被擠出去并變成适合被gc回收的狀态。
注意:這個cache隻使用強引用來儲存bitmap。
lrumemorycache實作memorycache,而memorycache繼承自memorycacheaware。
<code>1.</code><code>public</code> <code>interface</code> <code>memorycache </code><code>extends</code> <code>memorycacheaware<string, bitmap></code>
下面給出繼承關系圖
通過跟蹤lrumemorycache.get()的代碼我們發現,lrumemorycache聲稱保留在空間有限的情況下保留最近使用過的bitmap,這是一個linkedhashmap<string,
bitmap>,icache的源碼緩存的代碼如下:
@override
public icache<string, bitmap> getbitmapcache() {
if (bitmapcache == null) {
bitmapcache = new icache<string, bitmap>() {
@override
public void remove(string key) {
imageloader.getinstance().getmemorycache().remove(key);
}
public boolean put(string key, bitmap value) {
if (key == null || value == null) return false;
return imageloader.getinstance().getmemorycache().put(key, value);
public collection<string> keys() {
return null;
public bitmap get(string key) {
return imageloader.getinstance().getmemorycache().get(key);
public void clear() {
imageloader.getinstance().getmemorycache().clear();
};
}
return bitmapcache;
}
注意到代碼第8行中的size+=
sizeof(key, value),這個size是什麼呢?我們注意到在第19行有一個trimtosize(maxsize),trimtosize(...)這個函數就是用來限定lrumemorycache的大小不要超過使用者限定的大小,cache的大小由使用者在lrumemorycache剛開始初始化的時候限定。
<code></code>
是以不難了解我們在用universal-image-loader做圖檔加載的時候,有時候圖檔緩存超過門檻值的時候,會去重新重伺服器加載了
當bitmap緩存的大小超過原來設定的maxsize時應該是在trimtosize(...)這個函數中做到的。這個函數做的事情也簡單,周遊map,将多餘的項(代碼中對應toevict)剔除掉,直到目前cache的大小等于或小于限定的大小。
這時候我們會有一個以為,為什麼周遊一下就可以将使用最少的bitmap緩存給剔除,不會誤删到最近使用的bitmap緩存嗎?首先,我們要清楚,lrumemorycache定義的最近使用是指最近用get或put方式操作到的bitmap緩存。其次,之前我們直到lrumemorycache的get操作其實是通過其内部字段linkedhashmap.get(...)實作的,當linkedhashmap的accessorder==true時,每一次get或put操作都會将所操作項(圖中第3項)移動到連結清單的尾部(見下圖,連結清單頭被認為是最少使用的,連結清單尾被認為是最常使用的。),每一次操作到的項我們都認為它是最近使用過的,當記憶體不夠的時候被剔除的優先級最低。需要注意的是一開始的linkedhashmap連結清單是按插入的順序構成的,也就是第一個插入的項就在連結清單頭,最後一個插入的就在連結清單尾。假設隻要剔除圖中的1,2項就能讓lrumemorycache小于原先限定的大小,那麼我們隻要從連結清單頭周遊下去(從1→最後一項)那麼就可以剔除使用最少的項了。
至此,我們就知道了lrumemorycache緩存的整個原理,包括他怎麼put、get、剔除一個元素的的政策。接下去,我們要開始分析預設的磁盤緩存政策了。
uil中的磁盤緩存政策
幸好uil提供了幾種常見的磁盤緩存政策,你也可以自己去擴充,可以根據他提供的幾種緩存政策做進一步的緩存值的限制,
filecountlimiteddisccache(可以設定緩存圖檔的個數,當超過設定值,删除掉最先加入到硬碟的檔案) limitedagedisccache(設定檔案存活的最長時間,當超過這個值,就删除該檔案) totalsizelimiteddisccache(設定緩存bitmap的最大值,當超過這個值,删除最先加入到硬碟的檔案)
unlimiteddisccache(這個緩存類沒有任何的限制)
usingfreqlimitedmemorycache(如果緩存的圖檔總量超過限定值,先删除使用頻率最小的bitmap)
lrulimitedmemorycache(這個也是使用的lru算法,和lrumemorycache不同的是,他緩存的是bitmap的弱引用)
fifolimitedmemorycache(先進先出的緩存政策,當超過設定值,先删除最先加入緩存的bitmap)
largestlimitedmemorycache(當超過緩存限定值,先删除最大的bitmap對象)
在uil中有着比較完整的存儲政策,根據預先指定的空間大小,使用頻率(生命周期),檔案個數的限制條件,都有着對應的實作政策。最基礎的接口disccacheaware和抽象類basedisccache
接下來我們看一些硬碟緩存的一些政策:
接下來就給大家分析分析硬碟緩存的政策,這個架構也提供了幾種常見的緩存政策,當然如果你覺得都不符合你的要求,你也可以自己去擴充
filecountlimiteddisccache(可以設定緩存圖檔的個數,當超過設定值,删除掉最先加入到硬碟的檔案)
limitedagedisccache(設定檔案存活的最長時間,當超過這個值,就删除該檔案)
totalsizelimiteddisccache(設定緩存bitmap的最大值,當超過這個值,删除最先加入到硬碟的檔案)
下面我們就來分析分析totalsizelimiteddisccache的源碼實作
/*******************************************************************************
* copyright 2011-2013 sergey tarasevich
*
* licensed under the apache license, version 2.0 (the "license");
* you may not use this file except in compliance with the license.
* you may obtain a copy of the license at
* http://www.apache.org/licenses/license-2.0
* unless required by applicable law or agreed to in writing, software
* distributed under the license is distributed on an "as is" basis,
* without warranties or conditions of any kind, either express or implied.
* see the license for the specific language governing permissions and
* limitations under the license.
*******************************************************************************/
package com.nostra13.universalimageloader.cache.disc.impl;
import com.nostra13.universalimageloader.cache.disc.limiteddisccache;
import com.nostra13.universalimageloader.cache.disc.naming.filenamegenerator;
import com.nostra13.universalimageloader.core.defaultconfigurationfactory;
import com.nostra13.universalimageloader.utils.l;
import java.io.file;
/**
* disc cache limited by total cache size. if cache size exceeds specified limit then file with the most oldest last
* usage date will be deleted.
* @author sergey tarasevich (nostra13[at]gmail[dot]com)
* @see limiteddisccache
* @since 1.0.0
*/
public class totalsizelimiteddisccache extends limiteddisccache {
private static final int min_normal_cache_size_in_mb = 2;
private static final int min_normal_cache_size = min_normal_cache_size_in_mb * 1024 * 1024;
/**
* @param cachedir directory for file caching. <b>important:</b> specify separate folder for cached files. it's
* needed for right cache limit work.
* @param maxcachesize maximum cache directory size (in bytes). if cache size exceeds this limit then file with the
* most oldest last usage date will be deleted.
*/
public totalsizelimiteddisccache(file cachedir, int maxcachesize) {
this(cachedir, defaultconfigurationfactory.createfilenamegenerator(), maxcachesize);
}
* @param cachedir directory for file caching. <b>important:</b> specify separate folder for cached files. it's
* needed for right cache limit work.
* @param filenamegenerator name generator for cached files
* @param maxcachesize maximum cache directory size (in bytes). if cache size exceeds this limit then file with the
* most oldest last usage date will be deleted.
public totalsizelimiteddisccache(file cachedir, filenamegenerator filenamegenerator, int maxcachesize) {
super(cachedir, filenamegenerator, maxcachesize);
if (maxcachesize < min_normal_cache_size) {
l.w("you set too small disc cache size (less than %1$d mb)", min_normal_cache_size_in_mb);
}
@override
protected int getsize(file file) {
return (int) file.length();
}
這個類是繼承limiteddisccache,除了兩個構造函數之外,還重寫了getsize()方法,傳回檔案的大小,接下來我們就來看看limiteddisccache
package com.nostra13.universalimageloader.cache.disc;
import java.util.collections;
import java.util.hashmap;
import java.util.map;
import java.util.map.entry;
import java.util.set;
import java.util.concurrent.atomic.atomicinteger;
* abstract disc cache limited by some parameter. if cache exceeds specified limit then file with the most oldest last
* @see basedisccache
* @see filenamegenerator
public abstract class limiteddisccache extends basedisccache {
private static final int invalid_size = -1;
//記錄緩存檔案的大小
private final atomicinteger cachesize;
//緩存檔案的最大值
private final int sizelimit;
private final map<file, long> lastusagedates = collections.synchronizedmap(new hashmap<file, long>());
* @param cachedir directory for file caching. <b>important:</b> specify separate folder for cached files. it's
* needed for right cache limit work.
* @param sizelimit cache limit value. if cache exceeds this limit then file with the most oldest last usage date
* will be deleted.
public limiteddisccache(file cachedir, int sizelimit) {
this(cachedir, defaultconfigurationfactory.createfilenamegenerator(), sizelimit);
* @param sizelimit cache limit value. if cache exceeds this limit then file with the most oldest last usage date
* will be deleted.
public limiteddisccache(file cachedir, filenamegenerator filenamegenerator, int sizelimit) {
super(cachedir, filenamegenerator);
this.sizelimit = sizelimit;
cachesize = new atomicinteger();
calculatecachesizeandfillusagemap();
* 另開線程計算cachedir裡面檔案的大小,并将檔案和最後修改的毫秒數加入到map中
private void calculatecachesizeandfillusagemap() {
new thread(new runnable() {
@override
public void run() {
int size = 0;
file[] cachedfiles = cachedir.listfiles();
if (cachedfiles != null) { // rarely but it can happen, don't know why
for (file cachedfile : cachedfiles) {
//getsize()是一個抽象方法,子類自行實作getsize()的邏輯
size += getsize(cachedfile);
//将檔案的最後修改時間加入到map中
lastusagedates.put(cachedfile, cachedfile.lastmodified());
}
cachesize.set(size);
}
}
}).start();
* 将檔案添加到map中,并計算緩存檔案的大小是否超過了我們設定的最大緩存數
* 超過了就删除最先加入的那個檔案
public void put(string key, file file) {
//要加入檔案的大小
int valuesize = getsize(file);
//擷取目前緩存檔案大小總數
int curcachesize = cachesize.get();
//判斷是否超過設定的最大緩存值
while (curcachesize + valuesize > sizelimit) {
int freedsize = removenext();
if (freedsize == invalid_size) break; // cache is empty (have nothing to delete)
curcachesize = cachesize.addandget(-freedsize);
cachesize.addandget(valuesize);
long currenttime = system.currenttimemillis();
file.setlastmodified(currenttime);
lastusagedates.put(file, currenttime);
* 根據key生成檔案
public file get(string key) {
file file = super.get(key);
return file;
* 硬碟緩存的清理
public void clear() {
lastusagedates.clear();
cachesize.set(0);
super.clear();
* 擷取最早加入的緩存檔案,并将其删除
private int removenext() {
if (lastusagedates.isempty()) {
return invalid_size;
long oldestusage = null;
file mostlongusedfile = null;
set<entry<file, long>> entries = lastusagedates.entryset();
synchronized (lastusagedates) {
for (entry<file, long> entry : entries) {
if (mostlongusedfile == null) {
mostlongusedfile = entry.getkey();
oldestusage = entry.getvalue();
} else {
long lastvalueusage = entry.getvalue();
if (lastvalueusage < oldestusage) {
oldestusage = lastvalueusage;
mostlongusedfile = entry.getkey();
int filesize = 0;
if (mostlongusedfile != null) {
if (mostlongusedfile.exists()) {
filesize = getsize(mostlongusedfile);
if (mostlongusedfile.delete()) {
lastusagedates.remove(mostlongusedfile);
} else {
lastusagedates.remove(mostlongusedfile);
return filesize;
* 抽象方法,擷取檔案大小
* @param file
* @return
protected abstract int getsize(file file);
}
最後看一些我們需要怎麼使用
配置:
在做網絡請求的時候,我們也做些配置,如網絡請求之前預設什麼背景,請求完成後,請求失敗後怎麼顯示我們都可以在
加載圖檔: