Android二級緩衝之實體儲存體介質上的緩衝DiskLruCache

來源:互聯網
上載者:User

標籤:



Android二級緩衝之實體儲存體介質上的緩衝DiskLruCache

Android DiskLruCache屬於物理性質的緩衝,相較於LruCache緩衝,則DiskLruCache屬於Android二級緩衝中的最後一級。通常Android緩衝分為兩級,第一級是記憶體緩衝,第二級是物理緩衝也即DiskLruCache。顧名思義,DiskLruCache就是將資料緩衝到Android的物理介質如外部儲存空間儲存卡、內部儲存空間儲存卡上。
關於LruCache緩衝即記憶體緩衝,我在之前寫過一系列文章,詳情請見附錄文章2,3。本文介紹Android硬體級的緩衝策略:DiskLruCache。
DiskLruCache的AndroidGoogle官方實現代碼連結:
DiskLruCache.java AndroidGoogle官方原始碼實現連結:https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java
事實上,由於DiskLruCache實現原理和過程透明公開,有不少第三方實現,在github上有一個比較流行的DiskLruCache開源實現版本,其項目首頁:https://github.com/JakeWharton/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的緩衝是<K,V>結構。緩衝寫入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/<application package>/cache    * 而後者擷取到的是 /data/data/<application package>/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網路操作和存放裝置的讀寫,不要忘記加相關許可權:

<!-- SDCard中建立與刪除檔案許可權 -->    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>    <!-- 向SDCard寫入資料許可權 -->    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>    <uses-permission android:name="android.permission.INTERNET"></uses-permission>


附錄文章:
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



Android二級緩衝之實體儲存體介質上的緩衝DiskLruCache

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.