一個優秀的應用首先它的用戶體驗是優秀的,在 Android 應用中恰當的使用緩存技術不只能夠緩解服務器壓力還能夠優化用戶的使用體驗,減小用戶流量的使用。在 Android 中緩存分爲內存緩存和磁盤緩存兩種:java
DiskLruCache 是 JakeWharton 大神在 github 上的一個開源庫,代碼量並很少。與谷歌官方的內存緩存策略LruCache 相對應,DiskLruCache 也聽從於 LRU(Least recently used 最近最少使用)算法,只不過存儲位置在磁盤上。雖然在谷歌的文檔中有提到但 DiskLruCache 並未集成到官方的 API中,使用的話按照 github 庫中的方式集成就行。
DiskLruCache 使用時須要注意:android
[a-z0-9_-]{1,120}
的規則即數字大小寫字母長度在1-120之間,因此推薦將字符串譬如圖片的 url 等進行 MD5 加密後做爲 key。DiskLruCache 不能使用 new 的方式建立,建立一個緩存對象方式以下:git
/** *參數說明 * *cacheFile 緩存文件的存儲路徑 *appVersion 應用版本號。DiskLruCache 認爲應用版本更新後全部的數據都因該從服務器從新拉取,所以須要版本號進行判斷 *1 每條緩存條目對應的值的個數,這裏設置爲1個。 *Constants.CACHE_MAXSIZE 我本身定義的常量類中的值表示換粗的最大存儲空間 **/
DiskLruCache mDiskLruCache = DiskLruCache.open(cacheFile, appVersion, 1, Constants.CACHE_MAXSIZE);複製代碼
DiskLruCache 存緩存是經過 DiskLruCache.Editor 處理的:github
/** *此處是爲代碼,實際使用還須要 try catch 處理可能出現的異常 * **/
String key = getMD5Result(key);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OutputStream os = editor.newOutputStream(0);
//此處存的一個 新聞對象所以用 ObjectOutputStream
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(stories);
//別忘了關閉流和提交編輯
outputStream.close();
editor.commit();複製代碼
DiskLruCache 取緩存是經過 DiskLruCache.Snapshot 處理的:算法
/** *此處是爲代碼,實際使用還須要 try catch 處理可能出現的異常 * **/
String key = getMD5Result(key);
//經過設置的 key 去獲取縮略對象
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
//經過 SnapShot 對象獲取流數據
InputStream in = snapshot.getInputStream(0);
ObjectInputStream ois = new ObjectInputStream(in);
//將流數據轉換爲 Object 對象
ArrayList<ZhihuStory> stories = (ArrayList<ZhihuStory>) ois.readObject();複製代碼
使用 DiskLruCache 進行磁盤緩存基本流程就這樣,開——>存 或者 開——>取。緩存
//使用rxandroid+retrofit進行請求
public void loadDataByRxandroidRetrofit() {
mINewsListActivity.showProgressBar();
Subscription subscription = ApiManager.getInstence().getDataService()
.getZhihuDaily()
.map(new Func1<ZhiHuDaily, ArrayList<ZhihuStory>>() {
@Override
public ArrayList<ZhihuStory> call(ZhiHuDaily zhiHuDaily) {
ArrayList<ZhihuStory> stories = zhiHuDaily.getStories();
if (stories != null) {
//加載成功後將數據緩存倒本地(demo 中只有一頁,實際使用時根據需求選擇是否進行緩存)
makeCache(zhiHuDaily.getStories());
}
return stories;
}
})
//設置事件觸發在非主線程
.subscribeOn(Schedulers.io())
//設置事件接受在UI線程以達到UI顯示的目的
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArrayList<ZhihuStory>>() {
@Override
public void onCompleted() {
mINewsListActivity.hidProgressBar();
}
@Override
public void onError(Throwable e) {
mINewsListActivity.getDataFail("", e.getMessage());
}
@Override
public void onNext(ArrayList<ZhihuStory> stories) {
mINewsListActivity.getDataSuccess(stories);
}
});
//綁定觀察對象,注意在界面的ondestory或者onpouse方法中調用presenter.unsubcription();
addSubscription(subscription);
}
//生成Cache
private void makeCache(ArrayList<ZhihuStory> stories) {
File cacheFile = getCacheFile(MyApplication.getContext(), Constants.ZHIHUCACHE);
DiskLruCache diskLruCache = DiskLruCache.open(cacheFile, MyApplication.getAppVersion(), 1, Constants.CACHE_MAXSIZE);
try {
//使用MD5加密後的字符串做爲key,避免key中有非法字符
String key = SecretUtil.getMD5Result(Constants.ZHIHUSTORY_KEY);
DiskLruCache.Editor editor = diskLruCache.edit(key);
if (editor != null) {
ObjectOutputStream outputStream = new ObjectOutputStream(editor.newOutputStream(0));
outputStream.writeObject(stories);
outputStream.close();
editor.commit();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//加載Cache
public void loadCache() {
File cacheFile = getCacheFile(MyApplication.getContext(), Constants.ZHIHUCACHE);
DiskLruCache diskLruCache = DiskLruCache.open(cacheFile, MyApplication.getAppVersion(), 1, Constants.CACHE_MAXSIZE);
String key = SecretUtil.getMD5Result(Constants.ZHIHUSTORY_KEY);
try {
DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
if (snapshot != null) {
InputStream in = snapshot.getInputStream(0);
ObjectInputStream ois = new ObjectInputStream(in);
try {
ArrayList<ZhihuStory> stories = (ArrayList<ZhihuStory>) ois.readObject();
if (stories != null) {
mINewsListActivity.getDataSuccess(stories);
} else {
mINewsListActivity.getDataFail("", "無數據");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//獲取Cache 存儲目錄
private File getCacheFile(Context context, String uniqueName) {
String cachePath = null;
if ((Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable())
&& context.getExternalCacheDir() != null) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}複製代碼
上面的代碼跑通流程存 Cache 取 Cache 是沒有問題的,可是這麼寫確定是不優雅的!兩年前的我可能會將這樣的代碼做爲發佈代碼。服務器
既有 key 又有 value 還有 Editor 的你想到了什麼?應該是 SharePreferences 吧!在 MVPDemo 中我構建了一個 DiskLruCacheManager 類來封裝 Cache 的存取。代碼就不貼了,你們自行在 demo 中查看 DiskManager 類,我只說一下怎麼使用它來存取 Cache:app
DiskCacheManager manager = new DiskCacheManager(MyApplication.getContext(), Constants.ZHIHUCACHE);複製代碼
而後經過 manager 的公共方法進行數據的存取:ide
數據類型 | 存入方法 | 取出方法 | 說明 |
---|---|---|---|
String | put(String key,String value) | getString(String key) | 返回String對象 |
JsonObject | put(String key,JsonObject value) | getJsonObject(String key) | 內部實際是轉換成String存取 |
JsonArray | put(String key,JsonArray value) | getJsonArray(String key) | 內部實際是轉換成String存取 |
byte[] | put(String key,byte[] bytes) | getBytes(String key) | 存圖片用這個實現,你們自行封裝啦 |
Serializable | put(String key,Serializable value) | getSerializable(String key) | 返回的是一個泛型對象 |
manager.flush() 方法推薦在須要緩存的界面的 onpause() 方法中調用,它的做用是同步緩存的日誌文件,不必每次緩存都調用優化