【Android 內存優化】Bitmap 硬盤緩存 ( Google 官方 Bitmap 示例 | DiskLruCache 開源庫 | 代碼示例 )



在上一篇博客 【Android 內存優化】Bitmap 內存緩存 ( Bitmap 內存複用 | 弱引用 | 引用隊列 | 針對不同 Android 版本開發不同的 Bitmap 複用策略 | 工具類代碼 ) 中 , 使用 LruCache 緩存內存數據 , 同時兼顧 Bitmap 內存複用 , 使用弱引用 Bitmap 對象集合維護 Bitmap 複用池 , 確保該複用池中的 Bitmap 對象壽命都很短 , 每次 GC 都會清理一遍複用池 ; 當 LruCache 中的數據由於最近不常使用 , 從 LruCache 內存中移除 , 此時將其放入 Bitmap 複用池中 , 將該 Bitmap 對象納入複用機制管理 ;





一、Google 官方 Bitmap 相關示例參考



Google 官方相關示例參考 :


① Android 官方示例 : https://github.com/android

② Android 圖形示例 : android/graphics-samples

③ Bitmap 展示 Module : DisplayingBitmaps

④ 官方示例中的硬盤緩存類 : DiskLruCache.java


在 DisplayingBitmaps 示例中 , Google 官方展示了在應用中如何使用 Bitmap ;





二、磁盤緩存類 DiskLruCache



推薦使用 JakeWharton 的開源庫 DiskLruCache , 點擊上述連接即可進入工程界面 ;


點擊 GitHub 界面右側的 Release 界面 , 下載發佈的 Release 版本 , 儘量不要直接下載開發中的最新版本 ;

在這裏插入圖片描述

這裏選擇下載最新的 Release 版本 , disklrucache-2.0.2 版本 , 點擊此處直接調轉到該界面 ;

在這裏插入圖片描述

下載完畢後 , 打開壓縮包 , 直接將這三個文件 , 拷貝到項目中 ;

在這裏插入圖片描述





三、磁盤緩存初始化



首先聲明 DiskLruCache 磁盤 LRU 緩存類 ,然後初始化 DiskLruCache 類 , 主要是設置磁盤緩存目錄 , 應用版本號 , 每個 Key 可以對應多個文件個數 , 磁盤大小

    /**
     * 磁盤緩存
     */
    private DiskLruCache mDiskLruCache;

    /**
     * 初始化磁盤緩存
     * @param diskDirectory
     */
    private void initDiskLruCache(String diskDirectory){
        try {
            /*
                初始化內存緩存
                需要傳入內存緩存目錄文件
                APP 版本
                緩存值的個數
                緩存大小 , 單位字節 , 這個最重要
             */
            mDiskLruCache = DiskLruCache.open(
                    new File(diskDirectory),
                    BuildConfig.VERSION_CODE,
                    1,
                    8 * 1024 * 10024
            );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }




四、存儲數據到磁盤緩存中



1 . 從 DiskLruCache 中獲取 Bitmap 流程 :


① 獲取快照 : 通過 key 獲取 DiskLruCache.Snapshot 對象 ;

snapshot = mDiskLruCache.get(key);

② 打開編輯器 : 打開 DiskLruCache.Editor , 該用法與 SharedPreference 用法類似 ;

DiskLruCache.Editor editor = mDiskLruCache.edit(key);

③ 獲取輸出流 : 從 DiskLruCache.Editor 對象中獲取出輸出流 , 這裏的 0 表示獲取該 key 對應的第 0 個文件 , 每個 Key 可以對應多個文件 , 這個值是創建 DiskLruCache 時傳入的 valueCount 參數 ;

outputStream = editor.newOutputStream(0);

④ 寫出 Bitmap 對象到文件中 :

bitmap.compress(Bitmap.CompressFormat.JPEG, 0, outputStream);

⑤ 編輯器提交數據 :

editor.commit();


2 . 代碼示例 :

    /**
     * 將 Bitmap 放入 磁盤緩存 中
     * @param key
     * @param bitmap
     */
    public void putBitmapToDisk(String key, Bitmap bitmap){
        DiskLruCache.Snapshot snapshot = null;
        OutputStream outputStream = null;
        try {
            snapshot = mDiskLruCache.get(key);
            // 如果緩存中有對應 key 鍵值的文件 , 不進行任何處理
            if(snapshot != null) {
                // 該用法與 SharedPreference 用法類似
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                if(editor != null){
                    // 這裏的 0 表示獲取該 key 對應的第 0 個文件
                    // 每個 可以 可以對應多個文件 , 這個值是創建 DiskLruCache 時傳入的 valueCount 參數
                    outputStream = editor.newOutputStream(0);
                    // 寫出 Bitmap 對象到文件中
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 0, outputStream);

                    // 該用法與 SharedPreference 用法類似
                    editor.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(snapshot != null) {
                snapshot.close();
            }
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }




五、從磁盤緩存中讀取數據



1 . 從 DiskLruCache 中獲取 Bitmap 流程 :


① 獲取快照 : 通過 key 獲取 DiskLruCache.Snapshot 對象 ;

snapshot = mDiskLruCache.get(key);

② 打開編輯器 : 打開 DiskLruCache.Editor , 該用法與 SharedPreference 用法類似 ;

DiskLruCache.Editor editor = mDiskLruCache.edit(key);

③ 獲取輸入流 : 從 DiskLruCache.Editor 對象中獲取出輸入流 , 這裏的 0 表示獲取該 key 對應的第 0 個文件 , 每個 Key 可以對應多個文件 , 這個值是創建 DiskLruCache 時傳入的 valueCount 參數 ;

inputStream = editor.newInputStream(0);

④ 從輸入流中讀取數據到 Bitmap 中 : 這裏用到了 Bitmap 內存複用機制 , 另外從磁盤讀取數據後 , 向內存緩存一份 ;

                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inMutable = true;
                    options.inBitmap = inBitmap;

                    // 寫出 Bitmap 對象到文件中
                    bitmap = BitmapFactory.decodeStream(inputStream, null, options);
                    
                    if(bitmap != null){
                        // 從磁盤讀取後 , 先緩存到內存中
                        mLruCache.put(key, bitmap);
                    }

⑤ 編輯器提交數據 :

editor.commit();


2 . 代碼示例 :

    /**
     * 從 磁盤緩存 中取出 Bitmap 對象
     * @param key       鍵值
     * @param inBitmap 複用 Bitmap 內存
     * @return
     */
    public Bitmap getBitmapFromDisk(String key, Bitmap inBitmap){
        Bitmap bitmap = null;
        DiskLruCache.Snapshot snapshot = null;
        InputStream inputStream = null;
        try {
            snapshot = mDiskLruCache.get(key);
            // 如果緩存中有對應 key 鍵值的文件 , 不進行任何處理
            if(snapshot != null) {
                // 該用法與 SharedPreference 用法類似
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                if(editor != null){
                    // 這裏的 0 表示獲取該 key 對應的第 0 個文件
                    // 每個 可以 可以對應多個文件 , 這個值是創建 DiskLruCache 時傳入的 valueCount 參數
                    inputStream = editor.newInputStream(0);

                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inMutable = true;
                    options.inBitmap = inBitmap;

                    // 寫出 Bitmap 對象到文件中
                    bitmap = BitmapFactory.decodeStream(inputStream, null, options);

                    if(bitmap != null){
                        // 從磁盤讀取後 , 先緩存到內存中
                        mLruCache.put(key, bitmap);
                    }

                    // 該用法與 SharedPreference 用法類似
                    editor.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(snapshot != null) {
                snapshot.close();
            }
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }




六、 Android 10 文件訪問



文件存儲相關官方參考資料 :


參考之前的博客 【Android 內存優化】Android 原生 API 圖片壓縮代碼示例 ( PNG 格式壓縮 | JPEG 格式壓縮 | WEBP 格式壓縮 | 動態權限申請 | Android10 存儲策略 ) 三、 Android 10 文件訪問 有涉及到在 Android 10 系統中訪問 SD 卡 ;





七、代碼示例





1、二級緩存代碼示例



磁盤內存二級緩存代碼示例 :

package kim.hsl.bm.utils;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.LruCache;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import kim.hsl.bm.BuildConfig;
import kim.hsl.bm.diskcache.DiskLruCache;

/**
 * Bitmap 內存緩存
 * 在將圖片緩存到 LruCache 內存中基礎上 ,
 * 將從 LruCache 中移除的最近沒有使用的 Bitmap 對象的內存複用
 * 這樣能最大限度減少內存抖動
 */
public class BitmapDiskLruCacheMemoryReuse {
    private static final String TAG = "BitmapMemoryCache";

    /**
     * 應用上下文對象
     */
    private Context mContext;

    /**
     * 緩存圖片的 LruCache
     */
    private LruCache<String, Bitmap> mLruCache;

    /**
     * 磁盤緩存
     */
    private DiskLruCache mDiskLruCache;

    /**
     * Bitmap 複用池
     * 使用 inBitmap 複用選項
     * 需要獲取圖片時 , 優先從 Bitmap 複用池中查找
     * 這裏使用弱引用保存該 Bitmap , 每次 GC 時都會回收該 Bitmap
     * 創建一個線程安全的 HashSet , 其中的元素是 Bitmap 弱引用
     *
     * 該 Bitmap 複用池的作用是 , 假如 Bitmap 對象長時間不使用 , 就會從內存緩存中移除
     *
     * Bitmap 回收策略 :
     * 3.0 以下系統中 , Bitmap 內存在 Native 層
     * 3.0 以上系統中 , Bitmap 內存在 Java 層
     * 8.0 及以上的系統中 , Bitmap 內存在 Native 層
     *
     * 因此這裏需要處理 Bitmap 內存在 Native 層的情況 , 監控到 Java 層的弱引用被釋放了
     * 需要調用 Bitmap 對象的 recycle 方法 , 釋放 Native 層的內存
     *
     * 需要使用引用隊列監控弱引用的釋放情況
     */
    Set<WeakReference<Bitmap>> bitmapReusePool;

    /**
     * 引用隊列 , 用於監控 Set<WeakReference<Bitmap>> bitmapReusePool 的內存是否被回收
     * 需要維護一個線程 , 不斷嘗試從該引用隊列中獲取引用
     *
     */
    private ReferenceQueue<Bitmap> referenceQueue;

    /**
     * 監控 Set<WeakReference<Bitmap>> bitmapReusePool 的內存是否被回收 ,
     * 調用 ReferenceQueue<Bitmap> referenceQueue 的 remove 方法 ,
     * 查看是否存在被回收的弱引用 , 如果存在 , 直接回收該弱引用對應的 Bitmap 對象
     */
    private Thread referenceQueueMonitorThread;

    /**
     * 是否持續監控引用隊列 ReferenceQueue
     */
    private boolean isMonitorReferenceQueue = true;



    /**
     * 單例實現
     */
    private static BitmapDiskLruCacheMemoryReuse INSTANCE;
    private BitmapDiskLruCacheMemoryReuse(){}
    public static BitmapDiskLruCacheMemoryReuse getInstance(){
        if(INSTANCE == null){
            INSTANCE = new BitmapDiskLruCacheMemoryReuse();
        }
        return INSTANCE;
    }

    /**
     * 使用時初始化
     * @param context
     */
    public void init(Context context, String diskDirectory){
        // 初始化內存緩存
        initLruCache(context);

        // 初始化弱引用隊列
        initBitmapReusePool();

        // 初始化磁盤緩存
        initDiskLruCache(diskDirectory);
    }

    /**
     * 不使用時釋放
     */
    public void release(){
        isMonitorReferenceQueue = false;
    }

    private void initLruCache(Context context){
        // 爲成員變量賦值
        this.mContext = context;
        // 獲取 Activity 管理器
        ActivityManager activityManager = (ActivityManager) context.getSystemService(
                Context.ACTIVITY_SERVICE);
        // 獲取應用可用的最大內存
        int maxMemory = activityManager.getMemoryClass();
        // 獲取的 maxMemory 單位是 MB , 將其轉爲字節 , 除以 8
        int lruCacheMemoryByte = maxMemory / 8 * 1024 * 1024;

        // 設置的內存 , 一般是 APP 可用內存的 1/8
        mLruCache = new LruCache<String, Bitmap>(lruCacheMemoryByte){
            /**
             * 返回 LruCache 的鍵和值的大小 , 單位使用用戶自定義的單位
             * 默認的實現中 , 返回 1 ; size 是 鍵值對個數 , 最大的 size 大小是最多鍵值對個數
             * 鍵值對條目在 LruCache 中緩存時 , 其大小不能改變
             * @param key
             * @param value
             * @return 返回 LruCache<String, Bitmap> 的值 , 即 Bitmap 佔用內存
             */
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // 如果使用的是複用的 Bitmap 對象 , 其佔用內存大小是之前的圖像分配的內存大小
                // 大於等於當前圖像的內存佔用大小
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    return value.getAllocationByteCount();
                }
                return value.getByteCount();
            }

            /**
             * 從 LruCache 緩存移除 Bitmap 時會回調該方法
             * @param evicted
             * @param key
             * @param oldValue
             * @param newValue
             */
            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue,
                                        Bitmap newValue) {
                super.entryRemoved(evicted, key, oldValue, newValue);

                /*
                    如果從 LruCache 內存緩存中移除的 Bitmap 是可變的
                    才能被複用 , 否則只能回收該 Bitmap 對象

                    Bitmap 回收策略 :
                    3.0 以下系統中 , Bitmap 內存在 Native 層
                    3.0 以上系統中 , Bitmap 內存在 Java 層
                    8.0 及以上的系統中 , Bitmap 內存在 Native 層

                    因此這裏需要處理 Bitmap 內存在 Native 層的情況 , 監控到 Java 層的弱引用被釋放了
                    需要調用 Bitmap 對象的 recycle 方法 , 釋放 Native 層的內存
                 */
                if(oldValue.isMutable()){   // 可以被複用
                    // 將其放入弱引用中 , 每次 GC 啓動後 , 如果該弱引用沒有被使用 , 都會被回收
                    bitmapReusePool.add(new WeakReference<Bitmap>(oldValue, referenceQueue));
                }else{  // 不可被複用 , 直接回收
                    oldValue.recycle();
                }
            }
        };
    }

    /**
     * 初始化引用隊列
     */
    private void initBitmapReusePool(){
        // 創建一個線程安全的 HashSet , 其中的元素是 Bitmap 弱引用
        bitmapReusePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
        // 引用隊列 , 當弱引用被 GC 掃描後 , 需要回收 , 會將該弱引用放入隊列
        // 一直不斷的嘗試從該引用隊列中獲取數據 , 如果獲取到數據 , 就要回收該對象
        referenceQueue = new ReferenceQueue<>();

        // 定義監控線程
        referenceQueueMonitorThread = new Thread(){
            @Override
            public void run() {
                while (isMonitorReferenceQueue){
                    try {
                        Reference<Bitmap> reference = (Reference<Bitmap>) referenceQueue.remove();
                        Bitmap bitmap = reference.get();
                        // 不爲空 , 且沒有被回收 , 回收 Bitmap 內存
                        if(bitmap != null && !bitmap.isRecycled()){
                            bitmap.recycle();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        // 啓動引用隊列監控線程
        referenceQueueMonitorThread.start();
    }


    /**
     * 初始化磁盤緩存
     * @param diskDirectory
     */
    private void initDiskLruCache(String diskDirectory){
        try {
            /*
                初始化內存緩存
                需要傳入內存緩存目錄文件
                APP 版本
                緩存值的個數
                緩存大小 , 單位字節 , 這個最重要
             */
            mDiskLruCache = DiskLruCache.open(
                    new File(diskDirectory),
                    BuildConfig.VERSION_CODE,
                    1,
                    8 * 1024 * 10024
            );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取一個可以被複用的 Bitmap 對象
     *
     * 與 BitmapFactory 配合使用 :
     *
     * Android 4.4 以後的 Bitmap 複用情況 :
     * 在 KITKAT ( Android 4.4 , 19 平臺 ) 以後的代碼中 ,
     * 只要被解碼生成的 Bitmap 對象的字節大小 ( 縮放後的 )
     * 小於等於 inBitmap 的字節大小 , 就可以複用成功 ;
     *
     * Android 4.4 之前的 Bitmap 複用情況 : ( 比較苛刻 )
     * 在 KITKAT 之前的代碼中 , 被解碼的圖像必須是
     *  - JPEG 或 PNG 格式 ,
     *  - 並且 圖像大小必須是相等的 ,
     *  - inssampleSize 設置爲 1 ,
     * 才能複用成功 ;
     * 另外被複用的圖像的 像素格式 Config ( 如 RGB_565 ) 會覆蓋設置的 inPreferredConfig 參數
     *
     * @param width
     * @param height
     * @param inSampleSize
     * @return
     */
    public Bitmap getReuseBitmap(int width,int height,int inSampleSize){
        // Android 2.3.3(API 級別 10)及以下的版本中 , 使用 Bitmap 對象的 recycle 方法回收內存
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1){
            // 如果 API 級別小於等於 10 , 不啓用 Bitmap 內存複用機制 , 返回 null 即可
            return null;
        }
        // 獲取準備複用的 Bitmap , 之後設置到 Options 中
        Bitmap inBitmap = null;
        // 使用迭代器遍歷該 Set 集合 , 如果遍歷中涉及到刪除 , 就要使用迭代器遍歷
        Iterator<WeakReference<Bitmap>> iterator = bitmapReusePool.iterator();
        //迭代查找符合複用條件的Bitmap
        while (iterator.hasNext()){
            // 循環遍歷 Bitmap 對象
            Bitmap bitmap = iterator.next().get();
            if (bitmap != null){
                /*
                    檢查該 Bitmap 對象是否可以達到複用要求 ,
                    如果達到複用要求 , 就取出這個 Bitmap 對象 , 並將其從隊列中移除
                 */

                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2){
                    /*
                        Android 4.4(API 級別 19)以下的版本 : 在 Android 4.4(API 級別 19) 之前的代碼中 ,
                        複用的前提是必須同時滿足以下 3 個條件 :
                            1. 被解碼的圖像必須是 JPEG 或 PNG 格式
                            2. 被複用的圖像寬高必須等於 解碼後的圖像寬高
                            3. 解碼圖像的 BitmapFactory.Options.inSampleSize 設置爲 1 , 也就是不能縮放
                        才能複用成功 , 另外被複用的圖像的像素格式 Config ( 如 RGB_565 ) 會覆蓋設置的
                        BitmapFactory.Options.inPreferredConfig 參數 ;
                     */
                    if(bitmap.getWidth() == width &&
                            bitmap.getHeight() == height && //被複用的圖像寬高必須等於 解碼後的圖像寬高
                            inSampleSize == 1){// 圖像的 BitmapFactory.Options.inSampleSize 設置爲 1
                        //符合要求
                        inBitmap = bitmap;
                        iterator.remove();
                    }
                }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
                    /*
                        在 Android 4.4(API 級別 19)及以上的版本中 ,
                        只要被解碼後的 Bitmap 對象的字節大小 , 小於等於 inBitmap 的字節大小 , 就可以複用成功 ;
                        解碼後的乳香可以是縮小後的 , 即 BitmapFactory.Options.inSampleSize 可以大於1 ;
                     */

                    // 首先要計算圖像的內存佔用 , 先要計算出圖像的寬高 , 如果圖像需要縮放 , 計算縮放後的寬高
                    if(inSampleSize > 1){
                        width = width / inSampleSize ;
                        height = height / inSampleSize;
                    }

                    // 計算內存佔用 , 默認 ARGB_8888 格式
                    int byteInMemory = width * height * 4;;
                    if(bitmap.getConfig() == Bitmap.Config.ARGB_8888){
                        // 此時每個像素佔 4 字節
                        byteInMemory = width * height * 4;

                    }else if(bitmap.getConfig() == Bitmap.Config.RGB_565){
                        // 此時每個像素佔 2 字節
                        byteInMemory = width * height * 2;
                    }

                    // 如果解碼後的圖片內存小於等於被複用的內存大小 , 可以複用
                    if(byteInMemory <= bitmap.getAllocationByteCount()){
                        //符合要求
                        inBitmap = bitmap;
                        iterator.remove();
                    }

                }

            }else if( bitmap == null ){
                // 如果 bitmap 爲空 , 直接從複用 Bitmap 集合中移除
                iterator.remove();
            }
        }
        return inBitmap;
    }


    /*
        下面的 3 個方法是提供給用戶用於操作 LruCache 的接口
     */

    /**
     * 將鍵值對放入 LruCache 中
     * @param key
     * @param value
     */
    public void putBitmapToLruCache(String key, Bitmap value){
        mLruCache.put(key, value);
    }

    /**
     * 從 LruCache 中獲取 Bitmap 對象
     * @param key
     * @return
     */
    public Bitmap getBitmapFromLruCache(String key){
        return mLruCache.get(key);
    }

    /**
     * 清除 LruCache 緩存
     */
    public void clearLruCache(){
        mLruCache.evictAll();
    }


    /*
        下面的 2 個方法是提供給用戶用於操作 磁盤 的接口
     */

    /**
     * 將 Bitmap 放入 磁盤緩存 中
     * @param key
     * @param bitmap
     */
    public void putBitmapToDisk(String key, Bitmap bitmap){
        DiskLruCache.Snapshot snapshot = null;
        OutputStream outputStream = null;
        try {
            snapshot = mDiskLruCache.get(key);
            // 如果緩存中有對應 key 鍵值的文件 , 不進行任何處理
            if(snapshot != null) {
                // 該用法與 SharedPreference 用法類似
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                if(editor != null){
                    // 這裏的 0 表示獲取該 key 對應的第 0 個文件
                    // 每個 可以 可以對應多個文件 , 這個值是創建 DiskLruCache 時傳入的 valueCount 參數
                    outputStream = editor.newOutputStream(0);
                    // 寫出 Bitmap 對象到文件中
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 0, outputStream);

                    // 該用法與 SharedPreference 用法類似
                    editor.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(snapshot != null) {
                snapshot.close();
            }
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 從 磁盤緩存 中取出 Bitmap 對象
     * @param key       鍵值
     * @param inBitmap 複用 Bitmap 內存
     * @return
     */
    public Bitmap getBitmapFromDisk(String key, Bitmap inBitmap){
        Bitmap bitmap = null;
        DiskLruCache.Snapshot snapshot = null;
        InputStream inputStream = null;
        try {
            snapshot = mDiskLruCache.get(key);
            // 如果緩存中有對應 key 鍵值的文件 , 不進行任何處理
            if(snapshot != null) {
                // 該用法與 SharedPreference 用法類似
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                if(editor != null){
                    // 這裏的 0 表示獲取該 key 對應的第 0 個文件
                    // 每個 可以 可以對應多個文件 , 這個值是創建 DiskLruCache 時傳入的 valueCount 參數
                    inputStream = editor.newInputStream(0);

                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inMutable = true;
                    options.inBitmap = inBitmap;

                    // 寫出 Bitmap 對象到文件中
                    bitmap = BitmapFactory.decodeStream(inputStream, null, options);

                    if(bitmap != null){
                        // 從磁盤讀取後 , 先緩存到內存中
                        mLruCache.put(key, bitmap);
                    }

                    // 該用法與 SharedPreference 用法類似
                    editor.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(snapshot != null) {
                snapshot.close();
            }
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }



}



2、調用工具類代碼示例



   /**
     * 圖像磁盤內存緩存
     */
    private void diskMemoryCache(){
        // 初始化 LruCache 內存緩存 , 與引用隊列 , 一般在 onCreate 方法中初始化
        // 這裏爲了演示 , 放在方法的開頭位置
        BitmapDiskLruCacheMemoryReuse.getInstance().init(this, Environment.getExternalStorageDirectory() + "/diskCache");

        // 1. 第一次嘗試從 LruCache 內存中獲取 Bitmap 數據
        Bitmap bitmap = BitmapDiskLruCacheMemoryReuse.getInstance().
                getBitmapFromLruCache(R.drawable.blog + "");

        /*
            如果從內存中獲取 Bitmap 對象失敗 , 再次從磁盤中嘗試獲取該 Bitmap
         */
        if(bitmap == null){
            // 要複用內存的 Bitmap 對象 , 將新的 Bitmap 寫入到該 Bitmap 內存中
            Bitmap inBitmap = null;
            // 嘗試獲取複用對象
            BitmapDiskLruCacheMemoryReuse.getInstance().
                    getReuseBitmap(200, 200, 1);

            // 2. 第二次嘗試從磁盤中獲取圖片
            bitmap = BitmapDiskLruCacheMemoryReuse.getInstance().getBitmapFromDisk(
                    R.drawable.blog + "", inBitmap);


            // 磁盤中沒有找到 , 再次嘗試加載該圖片
            if(bitmap == null) {
                // 3. 如果內存, 磁盤都沒有獲取到 Bitmap, 那麼加載指定大小格式的圖像
                bitmap = BitmapSizeReduce.getResizedBitmap(this, R.drawable.blog,
                        200, 200, false, inBitmap);

                // 將新的 bitap 放入 LruCache 內存緩存中
                BitmapDiskLruCacheMemoryReuse.getInstance().
                        putBitmapToLruCache(R.drawable.blog + "", bitmap);
            }

        }
    }




八、源碼及資源下載



源碼及資源下載地址 :

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