自己封裝雙快取管理框架 Android 庫

Jamy Cai發表於2015-12-12

一、概述

Android開發中,網路請求是很重要的一部分,而快取網路請求來的圖片或者響應結果字串或者結果流,既可以省流量,同時也可以幫助我們解決無網或弱網情況下載入情況,當然也可以提升程式效能效率。縱所周知,快取管理中肯定需要用到記憶體快取,這裡我們採用LruCache來管理記憶體的快取。

LruCahce雖然速度快,但是隻是記憶體級別的快取,為了實現持久化的快取,我們還需要檔案級別的快取,也就是說我們要把快取儲存到檔案,而檔案則是儲存到手機儲存或者SD卡儲存中,即實現Disk級別的快取,這裡我們藉助DiskLruCache這個輔助工具類來實現。顧名思義,這個工具類的作用就是使用Lru演算法來儲存資訊到Disk上。

二、例項效果圖

下面是個簡單的例項演示效果圖

自己封裝雙快取管理框架Android 庫

三、快取管理框架的實現解

1、記憶體快取類的實現

該類主要實現記憶體級別的快取管理類MemoryCache,使用LruCache來實現,因為無論是記憶體快取還是Disk快取,都需要讀寫操作,所以我們先抽象出一個快取介面類:Cache介面:

public interface Cache {
    String get(final String key);
    void put(final String key, final String value);
    boolean remove(final String key);
}

然後,我們的記憶體快取類MemoryCache需要實現Cache這個介面:

/**
 * 記憶體快取類
 * Created by caizhiming on 2015/12/4.
 */
public class MemoryCache implements Cache {
    private LruCache<String, String> mMemoryLruCache;
    private EvictedListener mEvictedListener;

    public MemoryCache() {
        init();
    }

    public MemoryCache(EvictedListener listener) {
        init();
        this.mEvictedListener = listener;
    }

    public void setEvictedListener(EvictedListener listener) {
        this.mEvictedListener = listener;
    }

    public boolean hasEvictedListener() {
        return mEvictedListener != null;
    }

    private void init() {
        // 計算可使用的最大記憶體
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 取可用記憶體空間的1/4作為快取
        final int cacheSize = maxMemory / 4;
        mMemoryLruCache = new LruCache<String, String>(cacheSize) {
            @Override
            protected int sizeOf(String key, String value) {
                return value.getBytes().length;
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, String oldValue, String newValue) {
                if (evicted) {
                    if (mEvictedListener != null) {
                        mEvictedListener.handleEvictEntry(key, oldValue);
                    }
                }
            }
        };
    }

    @Override
    public String get(String key) {
        return mMemoryLruCache.get(key);
    }

    @Override
    public void put(String key, String value) {
        mMemoryLruCache.put(key, value);
    }

    @Override
    public boolean remove(String key) {
        return Boolean.parseBoolean(mMemoryLruCache.remove(key));
    }

    /**
     * called when mMemoryLruCache evict entrys,
     * <p/>
     * using by CacheManager.Strategy.MEMORY_FIRST
     */
    public interface EvictedListener {
        void handleEvictEntry(String evictKey, String evictValue);
    }

2、檔案級別的Disk快取類實現

接下來我們需要寫一個用於Disk快取管理的類:DiskCache類,該類我們也實現Cache介面,該類的主要功能也是提供Disk快取的讀取和寫入操作管理。

/**
 * Disk磁碟快取類
 * Created by caizhiming on 2015/12/4.
 */
public class DiskCache implements Cache{

    private DiskLruCache mDiskLruCache = null;
    public DiskCache(Context context){
        init(context);
    }
    /**
     * 初始化 DiskLruCache
     */
    public void init(Context context){
        try {
            File cacheDir = getDiskCacheDir(context, "http_cache");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            Log.v("czm", "cache file=" + cacheDir.getAbsolutePath());
            mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public String get(String key) {
        String result = null;
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(hashKeyForDisk(key));
            if (snapShot != null) {
                result = snapShot.getString(0);
                return result;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return result;
    }

    @Override
    public void put(String key, String value) {
        DiskLruCache.Editor editor = null;
        try {
            editor = mDiskLruCache.edit(hashKeyForDisk(key));
            if (editor != null) {
                editor.set(0, value);
                editor.commit();
            }
            mDiskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean remove(String key) {
        try {
            return mDiskLruCache.remove(hashKeyForDisk(key));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    public Bitmap getImageCache(String key){
        Bitmap bitmap = null;
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(hashKeyForDisk(key));
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);
                bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
    public void putImageCache(final String key){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyForDisk(key));
                    if (editor != null) {
                        OutputStream outputStream = editor.newOutputStream(0);
                        if (downloadUrlToStream(key, outputStream)) {
                            editor.commit();
                        } else {
                            editor.abort();
                        }
                    }
                    mDiskLruCache.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    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(), 8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            CloseUtils.closeCloseable(out);
            CloseUtils.closeCloseable(in);
        }
        return false;
    }

    public 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();
    }
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    public int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

}

3、搭建封裝雙快取管理框架類XCCacheManager

有了上面的記憶體快取類MemoryCache和Disk快取類DiskCache,我們就可以搭建封裝真正的快取管理類XCCacheManager了。

(1) 首先我們採用執行緒池技術來實現多執行緒快取的讀寫操作

這樣可以提高程式的效能,同時能處理任務量比較大的併發讀寫操作。

private static XCCacheManager mInstance = null;

private Strategy mStrategy = Strategy.MEMORY_FIRST;
//執行緒池
private ExecutorService mExecutor = null;
//記憶體快取
private MemoryCache mMemoryCache;
//Disk快取
private DiskCache mDiskCache;

/**
     * 初始化 DiskLruCache
     */
    private void init(Context context) {
        mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        mDiskCache = new DiskCache(context);
        mMemoryCache = new MemoryCache();
    }

(2)其次XCCacheManager管理類採用單例實現

public static XCCacheManager getInstance(Context context, Strategy strategy) {
    if (mInstance == null) {
        synchronized (XCCacheManager.class) {
            if (mInstance == null) {
                mInstance = new XCCacheManager(context.getApplicationContext(), strategy);
            }
        }
    } else {
        mInstance.setStrategy(strategy);
    }
    return mInstance;
}

(3)快取策略

這裡我們定義了快取策略,便於適應各種不同業務需求,可以靈活使用不同的策略

enum Strategy {
        MEMORY_ONLY(0), MEMORY_FIRST(1), DISK_ONLY(3);
        int id;

        Strategy(int id) {
            this.id = id;
        }
    }

預設採用記憶體優先MEMORY_FIRST這種策略

(4)根據對應的策略從快取中讀取內容

/**
     * 從快取中讀取value
     */
    public String readCache(final String key) {
        Future<String> ret = mExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String result = null;
                switch (mStrategy) {
                    case MEMORY_ONLY:
                        result = mMemoryCache.get(key);
                        break;
                    case MEMORY_FIRST:
                        result = mMemoryCache.get(key);
                        if (result == null) {
                            result = mDiskCache.get(key);
                        }
                        break;
                    case DISK_ONLY:
                        result = mDiskCache.get(key);
                        break;
                }
                return result;
            }
        });
        try {
            return ret.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }

(5)將內容寫入到快取中

/**
     * 將value 寫入到快取中
     */
    public void writeCache(final String key, final String value) {
        mExecutor.submit(new Runnable() {
            @Override
            public void run() {
                switch (mStrategy) {
                    case MEMORY_FIRST:
                        if (!mMemoryCache.hasEvictedListener()) {
                            mMemoryCache.setEvictedListener(new MemoryCache.EvictedListener() {
                                @Override
                                public void handleEvictEntry(String evictKey, String evictValue) {
                                    mDiskCache.put(evictKey, evictValue);
                                }
                            });
                        }
                        mMemoryCache.put(key, value);
                        break;
                    case MEMORY_ONLY:
                        if (mMemoryCache.hasEvictedListener())
                            mMemoryCache.setEvictedListener(null);
                        mMemoryCache.put(key, value);
                        break;
                    case DISK_ONLY:
                        mDiskCache.put(key, value);
                        break;
                }
            }
        });
    }

到此為止,框架的開發到此完成。希望對有需要的人有所幫助。

四、原始碼下載

原始碼下載 : http://download.csdn.net/detail/jczmdeveloper/9348031

GitHub地址:https://github.com/jczmdeveloper/XCCacheManager

相關文章