《Android開發藝術探索》筆記——Bitmap的加載和Cache(二)

上一篇記錄了Bitmap的(高效)加載,那麼這一篇就記錄Cache。
對於網絡上的圖片,第一次使用就需要從網絡上去下載下來,但如果每次都去從網絡上下載,那就非常浪費流量了,所以需要做緩存。另外的添加了緩存也要做好刪除緩存,畢竟有些過久地圖片或是很少會再用到的圖片,就需要刪掉了,釋放空間。

這裏用到的緩存算法是LRU(Least Recently Used),最近最少使用算法。在該算法的基礎上有衍生出兩種緩存,LruCache和DiskLruCache,前者用於實現內存緩存,後者用於實現存儲設備的緩存。所以這裏就是將這兩者結合,實現了一個ImageLoader(圖片加載器),這裏用到了三級緩存(網絡緩存,磁盤緩存和內存緩存)。

1. LruCache

引用原文的話:

LruCache是一個泛型類,它內部採用一個LinkedHashMap強引用的方式存儲外界的緩存對象。
另外LruCache是線程安全的。

短短兩句話涉及了不少概念。
LinkedHashMap如果去查找Lru算法的話,基本都是在它的基礎上實現的;
從構造方法裏可以看出它是個泛型類:
這裏寫圖片描述

然後關於強引用:
這裏寫圖片描述
這裏寫圖片描述

之所以說線程安全,因爲在LruCache裏的添加,刪除,獲取都是有同步鎖機制的。
這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

1.1 LruCache的使用

 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);//單位KB
        //設定緩存的容量爲總容量的1/8
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //返回bitmap大小的計算
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };

總的來說就是提供緩存的總容量大小然後重寫sizeof方法。這裏緩存的總容量大小爲當前進程的可用內存的1/8,sizeof裏就返回的是bitmap對象的大小計算。這兩個的單位應該一致,所以這裏都除以1024。
然後是獲取的方法:

mMemoryCache.get(key);

添加的方法:

mMemoryCache.put(key, bitmap);

2. DiskLruCache

2.1 DiskLruCache的使用

2.1.1 引用

用DiskLruCache來做磁盤緩存,可以通過依賴來獲取:

 compile 'com.jakewharton:disklrucache:2.0.2'

2.1.2 創建

DiskLruCache需要通過open方法來創建,而不是普通的構造方法:

//利用open方法來創建,第一個參數是存儲路徑,第二個參數是版本號,
//第三個參數是單個節點對應的數據的個數,第四個參數是緩存的總大小
   mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);

2.1.3 添加

與前面的LruCache一樣,DiskLruCache也是用到了LinkedHashMap,那麼在LruCache裏的操作都用到了“key”這個東西,這裏也同樣用到了key。由於在這個ImageLoader裏他們都是操作同一個東西,所以當然是一樣的。作者在這裏是用圖片的url來作key,但需要作一些轉換,用url的md5值來作爲key,主要是防止url裏可能有些特殊字符導致出錯:

    /**
     * 將圖片的url轉換成key,這裏採用url的md5的值作爲key
     * @param url
     * @return
     */
    private String hashKeyFromUrl(String url) {
        String cacheKey;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(url.getBytes());
            cacheKey = bytesToHexString(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {

            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

private String bytesToHexString(byte[] bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                stringBuilder.append('0');
            }
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }

那麼DiskLruCache的緩存添加是通過Editor來完成的,通過edit()方法和key就可以獲取到這個Editor對象,進而可以獲得文件輸出流。

String key = hashKeyFromUrl(url);
        //使用Editor進行緩存添加的操作
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
        }

那麼怎麼操作這個文件輸出流呢?或者說它的數據從哪來呢?
其實它的數據是從它的更上一級,網絡緩存那裏來的,我們通過url去做網絡請求的時候會獲得一個輸入流,然後我們把輸入流寫到這個輸出流裏,那麼它就有數據了。

private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

現在我們通過網絡請求將流寫給了磁盤緩存,但需要通過進一步的確認操作來真正的寫入。即commit()方法,所以把前面的一塊代碼修改下:

 if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }

2.1.4 獲取

DiskLruCache對緩存的獲取則是通過它的Snapshot對象,與上面的類似,它是通過get()方法和key得到的,然後可以進一步的得到文件輸入流,那拿到了文件輸入流我們通過上一篇的BitmapFactory提供的解碼方法就可以得到一個Bitmap對象了。

 String key = hashKeyFromUrl(url);
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
        if (snapshot != null) {
            //獲取該圖片的文件輸入流
            FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
            //獲取該文件輸入流的文件描述
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            //通過文件描述得到想要的bitmap
            bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);
        }

這裏對文件輸入流的處理是用了FileDescriptor, 也就是對應於decodeFileDescriptor()方法。爲什麼這裏要用這個方法?
作者給的解釋是FileInputStream是一種有序的文件流,兩次decodeStream調用影響文件流的位置屬性,在第二次decodeStream的時候會得到null,那這裏我做了測試,確實在第二次的時候會得到null。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章