Android知識總結:Universal-Imageloader學習筆記5 一種特殊情況下的圖片緩存方式

背景

在最近的項目中,由於後臺的特殊要求,每次加載圖片時,圖片url都會攜帶一個時間戳,即如下圖片格式爲這樣的形式:http://xxxx/aaaa/cccc.jpg?timestamp 其中問號前面的部分是不變的,而timestamp每次是不同的。換句話說,每次加載圖片的url是不同的。也就是說,如果不經過特殊處理,ImageLoader的緩存是沒有辦法使用的,也就是說所有圖片只要顯示必須當下下載,也就別提什麼用戶體驗了。
通過抓包瞭解到,服務端每張圖片的url其實是在url的基礎上加了一個時間戳,也就是說對於同一張圖片,每次下載時其實是在一個固定url的基礎上加時間戳構成真的url。換句話說,就是上面那個示例url其實問號前面的部分是不變的,既然有不變的地方,可否爲我們所用?
感覺上當然是可以的,實現就需要我們重新分析一下ImageLoader是怎樣加載緩存圖片的。

加載圖片過程

詳細過程分析可以參考如下鏈接http://blog.csdn.net/lidec/article/details/50133533,此處我們只看bitmap是如何被存入與取出緩存的。
由之前的博文可以知道,下載的主要業務邏輯存在於ImageLoader的displayImage方法。
圖片的緩存分爲內存緩存(memoryCache)和硬盤緩存(diskCache),如果我們能找到url與緩存索引的對應關係,我們就可以直接將url中固定不變的部分拿出來,用這部分生成索引,然後當我們查找緩存中的索引時,直接使用固定不變那部分url生成索引。總之,就是用url中不帶時間戳的部分來作爲取緩存的依據,這樣,不管後面時間戳怎麼變化,我們都能得到唯一的圖片。
通過分析源碼可知,圖片內存緩存的索引生成方式如下:
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
......
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
       通過圖片的uri與大小,經過一定的算法生成內存緩存索引,然後通過這個索引就可以在緩存中獲取bitmap
當內存中不存在緩存時,我們就需要從硬盤中獲取。在前文分析配置文件時候,我們發現ImageLoader中爲我們提供了一個FileNameGenerator接口,通過重寫這個接口可以實現我們對硬盤緩存文件的自定義命名。所以,我們可以通過實現自己的FileNameGenerator,來截取我們所需要的有效url,並使用一定的算法生成我們的命名。是不是這樣呢,我們看看源碼。
 //嘗試在文件中加載  
        File imageFile = configuration.diskCache.get(uri);  
        if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {  
            ......  
            //從文件中解碼圖片  
            bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));  
        }  
我們可以看看diskCache的get(uri)是怎麼做的。
    protected File getFile(String imageUri) {
        String fileName = this.fileNameGenerator.generate(imageUri);
        File dir = this.cacheDir;
        if(!this.cacheDir.exists() && !this.cacheDir.mkdirs() && this.reserveCacheDir != null && (this.reserveCacheDir.exists() || this.reserveCacheDir.mkdirs())) {
            dir = this.reserveCacheDir;
        }

        return new File(dir, fileName);
    }
可見,裏面是調用fileNameGenerator來生成的文件名,而解碼文件時,用到的file就是這個文件民對應的file,所以,我們只需要實現自己的fileNameGenerator,將時間戳部分截取掉,用其餘部分生成文件名,就可以實現uri的固定。

兩個實際問題

原理就是這樣,而讓人蛋疼的問題還在後面,首先,要修正以前博客配置ImageLoder文章的一個bug,經過試驗,在config中設置fileNameGenrator是無效的。如下
config.diskCacheFileNameGenerator(new MyHashCodeNameGenerator());
這麼寫就扯了dan了,其實打開緩存文件夾,裏面根本不是按照你的規則生成的文件名,而是默認的hashcode生成。通過閱讀源碼,我發現必須在設置diskCache類型的時候傳入將Generator構造函數,這樣纔能有效設置。代碼如下:
ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(context);
         ......
        config.diskCache(new BaseDiskCache(new File(FileUtils.getImageDir()), null, new MyHashCodeNameGenerator()) {
        });
        //config.diskCacheFileNameGenerator(new MyHashCodeNameGenerator());    這個沒用
        ImageLoader.getInstance().init(config.build());
第二坑是我一開始直接截取字符串後,用這個字符串的hashcode作爲文件名返回,後來發現對於相同的字符串,返回的hashcode是不一樣的。所以,是時候回頭看看java基礎了............. 然後我開始參照原文件中的MD5NameGenerator進行了實現,MD5加密只和字符有關係,只要傳入相同字符串,返回值必定相同,所以只是對問號前的部分進行MD5編碼並返回字符串。代碼如下
/**
 * Created by vonchenchen on 2016/1/6 0006.
 */
public class MyHashCodeNameGenerator implements FileNameGenerator {

    private static final String HASH_ALGORITHM = "MD5";
    private static final int RADIX = 10 + 26; // 10 digits + 26 letters

    @Override
    public String generate(String s) {

        String urlHeader = s.split("[?]")[0];
        byte[] md5 = getMD5(urlHeader.getBytes());
        BigInteger bi = new BigInteger(md5).abs();
        return bi.toString(RADIX);

        //String urlHeader = s.split("[?]")[0];    //這樣寫雖然截取後url一樣,但是生成的hashcode每次不同
        //return String.valueOf(s.hashCode());
    }

    private byte[] getMD5(byte[] data) {
        byte[] hash = null;
        try {
            MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
            digest.update(data);
            hash = digest.digest();
        } catch (NoSuchAlgorithmException e) {
            //L.e(e);
        }
        return hash;
    }
}

這樣,我們就在不改變ImageLoader源碼的情況下,爲程序增加了硬盤緩存,節省了流量,也使圖片加載更爲流暢。問題有點煩人,原理其實很簡單,實現也走了些彎路,總結經驗,穩步前進。


發佈了83 篇原創文章 · 獲贊 25 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章