天天看點

Universal-Image-Loader源碼分析,及常用的緩存政策

講到圖檔請求,主要涉及到網絡請求,記憶體緩存,硬碟緩存等原理和4大引用的問題,概括起來主要有以下幾個内容:

原理示意圖

    主體有三個,分别是ui,緩存子產品和資料源(網絡)。它們之間的關系如下:

Universal-Image-Loader源碼分析,及常用的緩存政策

① 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&lt;string, bitmap&gt;</code>

下面給出繼承關系圖

Universal-Image-Loader源碼分析,及常用的緩存政策

通過跟蹤lrumemorycache.get()的代碼我們發現,lrumemorycache聲稱保留在空間有限的情況下保留最近使用過的bitmap,這是一個linkedhashmap&lt;string,

bitmap&gt;,icache的源碼緩存的代碼如下:

@override

    public icache&lt;string, bitmap&gt; getbitmapcache() {

        if (bitmapcache == null) {

            bitmapcache = new icache&lt;string, bitmap&gt;() {

                @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&lt;string&gt; 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→最後一項)那麼就可以剔除使用最少的項了。

Universal-Image-Loader源碼分析,及常用的緩存政策
Universal-Image-Loader源碼分析,及常用的緩存政策

至此,我們就知道了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的源碼實作

Universal-Image-Loader源碼分析,及常用的緩存政策

/******************************************************************************* 

 * 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. &lt;b&gt;important:&lt;/b&gt; 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. &lt;b&gt;important:&lt;/b&gt; 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 &lt; 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

Universal-Image-Loader源碼分析,及常用的緩存政策

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&lt;file, long&gt; lastusagedates = collections.synchronizedmap(new hashmap&lt;file, long&gt;());  

     * @param cachedir  directory for file caching. &lt;b&gt;important:&lt;/b&gt; 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 &gt; 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&lt;entry&lt;file, long&gt;&gt; entries = lastusagedates.entryset();  

        synchronized (lastusagedates) {  

            for (entry&lt;file, long&gt; entry : entries) {  

                if (mostlongusedfile == null) {  

                    mostlongusedfile = entry.getkey();  

                    oldestusage = entry.getvalue();  

                } else {  

                    long lastvalueusage = entry.getvalue();  

                    if (lastvalueusage &lt; 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);  

最後看一些我們需要怎麼使用

配置:

在做網絡請求的時候,我們也做些配置,如網絡請求之前預設什麼背景,請求完成後,請求失敗後怎麼顯示我們都可以在

加載圖檔: