1. 程式人生 > >Android基於DiskLruCache做一個數據物件的快取工具

Android基於DiskLruCache做一個數據物件的快取工具

面試的時候被問過一次,如何對資料進行快取,我答的資料庫儲存json字串。被問到可不可以不用資料庫,直接檔案快取物件。當然也是行的。之前看過郭神的部落格:用lrucache與disklrucache快取圖片的。去年也仿著敲一個圖片快取工具類點選開啟連結,今年來到新公司正好也遇到要離線載入資料的功能,換湯不換藥的做了一個物件快取到本地的工具類。

思路

方案

資料庫我用的greendao,我發現greendao都有3.0的版本了,比2.0有較大的變化但更加方便了。

存物件物件要序列化

 public void putObject(String httpurl,Object ob){
        //將物件存入磁碟中
        //將url轉md5key
        String key = hashKeyForDisk(httpurl);
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            if (snapShot!=null){
                //說明磁盤裡有資料了,就刪掉
                mDiskLruCache.remove(key);
            }
            putObjectToMemoryCache(key,ob);
            //開始往磁碟放資料
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            new Thread(new Put_Object_Runnable(editor,cacheDbBeanDao,ob,httpurl,key)).start();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG,"向磁碟中存入"+httpurl+"的物件是異常");
        }
    }
 private class Put_Object_Runnable implements Runnable {
        private  DiskLruCache.Editor editor;
        private  CacheDbBeanDao cacheDbBeanDao;
        private Object ob;
        private String httpurl;
        private String key;

        public Put_Object_Runnable(DiskLruCache.Editor editor, CacheDbBeanDao cacheDbBeanDao, Object ob, String httpurl, String key) {
            this.editor = editor;
            this.cacheDbBeanDao = cacheDbBeanDao;
            this.ob = ob;
            this.httpurl = httpurl;
            this.key = key;
        }

        @Override
        public void run() {

            if (editor != null) {
                ObjectOutputStream  oos =null;
                try {
                    OutputStream outputStream = editor.newOutputStream(0);
                    oos= new ObjectOutputStream(outputStream);
                    oos.writeObject(ob);
                    oos.flush();
                    editor.commit();
                    //將記錄存資料庫
                    CacheDbBean cacheDbBean = cacheDbBeanDao.queryBuilder().where(CacheDbBeanDao.Properties.Key.eq(key)).unique();
                    if (cacheDbBean==null){
                        //第一次插入資料庫
                        Log.i(TAG,"此資料第一次插入資料庫"+httpurl);
                        cacheDbBeanDao.insert(new CacheDbBean(key,System.currentTimeMillis()));
                    }else{
                        Log.i(TAG,"此資料跟新資料庫的時間"+httpurl);
                        cacheDbBean.setTime(System.currentTimeMillis());
                        cacheDbBeanDao.update(cacheDbBean);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    try {
                        editor.abort();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }finally {
                    if (oos!=null){
                        try {
                            oos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            flushCache();
        }
    }

從磁碟讀取快取資料

 /**
     * 根據http獲取物件
     * @param httpurl
     * @return
     */

    public Object  getObjects(String httpurl){
        //將url轉md5key
        String key = hashKeyForDisk(httpurl);
        //先從記憶體中查詢
        Object objectFromMemoryCache = getObjectFromMemoryCache(key);
        if (objectFromMemoryCache==null){
            //記憶體中沒有
            Log.i(TAG,"記憶體中沒有此"+httpurl+"的物件");
            try {
                //查詢key 對應的硬碟快取
                DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
                if (snapShot != null) {
                    InputStream is = snapShot.getInputStream(0);
                    ObjectInputStream ois = new ObjectInputStream(is);
                    Log.i(TAG,"硬碟快取中有此"+httpurl+"的物件");
                    return  ois.readObject();
                }else{
                    Log.i(TAG,"硬碟快取中沒有此"+httpurl+"的物件");
                    return null;
                }
            } catch (IOException e) {
                e.printStackTrace();
                Log.i(TAG,"讀取硬碟快取異常");
                return null;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                Log.i(TAG,"讀取硬碟快取異常");
                return null;
            }
        }else{
            Log.i(TAG,"記憶體中有此"+httpurl+"的物件");
            return objectFromMemoryCache;
        }
    }

rxjava版

  public Flowable<Object> getObjectHH(String httpurl){

      return   Flowable.just(httpurl).map(new Function<String, String>() {
            @Override
            public String apply(String url) throws Exception {
                return  hashKeyForDisk(url);
            }
        }).subscribeOn(Schedulers.io()).flatMap(new Function<String, Publisher<?>>() {
            @Override
            public Publisher<?> apply(String key) throws Exception {
                Object objectFromMemoryCache = getObjectFromMemoryCache(key);
                //先從記憶體中查詢
                if (objectFromMemoryCache==null){
                    //記憶體中沒有
                    Log.i(TAG,"記憶體中沒有此"+httpurl+"的物件");
                    //查詢key 對應的硬碟快取
                    try {
                        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
                        if (snapShot != null) {
                            InputStream is = snapShot.getInputStream(0);
                            ObjectInputStream ois = new ObjectInputStream(is);
                            Log.i(TAG,"硬碟快取中有此"+httpurl+"的物件");
                            objectFromMemoryCache= ois.readObject();
                            return Flowable.just(objectFromMemoryCache);
                        }else{
                            Log.i(TAG,"硬碟快取中沒有此"+httpurl+"的物件");
                            return Flowable.just(objectFromMemoryCache);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.i(TAG,"讀取硬碟快取異常");
                        return  Flowable.just(objectFromMemoryCache);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        Log.i(TAG,"讀取硬碟快取異常");
                        return  Flowable.just(objectFromMemoryCache);
                    }

                }else{
                    Log.i(TAG,"記憶體中有此"+httpurl+"的物件");
                    return Flowable.just(objectFromMemoryCache);
                }
            }
        });

判斷快取是否存在或者或者快取是否有效

 //判斷一個介面是否有快取以及快取是否過期
    protected  boolean isHaveCacheOrCacheUseable(String httpurl){
        //是否有快取
        String key=hashKeyForDisk(httpurl);
        DiskLruCache.Snapshot snapShot = null;
        try {
            snapShot = mDiskLruCache.get(key);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (snapShot==null){
            //說明磁碟沒快取
            return false;
        }else{
            //有磁碟快取就判斷快取是否有效
            long currentTime = System.currentTimeMillis();//獲取當前的系統時間
            CacheDbBean cacheDbBean = cacheDbBeanDao.queryBuilder().where(CacheDbBeanDao.Properties.Key.eq(key)).unique();
            if (cacheDbBean!=null){
                if ((currentTime-cacheDbBean.getTime())>5*1000){
                    //超過5秒過期
                    return false;
                }else{
                    return true;
                }
            }else{
                return false;
            }
        }
    }

效果:


副本:

package com.shan.library.utils;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.util.Log;
import android.util.LruCache;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import io.reactivex.Flowable;

import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;


/**
 * Created by hcy on 2018/4/17.
 * func資料的三級快取
 */
public class SELrucacheUtils {

    private static final String TAG = "SELrucacheUtils";

    private DiskLruCache mDiskLruCache;//硬碟快取

    private LruCache<String, Object> mMemoryCache;//記憶體快取

    private Context context;

    private int maxMemory;//程式最大的可用記憶體

    private DaoSession mDaoSession;


    private static volatile SELrucacheUtils instance = null;

    public static SELrucacheUtils getInstance(Context context, String cacheDirName){
        if (instance == null) {
            synchronized (SELrucacheUtils.class) {
                if (instance == null) {
                    instance = new SELrucacheUtils(context,cacheDirName);
                }
            }
        }

        return instance;
    }

    //判斷一個介面是否有快取以及快取是否過期
    protected  boolean isHaveCacheOrCacheUseable(String httpurl){
        //是否有快取
        String key=hashKeyForDisk(httpurl);
        DiskLruCache.Snapshot snapShot = null;
        try {
            snapShot = mDiskLruCache.get(key);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (snapShot==null){
            //說明磁碟沒快取
            return false;
        }else{
            //有磁碟快取就判斷快取是否有效
            long currentTime = System.currentTimeMillis();//獲取當前的系統時間
            CacheDbBean cacheDbBean = cacheDbBeanDao.queryBuilder().where(CacheDbBeanDao.Properties.Key.eq(key)).unique();
            if (cacheDbBean!=null){
                if ((currentTime-cacheDbBean.getTime())>5*1000){
                    //超過5秒過期
                    return false;
                }else{
                    return true;
                }
            }else{
                return false;
            }
        }
    }


















    private DaoSession daoSession;
    private CacheDbBeanDao cacheDbBeanDao;

    public SELrucacheUtils(Context context, String cacheDirName) {
        this.context = context;
        //初始化資料庫
        //建立資料庫shop.db"
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, "shop.db", null);
        //獲取可寫資料庫
        SQLiteDatabase db = helper.getWritableDatabase();
        //獲取資料庫物件
        DaoMaster daoMaster = new DaoMaster(db);
        //獲取Dao物件管理者
        daoSession = daoMaster.newSession();
        cacheDbBeanDao=daoSession.getCacheDbBeanDao();


        try {
            File cacheDir = getDiskCacheDir(context, cacheDirName);//獲取快取路徑

            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            /**
             * 快取地址,應用版本,一個key對應幾個檔案,最大快取值
             */
            mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);

            maxMemory = (int) Runtime.getRuntime().maxMemory();

            int cacheSize = maxMemory / 8;
            mMemoryCache = new LruCache<String, Object>(cacheSize) {
                @Override
                protected int sizeOf(String key, Object value) {
                    return super.sizeOf(key, value);
                }
            };

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 關閉硬碟快取
     */
    public void closeDiskLruCache() {
        try {
            if (mDiskLruCache != null) {
                mDiskLruCache.close();//關閉快取
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 計算所有圖片的快取大小
     */
    public long countCache() {
        if (mDiskLruCache != null) {
            long size = mDiskLruCache.size();
            return size;
        } else {
            return 0;
        }
    }


    /**
     * 刪除快取
     */

    protected void deleteCache() throws IllegalStateException {
        if (mDiskLruCache != null) {
            try {
                mDiskLruCache.delete();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (IllegalStateException e) {
                throw new IllegalStateException("cache is closed");
            }
        }
    }


    /**
     * 將物件儲存到記憶體中
     *
     * @param key
     * @param ob
     */

    public void putObjectToMemoryCache(String key, Object ob) {
           mMemoryCache.put(key, ob);
    }




    //同步快取記錄
    private  void flushCache() {
        if (mDiskLruCache != null) {
            try {
                mDiskLruCache.flush();
            } catch (IOException e) {
                Log.i(TAG, "mDiskLruCache.flush() Error");
            } catch (IllegalStateException e) {
                Log.i(TAG, "mDiskLruCache.flush() ErrorE");
            }
        }
    }


    /**
     * 將物件放入
     * @param httpurl
     * @param ob
     */

    public void putObject(String httpurl,Object ob){
        //將物件存入磁碟中
        //將url轉md5key
        String key = hashKeyForDisk(httpurl);
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            if (snapShot!=null){
                //說明磁盤裡有資料了,就刪掉
                mDiskLruCache.remove(key);
            }
            putObjectToMemoryCache(key,ob);
            //開始往磁碟放資料
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            new Thread(new Put_Object_Runnable(editor,cacheDbBeanDao,ob,httpurl,key)).start();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG,"向磁碟中存入"+httpurl+"的物件是異常");
        }
    }


    private class Put_Object_Runnable implements Runnable {
        private  DiskLruCache.Editor editor;
        private  CacheDbBeanDao cacheDbBeanDao;
        private Object ob;
        private String httpurl;
        private String key;

        public Put_Object_Runnable(DiskLruCache.Editor editor, CacheDbBeanDao cacheDbBeanDao, Object ob, String httpurl, String key) {
            this.editor = editor;
            this.cacheDbBeanDao = cacheDbBeanDao;
            this.ob = ob;
            this.httpurl = httpurl;
            this.key = key;
        }

        @Override
        public void run() {

            if (editor != null) {
                ObjectOutputStream  oos =null;
                try {
                    OutputStream outputStream = editor.newOutputStream(0);
                    oos= new ObjectOutputStream(outputStream);
                    oos.writeObject(ob);
                    oos.flush();
                    editor.commit();
                    //將記錄存資料庫
                    CacheDbBean cacheDbBean = cacheDbBeanDao.queryBuilder().where(CacheDbBeanDao.Properties.Key.eq(key)).unique();
                    if (cacheDbBean==null){
                        //第一次插入資料庫
                        Log.i(TAG,"此資料第一次插入資料庫"+httpurl);
                        cacheDbBeanDao.insert(new CacheDbBean(key,System.currentTimeMillis()));
                    }else{
                        Log.i(TAG,"此資料跟新資料庫的時間"+httpurl);
                        cacheDbBean.setTime(System.currentTimeMillis());
                        cacheDbBeanDao.update(cacheDbBean);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    try {
                        editor.abort();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }finally {
                    if (oos!=null){
                        try {
                            oos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            flushCache();
        }
    }


 /*   *
     * 存入物件
     *//*

     public void  putObject(String httpurl,Object ob){
         //將url轉md5key
         String key = hashKeyForDisk(httpurl);
         Object objectFromMemoryCache = getObjectFromMemoryCache(key);
         if (objectFromMemoryCache==null){
             Log.i(TAG,"向記憶體中存入"+httpurl+"的物件");
             putObjectToMemoryCache(key,ob);
         }
         //查詢key 對應的硬碟快取
         try {
             DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
             if (snapShot==null){
                 Log.i(TAG,"向磁碟中存入"+httpurl+"的物件");
                 DiskLruCache.Editor editor = mDiskLruCache.edit(key);

             }else{
                 Log.i(TAG,"向磁碟中已經存在"+httpurl+"的物件");
             }
         } catch (IOException e) {
             Log.e(TAG,"向磁碟中存入"+httpurl+"的物件是異常");
             e.printStackTrace();
         }

     }*/

    /**
     * 根據http獲取物件
     * @param httpurl
     * @return
     */

    public Object  getObjects(String httpurl){
        //將url轉md5key
        String key = hashKeyForDisk(httpurl);
        //先從記憶體中查詢
        Object objectFromMemoryCache = getObjectFromMemoryCache(key);
        if (objectFromMemoryCache==null){
            //記憶體中沒有
            Log.i(TAG,"記憶體中沒有此"+httpurl+"的物件");
            try {
                //查詢key 對應的硬碟快取
                DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
                if (snapShot != null) {
                    InputStream is = snapShot.getInputStream(0);
                    ObjectInputStream ois = new ObjectInputStream(is);
                    Log.i(TAG,"硬碟快取中有此"+httpurl+"的物件");
                    return  ois.readObject();
                }else{
                    Log.i(TAG,"硬碟快取中沒有此"+httpurl+"的物件");
                    return null;
                }
            } catch (IOException e) {
                e.printStackTrace();
                Log.i(TAG,"讀取硬碟快取異常");
                return null;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                Log.i(TAG,"讀取硬碟快取異常");
                return null;
            }
        }else{
            Log.i(TAG,"記憶體中有此"+httpurl+"的物件");
            return objectFromMemoryCache;
        }
    }



    public Flowable<Object> getObjectHH(String httpurl){

      return   Flowable.just(httpurl).map(new Function<String, String>() {
            @Override
            public String apply(String url) throws Exception {
                return  hashKeyForDisk(url);
            }
        }).subscribeOn(Schedulers.io()).flatMap(new Function<String, Publisher<?>>() {
            @Override
            public Publisher<?> apply(String key) throws Exception {
                Object objectFromMemoryCache = getObjectFromMemoryCache(key);
                //先從記憶體中查詢
                if (objectFromMemoryCache==null){
                    //記憶體中沒有
                    Log.i(TAG,"記憶體中沒有此"+httpurl+"的物件");
                    //查詢key 對應的硬碟快取
                    try {
                        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
                        if (snapShot != null) {
                            InputStream is = snapShot.getInputStream(0);
                            ObjectInputStream ois = new ObjectInputStream(is);
                            Log.i(TAG,"硬碟快取中有此"+httpurl+"的物件");
                            objectFromMemoryCache= ois.readObject();
                            return Flowable.just(objectFromMemoryCache);
                        }else{
                            Log.i(TAG,"硬碟快取中沒有此"+httpurl+"的物件");
                            return Flowable.just(objectFromMemoryCache);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.i(TAG,"讀取硬碟快取異常");
                        return  Flowable.just(objectFromMemoryCache);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        Log.i(TAG,"讀取硬碟快取異常");
                        return  Flowable.just(objectFromMemoryCache);
                    }

                }else{
                    Log.i(TAG,"記憶體中有此"+httpurl+"的物件");
                    return Flowable.just(objectFromMemoryCache);
                }
            }
        });




     /*  return  Flowable.just(httpurl).map((url)->{
            return  hashKeyForDisk(url);
        }).flatMap((key)->{
            Object objectFromMemoryCache = getObjectFromMemoryCache(key);
            //先從記憶體中查詢
            if (objectFromMemoryCache==null){
                //記憶體中沒有
                Log.i(TAG,"記憶體中沒有此"+httpurl+"的物件");
                //查詢key 對應的硬碟快取
                try {
                    DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
                    if (snapShot != null) {
                        InputStream is = snapShot.getInputStream(0);
                        ObjectInputStream ois = new ObjectInputStream(is);
                        Log.i(TAG,"硬碟快取中有此"+httpurl+"的物件");
                        objectFromMemoryCache= ois.readObject();
                        return Flowable.just(objectFromMemoryCache);
                    }else{
                        Log.i(TAG,"硬碟快取中沒有此"+httpurl+"的物件");
                        return Flowable.just(objectFromMemoryCache);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.i(TAG,"讀取硬碟快取異常");
                    return  Flowable.just(objectFromMemoryCache);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    Log.i(TAG,"讀取硬碟快取異常");
                    return  Flowable.just(objectFromMemoryCache);
                }

            }else{
                Log.i(TAG,"記憶體中有此"+httpurl+"的物件");
                return Flowable.just(objectFromMemoryCache);
            }
        }).subscribeOn(Schedulers.io());*/

    }


    /**
     * 從記憶體中獲取物件
     * @param key
     * @return
     */
    public Object getObjectFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }

    /**
     * 使用MD5演算法對傳入的key進行加密並返回。
     */
    private 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();
    }










    /**
     * 獲取快取路徑
     *
     * @param context
     * @param cacheFileName
     * @return
     */

    private File getDiskCacheDir(Context context, String cacheFileName) {

        String cachePath;
        if (context != null) {
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                    || !Environment.isExternalStorageRemovable()) {
                if (context.getExternalCacheDir() != null) {
                    if (context.getExternalCacheDir().getPath() != null) {
                        cachePath = context.getExternalCacheDir().getPath();
                    } else {
                        cachePath = context.getCacheDir().getPath();
                    }
                } else {
                    cachePath = "/data/data/com.wyt.hcy.learningenglishapp/cache";
                }


            } else {
                cachePath = context.getCacheDir().getPath();
            }
        } else {
            cachePath = "/data/data/com.wyt.hcy.learningenglishapp/cache";
        }

        return new File(cachePath + File.separator + cacheFileName);

    }


    /**
     * 獲取當前應用程式的版本號。
     */
    private int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),
                    0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

}