今天就聊聊retrofit 中使用自定义的缓存拦截器来实现POST的缓存策略吧。
带着这几个疑问吧:
1.retrofit 不是可以直接使用缓存拦截器吗?为什么还要自定义呢。
2.拦截器有什么作用呢,这个自定义的缓存拦截器实现什么功能呢。
解答:
1.retrofit内部使用的是okHttpClient,它内部只支持GET缓存,所以如果你想缓存POST的请求的话
还是得自己动手,丰衣足食了。
2.他实现这样的功能:
2.1 .默认不缓存,支持自定义每个接口设置缓存请求时间。
2.2 设置了缓存,则从缓存中读取数据,不访问网络,离线也能获取数据。过了缓存时间则再次从网 络获取数据并插入到离线缓存中。
2.3 缓存是存储在文件中用DiskLruCache实现。
先看看我们拦截器的代码 HttpCacheInterceptor.java:
package com.eebbk.bbkmiddlemarket.modulecommon.net.intercetor;
import android.text.TextUtils;
import com.eebbk.bbkmiddlemarket.modulebase.BaseApplication;
import com.eebbk.bbkmiddlemarket.modulebase.utils.JsonTools;
import com.eebbk.bbkmiddlemarket.modulebase.utils.LogUtil;
import com.eebbk.bbkmiddlemarket.modulecommon.CommonApplication;
import com.eebbk.bbkmiddlemarket.modulecommon.net.cache.DiskLruCacheHelper;
import com.eebbk.bbkmiddlemarket.modulecommon.data.DataCache;
import com.eebbk.bbkmiddlemarket.modulecommon.net.RetrofitClient;
import com.eebbk.bbkmiddlemarket.modulecommon.utils.MD5;
import com.eebbk.bbkmiddlemarket.modulecommon.utils.constants.HttpConstant;
import java.io.IOException;
import java.nio.charset.Charset;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
public class HttpCacheInterceptor implements Interceptor {
private DiskLruCacheHelper helper;
public HttpCacheInterceptor() {
try {
helper = new DiskLruCacheHelper(BaseApplication.getContext());
} catch (IOException e) {
e.printStackTrace();
LogUtil.e(RetrofitClient.LOG_TAG, "RetrofitClient HttpCacheInterceptor 初始化DiskLruCacheHelp error :" + e.getMessage());
}
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RequestBody requestBody = request.body();
if (null == requestBody || CommonApplication.isDebugModeOpen()) {
LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor debug 不缓存 :" + request.url());
return chain.proceed(request);
}
long expiredTime = getExpiredTime(request);
if (expiredTime == 0) {
LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 默认不缓存 直接从网络中获取数据 ");
return chain.proceed(request);
} else {
//获取请求url 和 参数
String url = request.url().toString();
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
String params = buffer.readString(Charset.forName("UTF-8"));
DataCache dataCache = getCacheByDiskLru(url, params);
if (dataCache == null || TextUtils.isEmpty(dataCache.getJson()) || System.currentTimeMillis() > dataCache.getExpireTime()) {
LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 需要缓存 当前无缓存记录或缓存过期 直接从网络中获取数据 ");
Response response = chain.proceed(request);
ResponseBody responseBody = response.body();
if (null == responseBody) {
return chain.proceed(request);
}
//获取MediaType,用于重新构建ResponseBody
MediaType type = responseBody.contentType();
//获取body字节即响应,用于存入数据库和重新构建ResponseBody
byte[] bs = responseBody.bytes();
response = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
//重新构建body,原因在于body只能调用一次,之后就关闭了。
.body(ResponseBody.create(type, bs))
.build();
//插入缓存数据库
putCache(getCacheKey(url, params), expiredTime, bs);
return response;
} else {
LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 从缓存中获取数据");
return new Response.Builder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.body(ResponseBody.create(MediaType.parse("application/json"), dataCache.getJson().getBytes()))
.request(request)
.message("")
.protocol(Protocol.HTTP_1_1)
.code(200)
.build();
}
}
}
private DataCache getCacheByDiskLru(String url, String params) {
DataCache dataCache = null;
try {
//获取请求缓存
String cacheStr = helper.getAsString(getCacheKey(url, params));
dataCache = JsonTools.jsonToObj(cacheStr, DataCache.class);
} catch (Exception e) {
LogUtil.e(RetrofitClient.LOG_TAG, " HttpCacheInterceptor getCacheByDiskLru error :" + e.getMessage());
}
return dataCache;
}
private long getExpiredTime(Request request) {
long expiredTime = 0;
try {
String expiredTimeStr = request.header(HttpConstant.HEADER_EXPIRED_TIME);
expiredTime = Long.valueOf(expiredTimeStr);
} catch (NumberFormatException e) {
LogUtil.e(RetrofitClient.LOG_TAG, "HttpCacheInterceptor getExpiredTime error :" + e.getMessage());
}
LogUtil.i(RetrofitClient.LOG_TAG, "HttpCacheInterceptor getExpiredTime :" + expiredTime);
return expiredTime;
}
private void putCache(String key, long expiredTime, byte[] bs) {
try {
DataCache netCache = new DataCache(System.currentTimeMillis() + expiredTime * 1000, new String(bs, "UTF-8"));
helper.put(key, JsonTools.objToJson(netCache));
LogUtil.d(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 从网络中获取数据 插入缓存");
} catch (Exception e) {
LogUtil.e(RetrofitClient.LOG_TAG, "HttpCacheInterceptor 插入数据 DataCache error :" + e.getMessage());
}
}
/**
* 根据Url 与 params 获取缓存Key
*
* @param url
* @param params
* @return cacheKey
*/
private String getCacheKey(String url, String params) {
return MD5.md5(url + params);
}
}
讲下思路:
构造方法中,我初始化出DiskLruCacheHelper用来辅助存储网络缓存。
主要看看intercept(Chain chain)这个方法,每次请求都会经过这个方法。我们的缓存拦截就是要在这里实现。
1.首先进行安全校验,如果请求体为null或者是Debug模式(Debug模式是我自己写的逻辑)就直接返回,走正常访问网络流传。
2.通过getExpiredTime()方法获取请求体的缓存时间,如果未设置缓存时间,则走正常网络访问
3.如果设置了缓存时间,则从缓存中获取缓存对象DataCache,如果获取为Null,或者时间过期,则从网络中获取数据再插入本地缓存。
4.从缓存中获取的对象不为Null,则直接从本地缓存中获取数据。
我们这么用
初始化OkHttpClient的时候
new OkHttpClient.Builder().addInterceptor(new HttpCacheInterceptor()).build();
- 1
在Service中,在请求头中设置缓存时间。
- 1
@Headers(HttpConstant.HEADER_EXPIRED_TIME+":"+ HttpConstant.TIME_CACHE_TEN_MINUTE)**
@POST("businessLike/getHomePage")
@FormUrlEncoded
@Headers(HttpConstant.HEADER_EXPIRED_TIME+":"+ HttpConstant.TIME_CACHE_TEN_MINUTE)
Observable<BaseResponse<List<ChoicenessWithAdsBean>>> getHomePage(@FieldMap Map<String, String> maps);
- 1
- 2
- 3
- 4
HttpConstant.java
package com.eebbk.bbkmiddlemarket.modulecommon.utils.constants;
public class HttpConstant {
//expired_time 请求头
public static final String HEADER_EXPIRED_TIME = "expired_time";
/**----------------------------缓存时间 start--------------------------------**/
/**
* 秒
*/
public static final int SECOND = 1;
/**
* 分
*/
public static final int MINUTE = 60 * SECOND;
/**
* 时
*/
public static final int HOUR = 60 * MINUTE;
/**
* 天
*/
public static final int DAY = 24 * HOUR;
/**
* 周
*/
public static final int WEEK = 7 * DAY;
/**
* 缓存十分钟 60s x 10
*/
public static final int TIME_CACHE_TEN_MINUTE = 10 * MINUTE;
/**----------------------------缓存时间 end--------------------------------**/
}
再贴上我们几个缓存工具类
DiskLruCacheHelper.java
package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
/**
-
Created by zhy on 15/7/28.
*/
public class DiskLruCacheHelper {
private static final String DIR_NAME = “diskCache”;
private static final int MAX_COUNT = 5 * 1024 * 1024;
private static final int DEFAULT_APP_VERSION = 1;/**
- The default valueCount when open DiskLruCache.
*/
private static final int DEFAULT_VALUE_COUNT = 1;
private static final String TAG = “DiskLruCacheHelper”;
private DiskLruCache mDiskLruCache;
public DiskLruCacheHelper(Context context) throws IOException {
mDiskLruCache = generateCache(context, DIR_NAME, MAX_COUNT);
}public DiskLruCacheHelper(Context context, String dirName) throws IOException {
mDiskLruCache = generateCache(context, dirName, MAX_COUNT);
}public DiskLruCacheHelper(Context context, String dirName, int maxCount) throws IOException {
mDiskLruCache = generateCache(context, dirName, maxCount);
}//custom cache dir
public DiskLruCacheHelper(File dir) throws IOException {
mDiskLruCache = generateCache(null, dir, MAX_COUNT);
}public DiskLruCacheHelper(Context context, File dir) throws IOException {
mDiskLruCache = generateCache(context, dir, MAX_COUNT);
}public DiskLruCacheHelper(Context context, File dir, int maxCount) throws IOException {
mDiskLruCache = generateCache(context, dir, maxCount);
}private DiskLruCache generateCache(Context context, File dir, int maxCount) throws IOException {
if (!dir.exists() || !dir.isDirectory()) {
throw new IllegalArgumentException(
dir + " is not a directory or does not exists. ");
}int appVersion = context == null ? DEFAULT_APP_VERSION : CacheUtils.getAppVersion(context); DiskLruCache diskLruCache = DiskLruCache.open( dir, appVersion, DEFAULT_VALUE_COUNT, maxCount); return diskLruCache;
}
private DiskLruCache generateCache(Context context, String dirName, int maxCount) throws IOException {
DiskLruCache diskLruCache = DiskLruCache.open(
getDiskCacheDir(context, dirName),
CacheUtils.getAppVersion(context),
DEFAULT_VALUE_COUNT,
maxCount);
return diskLruCache;
}
// =======================================
// ============== String 数据 读写 =============
// =======================================public void put(String key, String value) {
DiskLruCache.Editor edit = null;
BufferedWriter bw = null;
try {
edit = editor(key);
if (edit == null) return;
OutputStream os = edit.newOutputStream(0);
bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write(value);
edit.commit();//write CLEAN
} catch (IOException e) {
e.printStackTrace();
try {
//s
edit.abort();//write REMOVE
} catch (IOException e1) {
e1.printStackTrace();
}
} finally {
try {
if (bw != null)
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}public String getAsString(String key) {
InputStream inputStream = null;
inputStream = get(key);
if (inputStream == null) return null;
String str = null;
try {
str = CacheUtils.readFully(new InputStreamReader(inputStream, CacheUtils.UTF_8));
} catch (IOException e) {
e.printStackTrace();
try {
inputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
return str;
}public void put(String key, JSONObject jsonObject) {
put(key, jsonObject.toString());
}public JSONObject getAsJson(String key) {
String val = getAsString(key);
try {
if (val != null)
return new JSONObject(val);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}// =======================================
// ============ JSONArray 数据 读写 =============
// =======================================public void put(String key, JSONArray jsonArray) {
put(key, jsonArray.toString());
}public JSONArray getAsJSONArray(String key) {
String JSONString = getAsString(key);
try {
JSONArray obj = new JSONArray(JSONString);
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}// =======================================
// ============== byte 数据 读写 =============
// =======================================/**
-
保存 byte数据 到 缓存中
-
@param key 保存的key
-
@param value 保存的数据
*/
public void put(String key, byte[] value) {
OutputStream out = null;
DiskLruCache.Editor editor = null;
try {
editor = editor(key);
if (editor == null) {
return;
}
out = editor.newOutputStream(0);
out.write(value);
out.flush();
editor.commit();//write CLEAN
} catch (Exception e) {
e.printStackTrace();
try {
editor.abort();//write REMOVE
} catch (IOException e1) {
e1.printStackTrace();
}} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public byte[] getAsBytes(String key) {
byte[] res = null;
InputStream is = get(key);
if (is == null) return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] buf = new byte[256];
int len = 0;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
res = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return res;
}// =======================================
// ============== 序列化 数据 读写 =============
// =======================================
public void put(String key, Serializable value) {
DiskLruCache.Editor editor = editor(key);
ObjectOutputStream oos = null;
if (editor == null) return;
try {
OutputStream os = editor.newOutputStream(0);
oos = new ObjectOutputStream(os);
oos.writeObject(value);
oos.flush();
editor.commit();
} catch (IOException e) {
e.printStackTrace();
try {
editor.abort();
} catch (IOException e1) {
e1.printStackTrace();
}
} finally {
try {
if (oos != null)
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}public <T> T getAsSerializable(String key) {
T t = null;
InputStream is = get(key);
ObjectInputStream ois = null;
if (is == null) return null;
try {
ois = new ObjectInputStream(is);
t = (T) ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (ois != null)
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return t;
}// =======================================
// ============== bitmap 数据 读写 =============
// =======================================
public void put(String key, Bitmap bitmap) {
put(key, CacheUtils.bitmap2Bytes(bitmap));
}public Bitmap getAsBitmap(String key) {
byte[] bytes = getAsBytes(key);
if (bytes == null) return null;
return CacheUtils.bytes2Bitmap(bytes);
}// =======================================
// ============= drawable 数据 读写 =============
// =======================================
public void put(String key, Drawable value) {
put(key, CacheUtils.drawable2Bitmap(value));
}public Drawable getAsDrawable(String key) {
byte[] bytes = getAsBytes(key);
if (bytes == null) {
return null;
}
return CacheUtils.bitmap2Drawable(CacheUtils.bytes2Bitmap(bytes));
}// =======================================
// ============= other methods =============
// =======================================
public boolean remove(String key) {
try {
key = CacheUtils.hashKeyForDisk(key);
return mDiskLruCache.remove(key);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}public void close() throws IOException {
mDiskLruCache.close();
}public void delete() throws IOException {
mDiskLruCache.delete();
}public void flush() throws IOException {
mDiskLruCache.flush();
}public boolean isClosed() {
return mDiskLruCache.isClosed();
}public long size() {
return mDiskLruCache.size();
}public void setMaxSize(long maxSize) {
mDiskLruCache.setMaxSize(maxSize);
}public File getDirectory() {
return mDiskLruCache.getDirectory();
}public long getMaxSize() {
return mDiskLruCache.getMaxSize();
}// =======================================
// ===遇到文件比较大的,可以直接通过流读写 =====
// =======================================
//basic editor
public DiskLruCache.Editor editor(String key) {
try {
key = CacheUtils.hashKeyForDisk(key);
//wirte DIRTY
DiskLruCache.Editor edit = mDiskLruCache.edit(key);
//edit maybe null :the entry is editing
if (edit == null) {
Log.w(TAG, “the entry spcified key:” + key + " is editing by other . ");
}
return edit;
} catch (IOException e) {
e.printStackTrace();
}return null;
}
//basic get
public InputStream get(String key) {
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(CacheUtils.hashKeyForDisk(key));
if (snapshot == null) //not find entry , or entry.readable = false
{
Log.e(TAG, “not find entry , or entry.readable = false”);
return null;
}
//write READ
return snapshot.getInputStream(0);} catch (IOException e) { e.printStackTrace(); return null; }
}
// =======================================
// ============== 序列化 数据 读写 =============
// =======================================private File getDiskCacheDir(Context context, String uniqueName) {
String cachePath = context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
} - The default valueCount when open DiskLruCache.
DiskLruCache.java
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- Created by lyj 2019-3-26
DiskLruCache
*/
public final class DiskLruCache implements Closeable {
private static final String JOURNAL_FILE = “journal”;
private static final String JOURNAL_FILE_TEMP = “journal.tmp”;
private static final String JOURNAL_FILE_BACKUP = “journal.bkp”;
private static final String MAGIC = “libcore.io.DiskLruCache”;
private static final String VERSION_1 = “1”;
private static final long ANY_SEQUENCE_NUMBER = -1;
private static final String STRING_KEY_PATTERN = “[a-z0-9_-]{1,120}”;
private static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
private static final String CLEAN = “CLEAN”;
private static final String DIRTY = “DIRTY”;
private static final String REMOVE = “REMOVE”;
private static final String READ = “READ”;
private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final File journalFileBackup;
private final int appVersion;
private long maxSize;
private final int valueCount;
private long size = 0;
private Writer journalWriter;
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<>(0, 0.75f, true);
private int redundantOpCount;
/**
- To differentiate between old and current snapshots, each entry is given
- a sequence number each time an edit is committed. A snapshot is stale if
- its sequence number is not equal to its entry’s sequence number.
*/
private long nextSequenceNumber = 0;
/** This cache uses a single background thread to evict entries. */
final ThreadPoolExecutor executorService =
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private final Callable<Void> cleanupCallable = new Callable<Void>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}
/**
- Opens the cache in {@code directory}, creating a cache if none exists
- there.
- @param directory a writable directory
- @param valueCount the number of values per cache entry. Must be positive.
- @param maxSize the maximum number of bytes this cache should use to store
- @throws IOException if reading or writing the cache directory fails
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException(“maxSize <= 0”);
}
if (valueCount <= 0) {
throw new IllegalArgumentException(“valueCount <= 0”);
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
return cache;
} catch (IOException journalIsCorrupt) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), CacheUtils.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException(“unexpected journal header: [” + magic + ", " + version + ", "
+ valueCountString + ", " + blank + “]”);
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
// If we ended on a truncated line, rebuild the journal before appending to it.
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), CacheUtils.US_ASCII));
}
} finally {
CacheUtils.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(’ ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
/**
- Computes the initial size and collects garbage as a part of opening the
- cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
/**
- Creates a new journal that omits redundant information. This replaces the
- current journal if it exists.
*/
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
Writer writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFileTmp), CacheUtils.US_ASCII));
try {
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY + ' ' + entry.key + '\n');
} else {
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
}
}
} finally {
writer.close();
}
if (journalFile.exists()) {
renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);
journalFileBackup.delete();
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true), CacheUtils.US_ASCII));
}
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
if (deleteDestination) {
deleteIfExists(to);
}
if (!from.renameTo(to)) {
throw new IOException();
}
}
/**
- Returns a snapshot of the entry named {@code key}, or null if it doesn’t
- exist is not currently readable. If a value is returned, it is moved to
- the head of the LRU queue.
*/
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
InputStream[] ins = new InputStream[valueCount];
try {
for (int i = 0; i < valueCount; i++) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// A file must have been deleted manually!
for (int i = 0; i < valueCount; i++) {
if (ins[i] != null) {
CacheUtils.closeQuietly(ins[i]);
} else {
break;
}
}
return null;
}
redundantOpCount++;
journalWriter.append(READ + ' ' + key + '\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
}
/**
- Returns an editor for the entry named {@code key}, or null if another
- edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;
}
/** Returns the directory where this cache stores its data. */
public File getDirectory() {
return directory;
}
/**
- Returns the maximum number of bytes that this cache should use to store
- its data.
*/
public synchronized long getMaxSize() {
return maxSize;
}
/**
- Changes the maximum number of bytes the cache can store and queues a job
- to trim the existing store, if necessary.
*/
public synchronized void setMaxSize(long maxSize) {
this.maxSize = maxSize;
executorService.submit(cleanupCallable);
}
/**
- Returns the number of bytes currently being used to store the values in
- this cache. This may be greater than the max size if a background
- deletion is pending.
*/
public synchronized long size() {
return size;
}
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
// If this edit is creating the entry for the first time, every index must have a value.
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
}
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
/**
- We only rebuild the journal when it will halve the size of the journal
- and eliminate at least 2000 ops.
*/
private boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold //
&& redundantOpCount >= lruEntries.size();
}
/**
- Drops the entry for {@code key} if it exists and can be removed. Entries
- actively being edited cannot be removed.
- @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/** Returns true if this cache has been closed. */
public synchronized boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException(“cache is closed”);
}
}
/** Force buffered operations to the filesystem. */
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}
/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
/**
- Closes the cache and deletes all of its stored values. This will delete
- all files in the cache directory including files that weren’t created by
- the cache.
*/
public void delete() throws IOException {
close();
CacheUtils.deleteContents(directory);
}
private void validateKey(String key) {
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
if (!matcher.matches()) {
throw new IllegalArgumentException("keys must match regex "
+ STRING_KEY_PATTERN + “: “” + key + “””);
}
}
private static String inputStreamToString(InputStream in) throws IOException {
return CacheUtils.readFully(new InputStreamReader(in, CacheUtils.UTF_8));
}
/** A snapshot of the values for an entry. */
public final class Snapshot implements Closeable {
private final String key;
private final long sequenceNumber;
private final InputStream[] ins;
private final long[] lengths;
private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
this.key = key;
this.sequenceNumber = sequenceNumber;
this.ins = ins;
this.lengths = lengths;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public Editor edit() throws IOException {
return DiskLruCache.this.edit(key, sequenceNumber);
}
/** Returns the unbuffered stream with the value for {@code index}. */
public InputStream getInputStream(int index) {
return ins[index];
}
/** Returns the string value for {@code index}. */
public String getString(int index) throws IOException {
return inputStreamToString(getInputStream(index));
}
/** Returns the byte length of the value for {@code index}. */
public long getLength(int index) {
return lengths[index];
}
public void close() {
for (InputStream in : ins) {
CacheUtils.closeQuietly(in);
}
}
}
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
@Override
public void write(int b) throws IOException {
// Eat all writes silently. Nom nom.
}
};
/** Edits the values for an entry. */
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public InputStream newInputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
return null;
}
try {
return new FileInputStream(entry.getCleanFile(index));
} catch (FileNotFoundException e) {
return null;
}
}
}
/**
* Returns the last committed value as a string, or null if no value
* has been committed.
*/
public String getString(int index) throws IOException {
InputStream in = newInputStream(index);
return in != null ? inputStreamToString(in) : null;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public OutputStream newOutputStream(int index) throws IOException {
if (index < 0 || index >= valueCount) {
throw new IllegalArgumentException("Expected index " + index + " to "
+ "be greater than 0 and less than the maximum value count "
+ "of " + valueCount);
}
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e) {
// Attempt to recreate the cache directory.
directory.mkdirs();
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e2) {
// We are unable to recover. Silently eat the writes.
return NULL_OUTPUT_STREAM;
}
}
return new FaultHidingOutputStream(outputStream);
}
}
/** Sets the value at {@code index} to {@code value}. */
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
writer = new OutputStreamWriter(newOutputStream(index), CacheUtils.UTF_8);
writer.write(value);
} finally {
CacheUtils.closeQuietly(writer);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {
completeEdit(this, false);
}
public void abortUnlessCommitted() {
if (!committed) {
try {
abort();
} catch (IOException ignored) {
}
}
}
private class FaultHidingOutputStream extends FilterOutputStream {
private FaultHidingOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int oneByte) {
try {
out.write(oneByte);
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void write(byte[] buffer, int offset, int length) {
try {
out.write(buffer, offset, length);
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void close() {
try {
out.close();
} catch (IOException e) {
hasErrors = true;
}
}
@Override
public void flush() {
try {
out.flush();
} catch (IOException e) {
hasErrors = true;
}
}
}
}
private final class Entry {
private final String key;
/** Lengths of this entry's files. */
private final long[] lengths;
/** True if this entry has ever been published. */
private boolean readable;
/** The ongoing edit or null if this entry is not being edited. */
private Editor currentEditor;
/** The sequence number of the most recently committed edit to this entry. */
private long sequenceNumber;
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
}
public String getLengths() throws IOException {
StringBuilder result = new StringBuilder();
for (long size : lengths) {
result.append(' ').append(size);
}
return result.toString();
}
/** Set lengths using decimal numbers like "10123". */
private void setLengths(String[] strings) throws IOException {
if (strings.length != valueCount) {
throw invalidLengths(strings);
}
try {
for (int i = 0; i < strings.length; i++) {
lengths[i] = Long.parseLong(strings[i]);
}
} catch (NumberFormatException e) {
throw invalidLengths(strings);
}
}
private IOException invalidLengths(String[] strings) throws IOException {
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
}
public File getCleanFile(int i) {
return new File(directory, key + "." + i);
}
public File getDirtyFile(int i) {
return new File(directory, key + "." + i + ".tmp");
}
}
}
StrictLineReader.java
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
/**
- Buffers input from an {@link InputStream} for reading lines.
- <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
- with “\n” or “\r\n”. End of input is reported by throwing {@code EOFException}. Unterminated
- line at end of input is invalid and will be ignored, the caller may use {@code
- hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
- <p>This class is intended for reading input that strictly consists of lines, such as line-based
- cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
- with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
- end-of-input reporting and a more restrictive definition of a line.
- <p>This class supports only charsets that encode ‘\r’ and ‘\n’ as a single byte with value 13
- and 10, respectively, and the representation of no other character contains these values.
- We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
- The default charset is US_ASCII.
*/
class StrictLineReader implements Closeable {
private static final byte CR = (byte) ‘\r’;
private static final byte LF = (byte) ‘\n’;
private final InputStream in;
private final Charset charset;
/*
- Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
- and the data in the range [pos, end) is buffered for reading. At end of input, if there is
- an unterminated line, we set end == -1, otherwise end == pos. If the underlying
- {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
*/
private byte[] buf;
private int pos;
private int end;
/**
- Constructs a new {@code LineReader} with the specified charset and the default capacity.
- @param in the {@code InputStream} to read data from.
- @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
- supported.
- @throws NullPointerException if {@code in} or {@code charset} is null.
- @throws IllegalArgumentException if the specified charset is not supported.
*/
public StrictLineReader(InputStream in, Charset charset) {
this(in, 8192, charset);
}
/**
- Constructs a new {@code LineReader} with the specified capacity and charset.
- @param in the {@code InputStream} to read data from.
- @param capacity the capacity of the buffer.
- @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
- supported.
- @throws NullPointerException if {@code in} or {@code charset} is null.
- @throws IllegalArgumentException if {@code capacity} is negative or zero
- or the specified charset is not supported.
*/
public StrictLineReader(InputStream in, int capacity, Charset charset) {
if (in == null || charset == null) {
throw new NullPointerException();
}
if (capacity < 0) {
throw new IllegalArgumentException(“capacity <= 0”);
}
if (!(charset.equals(CacheUtils.US_ASCII))) {
throw new IllegalArgumentException(“Unsupported encoding”);
}
this.in = in;
this.charset = charset;
buf = new byte[capacity];
}
/**
- Closes the reader by closing the underlying {@code InputStream} and
- marking this reader as closed.
- @throws IOException for errors when closing the underlying {@code InputStream}.
*/
public void close() throws IOException {
synchronized (in) {
if (buf != null) {
buf = null;
in.close();
}
}
}
/**
-
Reads the next line. A line ends with {@code “\n”} or {@code “\r\n”},
-
this end of line marker is not included in the result.
-
@return the next line from the input.
-
@throws IOException for underlying {@code InputStream} errors.
-
@throws EOFException for the end of source stream.
*/
public String readLine() throws IOException {
synchronized (in) {
if (buf == null) {
throw new IOException(“LineReader is closed”);
}// Read more data if we are at the end of the buffered data.
// Though it’s an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
if (pos >= end) {
fillBuf();
}
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
String res = new String(buf, pos, lineEnd - pos, charset.name());
pos = i + 1;
return res;
}
}// Let’s anticipate up to 80 characters on top of those already read.
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
@Override
public String toString() {
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
try {
return new String(buf, 0, length, charset.name());
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // Since we control the charset this will never happen.
}
}
};while (true) {
out.write(buf, pos, end - pos);
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end = -1;
fillBuf();
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
if (i != pos) {
out.write(buf, pos, i - pos);
}
pos = i + 1;
return out.toString();
}
}
}
}
}
public boolean hasUnterminatedLine() {
return end == -1;
}
/**
- Reads new input data into the buffer. Call only with pos == end or end == -1,
- depending on the desired outcome if the function throws.
*/
private void fillBuf() throws IOException {
int result = in.read(buf, 0, buf.length);
if (result == -1) {
throw new EOFException();
}
pos = 0;
end = result;
}
}
CacheUtils.java
package com.eebbk.bbkmiddlemarket.modulecommon.net.cache;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
final class CacheUtils
{
static final Charset US_ASCII = Charset.forName(“US-ASCII”);
static final Charset UTF_8 = Charset.forName(“UTF-8”);
private CacheUtils()
{
}
static String readFully(Reader reader) throws IOException
{
try
{
StringWriter writer = new StringWriter();
char[] buffer = new char[1024];
int count;
while ((count = reader.read(buffer)) != -1)
{
writer.write(buffer, 0, count);
}
return writer.toString();
} finally
{
reader.close();
}
}
/**
* Deletes the contents of {@code dir}. Throws an IOException if any file
* could not be deleted, or if {@code dir} is not a readable directory.
*/
static void deleteContents(File dir) throws IOException
{
File[] files = dir.listFiles();
if (files == null)
{
throw new IOException("not a readable directory: " + dir);
}
for (File file : files)
{
if (file.isDirectory())
{
deleteContents(file);
}
if (!file.delete())
{
throw new IOException("failed to delete file: " + file);
}
}
}
static void closeQuietly(/*Auto*/Closeable closeable)
{
if (closeable != null)
{
try
{
closeable.close();
} catch (RuntimeException rethrown)
{
throw rethrown;
} catch (Exception ignored)
{
}
}
}
public static int getAppVersion(Context context)
{
try
{
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e)
{
e.printStackTrace();
}
return 1;
}
public static 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;
}
public static 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 static byte[] bitmap2Bytes(Bitmap bm)
{
if (bm == null)
{
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
public static Bitmap bytes2Bitmap(byte[] bytes)
{
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
/**
* Drawable → Bitmap
*/
public static Bitmap drawable2Bitmap(Drawable drawable)
{
if (drawable == null)
{
return null;
}
// 取 drawable 的长宽
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
// 取 drawable 的颜色格式
Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
// 建立对应 bitmap
Bitmap bitmap = Bitmap.createBitmap(w, h, config);
// 建立对应 bitmap 的画布
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
// 把 drawable 内容画到画布中
drawable.draw(canvas);
return bitmap;
}
/*
* Bitmap → Drawable
*/
@SuppressWarnings("deprecation")
public static Drawable bitmap2Drawable(Bitmap bm)
{
if (bm == null)
{
return null;
}
BitmapDrawable bd = new BitmapDrawable(bm);
bd.setTargetDensity(bm.getDensity());
return new BitmapDrawable(bm);
}
}