Android使用磁碟快取DiskLruCache

_小馬快跑_發表於2017-12-15

DiskLruCache 不同於LruCache,LruCache是將資料快取到記憶體中去,而DiskLruCache是外部快取,例如可以將網路下載的圖片永久的快取到手機外部儲存中去,並可以將快取資料取出來使用,DiskLruCache不是google官方所寫,但是得到了官方推薦,DiskLruCache沒有編寫到SDK中去,如需使用可直接copy這個類到專案中去。

DiskLruCache地址: https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java

或者可以在Jake大神的Github上找到: https://github.com/JakeWharton/DiskLruCache

  • ######DiskLruCache常用方法:
方法 備註
DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 開啟一個快取目錄,如果沒有則首先建立它,**directory:**指定資料快取地址 **appVersion:**APP版本號,當版本號改變時,快取資料會被清除 **valueCount:**同一個key可以對應多少檔案 **maxSize:**最大可以快取的資料量
Editor edit(String key) 通過key可以獲得一個DiskLruCache.Editor,通過Editor可以得到一個輸出流,進而快取到本地儲存上
void flush() 強制緩衝檔案儲存到檔案系統
Snapshot get(String key) 通過key值來獲得一個Snapshot,如果Snapshot存在,則移動到LRU佇列的頭部來,通過Snapshot可以得到一個輸入流InputStream
long size() 快取資料的大小,單位是byte
boolean remove(String key) 根據key值來刪除對應的資料,如果該資料正在被編輯,則不能刪除
void delete() 關閉快取並且刪除目錄下所有的快取資料,即使有的資料不是由DiskLruCache 快取到本目錄的
void close() 關閉DiskLruCache,快取資料會保留在外存中
boolean isClosed() 判斷DiskLruCache是否關閉,返回true表示已關閉
File getDirectory() 快取資料的目錄
  • ######如何使用DiskLruCache:

1、因為要操作外部儲存,所以必須要先加上許可權:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
複製程式碼

另外要從網路下載圖片,還要加上許可權:

 <uses-permission android:name="android.permission.INTERNET" />
複製程式碼

2、DiskLruCache是在外部儲存上(如SD卡),所以首先判斷外部儲存是否存在:

  /**
   * Get a usable cache directory (external if available, internal otherwise).
   * external:如:/storage/emulated/0/Android/data/package_name/cache
   * internal 如:/data/data/package_name/cache
   *
   * @param context    The context to use
   * @param uniqueName A unique directory name to append to the cache dir
   * @return The cache dir
   */
  public static File getDiskCacheDir(Context context, String uniqueName) {
      final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() 
              ? context.getExternalCacheDir().getPath()
              : context.getCacheDir().getPath();
      return new File(cachePath + File.separator + uniqueName);
  }
複製程式碼

(1)、首先判斷外部快取是否被移除或已存滿,如果已存滿或者外儲存被移除,則快取目錄=context.getCacheDir().getPath(),即存到 /data/data/package_name/cache 這個檔案系統目錄下; (2)、反之快取目錄=context.getExternalCacheDir().getPath(),即存到 /storage/emulated/0/Android/data/package_name/cache 這個外部儲存目錄中,PS:外部儲存可以分為兩種:一種如上面這種路徑 (/storage/emulated/0/Android/data/package_name/cache), 當應用解除安裝後,儲存資料也會被刪除,另外一種是永久儲存,即使應用被解除安裝,儲存的資料依然存在,儲存路徑如:/storage/emulated/0/mDiskCache,可以通過Environment.getExternalStorageDirectory().getAbsolutePath() + "/mDiskCache" 來獲得路徑。

3、根據URL下載一個線上圖片並把它寫到輸出流outputstream中:

  /**
     * Download a bitmap from a URL and write the content to an output stream.
     *
     * @param urlString The URL to fetch
     * @return true if successful, false otherwise
     */
    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(), IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (Exception e) {
            Log.e(TAG, "Error in downloadBitmap - " + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
            }
        }
        return false;
    }
複製程式碼

4、上面已經下載了圖片,接著初始化DiskLruCache,並使用DiskLruCache.Editor準備快取:

  private static final int MAX_SIZE = 10 * 1024 * 1024;//10MB
    private DiskLruCache diskLruCache;
    private void initDiskLruCache() {
        if (diskLruCache == null || diskLruCache.isClosed()) {
            try {
                File cacheDir = CacheUtil.getDiskCacheDir(this, "CacheDir");
                if (!cacheDir.exists()) {
                    cacheDir.mkdirs();
                }
                //初始化DiskLruCache
                diskLruCache = DiskLruCache.open(cacheDir, 1, 1, MAX_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
複製程式碼

下載圖片時需要放在非同步執行緒裡,這裡放在了AsyncTask的doInBackground中:

@Override
protected Boolean doInBackground(Object... params) {
    try {
        String key = Util.hashKeyForDisk(Util.IMG_URL);
        DiskLruCache diskLruCache = (DiskLruCache) params[0];
         //得到DiskLruCache.Editor
        DiskLruCache.Editor editor = diskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(0);
            if (downloadUrlToStream(Util.IMG_URL, outputStream)) {
                publishProgress("");
                //寫入快取
                editor.commit();
            } else {
                 //寫入失敗
                editor.abort();
            }
        }
        diskLruCache.flush();
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
    return true;
}
複製程式碼

上面程式碼中有個hashKeyForDisk()方法,其作用是把圖片URL經過MD5加密生成唯一的key值,避免了URL中可能含有非法字元問題,hashKeyForDisk()程式碼如下:

 /**
  * A hashing method that changes a string (like a URL) into a hash suitable for using as a
  * disk filename.
  */
 public static 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 static String bytesToHexString(byte[] bytes) {
     // http://stackoverflow.com/questions/332079
     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();
 }
複製程式碼

經過上面的程式碼,我們已經可以看到圖片已經快取到 /storage/emulated/0/Android/data/package_name/cache/CacheDir 這個目錄下了:

cacheDir.png

第一個標識為110.78kb大小的就是我們快取下來的圖片,它的名字正是由圖片的URL經過MD5加密得到的,它下面的journal檔案是用來記錄的,來看裡面的內容:

journal.png

第一行:libcore.io.DiskLruCache固定寫死 第二行:DiskLruCache版本號 第三行:APP版本號,由open()方法的引數appVersion傳入 第四行:同一個key可以對應多少檔案,由open()方法的引數valueCount傳入,一般為1 第五行:空格 第六行:以DIRTY開頭,後面跟著的是圖片的key值,表示準備快取這張圖片,當呼叫DiskLruCache的edit()時就會生成這行記錄 第七行: 以CLEAN開頭,後面跟著的是圖片的Key值和大小,當呼叫editor.commit()時會生成這條記錄,表示快取成功;如果呼叫editor.abort()表示快取失敗,則會生成REMOVE開頭的表示刪除這條資料。

5、通過diskLruCache.get(key)得到DiskLruCache.Snapshot,key是經過MD5加密後那個唯一的key,接著使用Snapshot.getInputStream()可以得到輸入流InputStream ,進而得到快取圖片:

private Bitmap getCache() {
     try {
         String key = Util.hashKeyForDisk(Util.IMG_URL);
         DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
         if (snapshot != null) {
             InputStream in = snapshot.getInputStream(0);
             return BitmapFactory.decodeStream(in);
         }
     } catch (IOException e) {
         e.printStackTrace();
     }
     return null;
 }
複製程式碼
Bitmap bitmap = getCache();
     if (bitmap != null) {
         iv_img.setImageBitmap(bitmap);
     }
複製程式碼

效果圖:

cache.png

完整Demo地址: http://download.csdn.net/detail/u013700502/9883037

相關文章