Android中的緩存

為什么會用到緩存呢?主要是流量耗不起啊,國內的公共場所的WiFi的普及率不高,因此必須考慮流量的問題,說白了,就是用戶體驗啊,每次都網絡請求,消耗資源不說,網速不好的情況下還會有網絡延時,用戶體驗不好。

Android中的緩存,從方式上來說,一般有網絡緩存,磁盤緩存即SD卡緩存,內存緩存。網絡緩存需要服務端的配合,用于加快網絡請求的響應速度。磁盤緩存一般用DiskLruCache,當然也可以用SqlLite數據庫,以及sharedpreference等作持久化處理。這里主要說下兩種常用的緩存方法,LruCache、DiskLruCache。前者用于內存緩存,后者用于設備緩存,一般兩者結合起來效果更好。

其實緩存的實現并不難,每一中緩存都會有三個基本操作,添加、獲取、刪除。了解這些了,就會有思路了。

再說LruCache、DiskLruCache,可以看到,兩者都有Lru,那么Lru是什么呢?這是目前常用的一種緩存算法:近期最少使用算法,核心思想很簡單,就是當緩存滿時,會優先刪除那些近期最少使用的緩存。那么現在分別了解下這兩種緩存吧。

LruCache

LruCache內部用到的是LinkedHashMap,LinkedHashMap與HashMap的不同住處在于LinkedHashMap 維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。也就說它的插入和訪問是有順序的。另外LruCache是線程安全的。至于使用的話就很簡單了。

// 初始化
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    int cacheSize = maxMemory / 8;
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
            return value.getRowBytes() * value.getHeight() / 1024;
        }
    };

總緩存大小一般會設置為當前進程可用內存的1/8,當然這個數是可以自己設置的,這個數是推薦的。sizeOf方法是為了計算緩存對象的大小。如果有必要也可以重寫entryRemoved來完成某些資源回收工作。

再看緩存的添加與刪除,

//添加緩存
    mMemoryCache.put(key,bitmap);
    //獲取緩存
    mMemoryCache.get(key);
    //刪除緩存
    mMemoryCache.remove(key);

DiskLruCache

DiskLruCache用與磁盤緩存,被官方推薦使用。下面來看看它的使用。

自從用了Gradle后,引入項目方便多了,誰用誰知道。

compile 'com.jakewharton:disklrucache:2.0.2'

創建DiskLruCache:

DiskLruCache mDiskLruCache = null;  
try {  
    File cacheDir = getDiskCacheDir(context, "bitmap");  
    if (!cacheDir.exists()) {  
        cacheDir.mkdirs();  
    }  
    mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);  
} catch (IOException e) {  
    e.printStackTrace();  
}

解釋下DiskLruCache.open的參數,第一個表示存儲的路徑,第二個表示應用的版本號,注意這里當版本號發生改變時會清空之前所有的緩存文件,而在實際開發中這個性質用的不多,所以直接寫1。第三個表示單個節點對應的數據的個數,設置為1就可以了,第四個表示緩存的總大小,當超出這個值時,會清除一些緩存保證總大小不大于這個設定的值。

添加緩存:

第一步,網絡下載圖片(文件也是一樣的步驟的)并通過outputStream寫入到本地

private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {  
HttpURLConnection urlConnection = null;  
BufferedOutputStream out = null;  
BufferedInputStream in = null;  
try {  
    final URL url = new URL(urlString);  
    urlConnection = (HttpURLConnection) url.openConnection();  
    in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);  
    out = new BufferedOutputStream(outputStream, 8 * 1024);  
    int b;  
    while ((b = in.read()) != -1) {  
        out.write(b);  
    }  
    return true;  
} catch (final IOException e) {  
    e.printStackTrace();  
} finally {  
    if (urlConnection != null) {  
        urlConnection.disconnect();  
    }  
    try {  
        if (out != null) {  
            out.close();  
        }  
        if (in != null) {  
            in.close();  
        }  
    } catch (final IOException e) {  
        e.printStackTrace();  
    }  
}  
return false;  
}

第二步,處理緩存的key,直接用url作為key值時最有快捷的方式,但是url里會有特殊字符,不符合Android的命名規范,最好的辦法就是把url進行MD5摘要。

public String hashKeyForDisk(String key) {  
    String cacheKey;  
    try {  
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");  
        mDigest.update(key.getBytes());  
        cacheKey = bytesToHexString(mDigest.digest());  
    } catch (NoSuchAlgorithmException e) {  
        cacheKey = String.valueOf(key.hashCode());  
    }  
    return cacheKey;  
}  

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

第三步 創建DiskLruCache.Editor的實例,寫入數據

String key = hashKeyForDisk(imageUrl);  
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);  
            if (editor != null) {  
                OutputStream outputStream = editor.newOutputStream(0);  
                if (downloadUrlToStream(imageUrl, outputStream)) {  
                    editor.commit();  
                } else {  
                    editor.abort();  
                }  
            }  
            mDiskLruCache.flush();

editor.commit()方法用來提交寫入操作,editor.abort()回退整個操作。

讀取緩存:

Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(0);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

需要說明下的是為了避免加載圖片時導致OOM,不建議直接加在Bitmap,通常我們會通過BitmapFactory.Options來加載一張縮放的圖片,但是這中方法對于FileInputStream有問題,因為FileInputStream是有序的文件流,而兩次的從的 decodeStream調用影響了文件流的位置屬性,導致第二次decodeStream時得到的為null。為了解決這個問題,可以先得到對應的文件描述符,然后通過BitmapFactory.decodeFileDescriptor()來加載圖片。

移除緩存:

mDiskLruCache.remove(key);

 

來自:http://www.jianshu.com/p/96a7865fdab4

 

 本文由用戶 ChaBedard 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!