Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android二級緩存之物理存儲介質上的緩存DiskLruCache

Android二級緩存之物理存儲介質上的緩存DiskLruCache

編輯:關於Android編程

Android二級緩存之物理存儲介質上的緩存DiskLruCache

Android DiskLruCache屬於物理性質的緩存,相較於LruCache緩存,則DiskLruCache屬於Android二級緩存中的最後一級。通常Android緩存分為兩級,第一級是內存緩存,第二級是物理緩存也即DiskLruCache。顧名思義,DiskLruCache就是將數據緩存到Android的物理介質如外部存儲器存儲卡、內部存儲器存儲卡上。
關於LruCache緩存即內存緩存,我在之前寫過一系列文章,詳情請見附錄文章2,3。本文介紹Android硬件級的緩存策略:DiskLruCache。

事實上,由於DiskLruCache實現原理和過程透明公開,有不少第三方實現,在github上有一個比較流行的DiskLruCache開源實現版本,其項目主頁:
本文將基於JakeWharton實現的DiskLruCache開源庫為例說明。使用JakeWharton實現的DiskLruCache,需要先將github上的代碼下載,下載後,直接復制到自己項目代碼java目錄下作為自己的源代碼直接使用即可。

(1)DiskLruCache的初始化。

 

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize);

 

 

DiskLruCache使用前首先需要從一個靜態方法open創建一個DiskLruCache實例。
一個緩存目錄directory,緩存目錄directory可以用Android系統提供的默認緩存目錄,也可以自己指定一個顯而易見的目錄。
DiskLruCache在open緩存目錄時候,如果前後appVersion不同則銷魂緩存。
valueCount類似於指明一個數組的長度,通常是1,是1 的話,那麼在後面寫緩存newOutputStream時候是newOutputStream(0),因為類似數組下標。長度為1的數組,那麼數組中只有一個元素且該元素的下標是0。同樣,讀的時候也是getInputStream(0)。
maxSize意義簡單,指定DiskLruCache緩存的大小,總不能讓DiskLruCache無限制緩存吧。所以一般要給DiskLruCache指定一個適當的緩存尺寸和限制,一般是10 * 1024 * 1024,10MB。

我寫的初始化例子:

 

private void makeDiskLruCache() {
        try {
            File cacheDir = getDiskCacheDir(this, UNIQUENAME);

            if (!cacheDir.exists()) {
                Log.d(TAG, "緩存目錄不存在,創建之...");
                cacheDir.mkdirs();
            } else
                Log.d(TAG, "緩存目錄已存在,不需創建.");

            //第二個參數我選取APP的版本code。DiskLruCache如果發現第二個參數version不同則銷毀緩存
            //第三個參數為1,在寫緩存的流時候,newOutputStream(0),0為索引,類似數組的下標
            mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

 

 

(2)往DiskLruCache寫緩存的一般過程。
DiskLruCache的緩存是結構。緩存寫入DiskLruCache,首先要從DiskLruCache獲得一個DiskLruCache.Editor,用DiskLruCache.Editor的editor傳遞參數key進去,再獲得一個OutputStream,拿到這個OutputStream,就可以把DiskLruCache當作一個接收數據的輸出流往裡面寫數據。寫完不要忘記commit。一個DiskLruCache寫緩存的代碼片段:

 

//把byte字節寫入緩存DiskLruCache
    private void writeToDiskLruCache(String url, byte[] buf) throws Exception {
        Log.d(TAG, url + " : 開始寫入緩存...");

        //DiskLruCache緩存需要一個key,我先把url轉換成md5字符串,
        //然後以md5字符串作為key鍵
        String key=urlToKey(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);

        OutputStream os = editor.newOutputStream(0);
        os.write(buf);
        os.flush();
        editor.commit();

        mDiskLruCache.flush();

        Log.d(TAG, url + " : 寫入緩存完成.");
    }

 

 

 

(3)從DiskLruCache讀緩存的一般過程。
直接的以之前寫緩存時候的key鍵從DiskLruCache中讀取:DiskLruCache.get(key),獲得一個快照DiskLruCache.Snapshot,如果這個DiskLruCache.Snapshot為null,則說明沒有緩存,如果有,則說明已經緩存,然後從DiskLruCache.Snapshot組建一個輸入流把緩存數據恢復出來即可。讀緩存的代碼:

 

//從DiskLruCache中讀取緩存
    private Bitmap readBitmapFromDiskLruCache(String url) {
        DiskLruCache.Snapshot snapShot = null;
        try {
            //把url轉換成一個md5字符串,然後以這個md5字符串作為key
            String key = urlToKey(url);

            snapShot = mDiskLruCache.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (snapShot != null) {
            Log.d(TAG, "發現緩存:" + url);
            InputStream is = snapShot.getInputStream(0);
            Bitmap bitmap = BitmapFactory.decodeStream(is);
            Log.d(TAG, "從緩存中讀取Bitmap.");

            return bitmap;
        } else
            return null;
    }

 

 

 

再寫一個完整的簡單例子說明。一個ImageView,ImageView需要加載一個網路圖片,該圖片是我的csdn博客頭像。例子中,代碼啟動後,在為ImageView加載網絡圖片時候,會首先檢查本地DiskLruCache中是否有緩存,如果有則直接使用緩存,如果沒有,則重新開啟一個線程下載圖片資源,圖片下載完成後,一方面要設置到ImageView中,同時要把圖片數據寫入DiskLruCache緩存中。

 

package zhangphil.app;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;

import com.jakewharton.disklrucache.DiskLruCache;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class MainActivity extends AppCompatActivity {

    private Handler handler;

    private ExecutorService pool;
    // 默認的線程池容量
    private int DEFAULT_TASK_NUMBER = 10;

    private final int WHAT = 0xe001;

    private String TAG = "zhangphil_tag";

    private String UNIQUENAME = "zhangphil_cache";

    private DiskLruCache mDiskLruCache = null;

    //緩存大小
    private int DISK_CACHE_MAX_SIZE = 10 * 1024 * 1024;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化DiskLruCache,創建DiskLruCache實例
        makeDiskLruCache();

        //創建容量為 asyncTaskNumber 的線程池。
        pool = Executors.newFixedThreadPool(DEFAULT_TASK_NUMBER);

        final ImageView image = (ImageView) findViewById(R.id.image);

        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case WHAT:
                        image.setImageBitmap((Bitmap) msg.obj);
                }
            }
        };

        //一個測試的URL連接,從這個鏈接下載一個圖片加載到ImageView中
        String image_url = "http://avatar.csdn.net/9/7/A/1_zhangphil.jpg";

        getBitmap(image_url);
    }

    private void getBitmap(String url) {
        //首先從DiskLruCache讀取緩存,緩存是否有該url的圖片緩存
        Bitmap bmp = readBitmapFromDiskLruCache(url);

        if (bmp == null) {
            //如果緩存中沒有,則創建一個線程下載
            Thread t = new DownloadThread(url);

            //把線程放到線程池中下載
            pool.execute(t);
        } else {
            //在DiskLruCache中發現緩存,直接復用
            sendResult(bmp);
        }
    }

    //從DiskLruCache中讀取緩存
    private Bitmap readBitmapFromDiskLruCache(String url) {
        DiskLruCache.Snapshot snapShot = null;
        try {
            //把url轉換成一個md5字符串,然後以這個md5字符串作為key
            String key = urlToKey(url);

            snapShot = mDiskLruCache.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (snapShot != null) {
            Log.d(TAG, "發現緩存:" + url);
            InputStream is = snapShot.getInputStream(0);
            Bitmap bitmap = BitmapFactory.decodeStream(is);
            Log.d(TAG, "從緩存中讀取Bitmap.");

            return bitmap;
        } else
            return null;
    }

    //把byte字節寫入緩存DiskLruCache
    private void writeToDiskLruCache(String url, byte[] buf) throws Exception {
        Log.d(TAG, url + " : 開始寫入緩存...");

        //DiskLruCache緩存需要一個key,我先把url轉換成md5字符串,
        //然後以md5字符串作為key鍵
        String key=urlToKey(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);

        OutputStream os = editor.newOutputStream(0);
        os.write(buf);
        os.flush();
        editor.commit();

        mDiskLruCache.flush();

        Log.d(TAG, url + " : 寫入緩存完成.");
    }

    private void makeDiskLruCache() {
        try {
            File cacheDir = getDiskCacheDir(this, UNIQUENAME);

            if (!cacheDir.exists()) {
                Log.d(TAG, "緩存目錄不存在,創建之...");
                cacheDir.mkdirs();
            } else
                Log.d(TAG, "緩存目錄已存在,不需創建.");

            //第二個參數我選取APP的版本code。DiskLruCache如果發現第二個參數version不同則銷毀緩存
            //第三個參數為1,在寫緩存的流時候,newOutputStream(0),0為索引,類似數組的下標
            mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 開辟一個下載線程
    private class DownloadThread extends Thread {

        private String url;

        public DownloadThread(String url) {
            this.url = url;
        }

        @Override
        public void run() {

            try {
                byte[] imageBytes = loadRawDataFromURL(url);

                // 數據下載完畢,把新的bitmap數據寫入DiskLruCache緩存
                writeToDiskLruCache(url, imageBytes);

                Bitmap bmp = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);

                sendResult(bmp);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 發送消息通知:bitmap已經下載完成。
    private void sendResult(Bitmap bitmap) {
        Message message = handler.obtainMessage();
        message.what = WHAT;
        message.obj = bitmap;
        handler.sendMessage(message);
    }

    //從一個url下載原始數據,本例是一個圖片資源。
    public byte[] loadRawDataFromURL(String u) throws Exception {
        Log.d(TAG, "開始下載 " + u);

        URL url = new URL(u);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        InputStream is = conn.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        final int BUFFER_SIZE = 2048;
        final int EOF = -1;

        int c;
        byte[] buf = new byte[BUFFER_SIZE];

        while (true) {
            c = bis.read(buf);
            if (c == EOF)
                break;

            baos.write(buf, 0, c);
        }

        conn.disconnect();
        is.close();

        byte[] data = baos.toByteArray();
        baos.flush();

        Log.d(TAG, "下載完成! " + u);

        return data;
    }


    /*
    *
    * 當SD卡存在或者SD卡不可被移除的時候,就調用getExternalCacheDir()方法來獲取緩存路徑,
    * 否則就調用getCacheDir()方法來獲取緩存路徑。
    * 前者獲取到的就是 /sdcard/Android/data//cache
    * 而後者獲取到的是 /data/data//cache 。
    *
    * */
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath = null;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }

        File dir = new File(cachePath + File.separator + uniqueName);
        Log.d(TAG, "緩存目錄:" + dir.getAbsolutePath());

        return dir;
    }


    //版本名
    public static String getVersionName(Context context) {
        return getPackageInfo(context).versionName;
    }

    //版本號
    public static int getVersionCode(Context context) {
        return getPackageInfo(context).versionCode;
    }

    private static PackageInfo getPackageInfo(Context context) {
        PackageInfo pi = null;

        try {
            PackageManager pm = context.getPackageManager();
            pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS);

            return pi;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return pi;
    }

    public String urlToKey(String url) {
        return getMD5(url);
    }

    /*
    * 傳入一個字符串String msg,返回Java MD5加密後的16進制的字符串結果。
    * 結果形如:c0e84e870874dd37ed0d164c7986f03a
    */
    public static String getMD5(String msg) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        md.reset();
        md.update(msg.getBytes());
        byte[] bytes = md.digest();

        String result = "";
        for (byte b : bytes) {
            // byte轉換成16進制
            result += String.format("%02x", b);
        }

        return result;
    }
}

 

 

 

涉及到Android網絡操作和存儲設備的讀寫,不要忘記加相關權限:

 


    
    
    

    

 

 

 

附錄文章:
1,《基於Java LinkedList,實現Android大數據緩存策略》鏈接地址:http://blog.csdn.net/zhangphil/article/details/44116885
2,《使用新式LruCache取代SoftReference緩存圖片,Android異步加載圖片》鏈接地址:http://blog.csdn.net/zhangphil/article/details/43667415
3,《使用Android新式LruCache緩存圖片,基於線程池異步加載圖片》鏈接地址:http://blog.csdn.net/zhangphil/article/details/44082287
4,《Java MD5(字符串)》鏈接地址:http://blog.csdn.net/zhangphil/article/details/44152077
5,《從一個URL下載原始數據,基於byte字節》鏈接地址:http://blog.csdn.net/zhangphil/article/details/43794837

6,《Android獲取App版本號和版本名》鏈接地址:http://blog.csdn.net/zhangphil/article/details/43795099

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved