今天看了朋友一個項目用到了Hawk,然後寫了這邊文章
一、瞭解一下概念
Android Hawk數據庫github開源項目 Hawk是一個非常便捷的數據庫.操作數據庫只需一行代碼,能存任何數據類型.
相信大家應該很熟悉SharedPreferences。它是一種輕量級的存儲簡單配置信息的存儲機制,以key-value的形式保存數據。 本文介紹就是基於SharedPreferences的的存儲框架,是由Orhan Obut大神寫的, 它是Secure,simple key-value storage for Android。安全簡單的Android存儲工具。
這裏附上Orhan Obut大神的Github 地址 https://github.com/orhanobut
**
二、用法
**
1.添加依賴
compile 'com.orhanobut:hawk:2.0.1'
2.初始化
Hawk.init(this).build();
這兩步就可以正常使用了,那就看一下API吧
<T> :Saves any type including any collection, primitive values or custom objects
任何數據類型()
存數據
public static <T> boolean put(String key, T value) {
return hawkFacade.put(key, value);
}
取數據
public static <T> T get(String key) {
return hawkFacade.get(key);
}
總數量
public static long count() {
return hawkFacade.count();
}
刪除全部數據
public static boolean deleteAll() {
return hawkFacade.deleteAll();
}
刪除某個數據
public static boolean delete(String key) {
return hawkFacade.delete(key);
}
是否包含有某個的數據
public static boolean contains(String key) {
return hawkFacade.contains(key);
}
來驗證如果準備使用。如果正確地初始化和建造@return真實。否則錯誤。
public static boolean isBuilt() {
return hawkFacade.isBuilt();
}
public static void destroy() {
hawkFacade.destroy();
}
下面是在官網圖片 讓我們看看它是怎麼工作的
看圖片一目瞭然左邊的PUT方法中,是把T value 存儲到Disk當中。大致流程是,先將數據進行toString的轉換,接下來是加密,然後進行序列化,最後是存儲,用的就是SharePreference的存儲。獲取數據時就正好逆過來啦。
三、源碼解析開始啦
先看看初始化的代碼
Hawk.init(this).build();
/**
* This will init the hawk without password protection.
*
* @param context is used to instantiate context based objects.
* ApplicationContext will be used
*/
public static HawkBuilder init(Context context) {
HawkUtils.checkNull("Context", context);
hawkFacade = null;
return new HawkBuilder(context);
}
static void build(HawkBuilder hawkBuilder) {
hawkFacade = new DefaultHawkFacade(hawkBuilder);
}
初始化中創建了HawkBuilder對象,下面如下代碼
public HawkBuilder(Context context) {
HawkUtils.checkNull("Context", context);
this.context = context.getApplicationContext();
}
public void build() {
Hawk.build(this);
}
其中也用到了 HawkUtils,看一下代碼
這個工具類就是對數據的判空處理
final class HawkUtils {
private HawkUtils() {
//no instance
}
public static void checkNull(String message, Object value) {
if (value == null) {
throw new NullPointerException(message + " should not be null");
}
}
public static void checkNullOrEmpty(String message, String value) {
if (isEmpty(value)) {
throw new NullPointerException(message + " should not be null or empty");
}
}
public static boolean isEmpty(String text) {
return text == null || text.trim().length() == 0;
}
}
上面有個最重的沒有看完 DefaultHawkFacade
先看一下源碼吧
public DefaultHawkFacade(HawkBuilder builder) {
encryption = builder.getEncryption();
storage = builder.getStorage();
converter = builder.getConverter();
serializer = builder.getSerializer();
logInterceptor = builder.getLogInterceptor();
logInterceptor.onLog("Hawk.init -> Encryption : " + encryption.getClass().getSimpleName());
}
@Override public <T> boolean put(String key, T value) {
// Validate
HawkUtils.checkNull("Key", key);
log("Hawk.put -> key: " + key + ", value: " + value);
// If the value is null, delete it
if (value == null) {
log("Hawk.put -> Value is null. Any existing value will be deleted with the given key");
return delete(key);
}
// 1. Convert to text
String plainText = converter.toString(value);
log("Hawk.put -> Converted to " + plainText);
if (plainText == null) {
log("Hawk.put -> Converter failed");
return false;
}
// 2. Encrypt the text
String cipherText = null;
try {
cipherText = encryption.encrypt(key, plainText);
log("Hawk.put -> Encrypted to " + cipherText);
} catch (Exception e) {
e.printStackTrace();
}
if (cipherText == null) {
log("Hawk.put -> Encryption failed");
return false;
}
// 3. Serialize the given object along with the cipher text
String serializedText = serializer.serialize(cipherText, value);
log("Hawk.put -> Serialized to" + serializedText);
if (serializedText == null) {
log("Hawk.put -> Serialization failed");
return false;
}
// 4. Save to the storage
if (storage.put(key, serializedText)) {
log("Hawk.put -> Stored successfully");
return true;
} else {
log("Hawk.put -> Store operation failed");
return false;
}
}
@Override public <T> T get(String key) {
log("Hawk.get -> key: " + key);
if (key == null) {
log("Hawk.get -> null key, returning null value ");
return null;
}
// 1. Get serialized text from the storage
String serializedText = storage.get(key);
log("Hawk.get -> Fetched from storage : " + serializedText);
if (serializedText == null) {
log("Hawk.get -> Fetching from storage failed");
return null;
}
// 2. Deserialize
DataInfo dataInfo = serializer.deserialize(serializedText);
log("Hawk.get -> Deserialized");
if (dataInfo == null) {
log("Hawk.get -> Deserialization failed");
return null;
}
// 3. Decrypt
String plainText = null;
try {
plainText = encryption.decrypt(key, dataInfo.cipherText);
log("Hawk.get -> Decrypted to : " + plainText);
} catch (Exception e) {
log("Hawk.get -> Decrypt failed: " + e.getMessage());
}
if (plainText == null) {
log("Hawk.get -> Decrypt failed");
return null;
}
// 4. Convert the text to original data along with original type
T result = null;
try {
result = converter.fromString(plainText, dataInfo);
log("Hawk.get -> Converted to : " + result);
} catch (Exception e) {
log("Hawk.get -> Converter failed");
}
return result;
}
@Override public <T> T get(String key, T defaultValue) {
T t = get(key);
if (t == null) return defaultValue;
return t;
}
@Override public long count() {
return storage.count();
}
@Override public boolean deleteAll() {
return storage.deleteAll();
}
@Override public boolean delete(String key) {
return storage.delete(key);
}
@Override public boolean contains(String key) {
return storage.contains(key);
}
@Override public boolean isBuilt() {
return true;
}
@Override public void destroy() {
}
private void log(String message) {
logInterceptor.onLog(message);
}
}
解析一下DefaultHawkFacade
我們存數據之前應該怎麼做??跟買東西一個道理先檢查一下有沒有開封,有沒有用過。它這也是
- 驗證,如果是空的刪除
- 轉換 數據轉成字符串 HawkConverter看一下源碼
/**
* Concrete implementation of encoding and decoding.
* List types will be encoded/decoded by parser
* Serializable types will be encoded/decoded object stream
* Not serializable objects will be encoded/decoded by parser
*/
編碼和解碼的具體實現。列表類型將被解析器可序列化的編碼/解碼類型將編碼/解碼對象流可序列化的對象不會被解析器編碼/解碼;
final class HawkConverter implements Converter {
private final Parser parser;
public HawkConverter(Parser parser) {
if (parser == null) {
throw new NullPointerException("Parser should not be null");
}
this.parser = parser;
}
@Override public <T> String toString(T value) {
if (value == null) {
return null;
}
return parser.toJson(value);
}
@SuppressWarnings("unchecked")
@Override public <T> T fromString(String value, DataInfo info) throws Exception {
if (value == null) {
return null;
}
HawkUtils.checkNull("data info", info);
Class<?> keyType = info.keyClazz;
Class<?> valueType = info.valueClazz;
switch (info.dataType) {
case DataInfo.TYPE_OBJECT:
return toObject(value, keyType);
case DataInfo.TYPE_LIST:
return toList(value, keyType);
case DataInfo.TYPE_MAP:
return toMap(value, keyType, valueType);
case DataInfo.TYPE_SET:
return toSet(value, keyType);
default:
return null;
}
}
private <T> T toObject(String json, Class<?> type) throws Exception {
return parser.fromJson(json, type);
}
@SuppressWarnings("unchecked")
private <T> T toList(String json, Class<?> type) throws Exception {
if (type == null) {
return (T) new ArrayList<>();
}
List<T> list = parser.fromJson(
json,
new TypeToken<List<T>>() {
}.getType()
);
int size = list.size();
for (int i = 0; i < size; i++) {
list.set(i, (T) parser.fromJson(parser.toJson(list.get(i)), type));
}
return (T) list;
}
@SuppressWarnings("unchecked")
private <T> T toSet(String json, Class<?> type) throws Exception {
Set<T> resultSet = new HashSet<>();
if (type == null) {
return (T) resultSet;
}
Set<T> set = parser.fromJson(json, new TypeToken<Set<T>>() {
}.getType());
for (T t : set) {
String valueJson = parser.toJson(t);
T value = parser.fromJson(valueJson, type);
resultSet.add(value);
}
return (T) resultSet;
}
@SuppressWarnings("unchecked")
private <K, V, T> T toMap(String json, Class<?> keyType, Class<?> valueType) throws Exception {
Map<K, V> resultMap = new HashMap<>();
if (keyType == null || valueType == null) {
return (T) resultMap;
}
Map<K, V> map = parser.fromJson(json, new TypeToken<Map<K, V>>() {
}.getType());
for (Map.Entry<K, V> entry : map.entrySet()) {
String keyJson = parser.toJson(entry.getKey());
K k = parser.fromJson(keyJson, keyType);
String valueJson = parser.toJson(entry.getValue());
V v = parser.fromJson(valueJson, valueType);
resultMap.put(k, v);
}
return (T) resultMap;
}
- 加密 這裏用到的是臉書的加密
先是成是字節數組,然後進行Base64編碼得到字符串數據。
class ConcealEncryption implements Encryption {
private final Crypto crypto;
public ConcealEncryption(Context context) {
SharedPrefsBackedKeyChain keyChain = new SharedPrefsBackedKeyChain(context, CryptoConfig.KEY_256);
crypto = AndroidConceal.get().createDefaultCrypto(keyChain);
}
@Override public boolean init() {
return crypto.isAvailable();
}
@Override public String encrypt(String key, String plainText) throws Exception {
Entity entity = Entity.create(key);
byte[] bytes = crypto.encrypt(plainText.getBytes(), entity);
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
@Override public String decrypt(String key, String cipherText) throws Exception {
Entity entity = Entity.create(key);
byte[] decodedBytes = Base64.decode(cipherText, Base64.NO_WRAP);
byte[] bytes = crypto.decrypt(decodedBytes, entity);
return new String(bytes);
}
4.序列化 HawkSerializer
運用反射獲取原數據的數據類型。是List,map,set還是對象,根據不同類型保存不同數據。返回字符串類型的值,這個值是原始key的類型,原始數據的類型,數據類型和密文的拼接,這樣就可以存儲啦。
class HawkSerializer implements Serializer {
private static final char DELIMITER = '@';
private static final String INFO_DELIMITER = "#";
private static final char NEW_VERSION = 'V';
private final LogInterceptor logInterceptor;
HawkSerializer(LogInterceptor logInterceptor) {
this.logInterceptor = logInterceptor;
}
@Override public <T> String serialize(String cipherText, T originalGivenValue) {
HawkUtils.checkNullOrEmpty("Cipher text", cipherText);
HawkUtils.checkNull("Value", originalGivenValue);
String keyClassName = "";
String valueClassName = "";
char dataType;
if (List.class.isAssignableFrom(originalGivenValue.getClass())) {
List<?> list = (List<?>) originalGivenValue;
if (!list.isEmpty()) {
keyClassName = list.get(0).getClass().getName();
}
dataType = DataInfo.TYPE_LIST;
} else if (Map.class.isAssignableFrom(originalGivenValue.getClass())) {
dataType = DataInfo.TYPE_MAP;
Map<?, ?> map = (Map) originalGivenValue;
if (!map.isEmpty()) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
keyClassName = entry.getKey().getClass().getName();
valueClassName = entry.getValue().getClass().getName();
break;
}
}
} else if (Set.class.isAssignableFrom(originalGivenValue.getClass())) {
Set<?> set = (Set<?>) originalGivenValue;
if (!set.isEmpty()) {
Iterator<?> iterator = set.iterator();
if (iterator.hasNext()) {
keyClassName = iterator.next().getClass().getName();
}
}
dataType = DataInfo.TYPE_SET;
} else {
dataType = DataInfo.TYPE_OBJECT;
keyClassName = originalGivenValue.getClass().getName();
}
return keyClassName + INFO_DELIMITER +
valueClassName + INFO_DELIMITER +
dataType + NEW_VERSION + DELIMITER +
cipherText;
}
@Override public DataInfo deserialize(String serializedText) {
String[] infos = serializedText.split(INFO_DELIMITER);
char type = infos[2].charAt(0);
// if it is collection, no need to create the class object
Class<?> keyClazz = null;
String firstElement = infos[0];
if (firstElement != null && firstElement.length() != 0) {
try {
keyClazz = Class.forName(firstElement);
} catch (ClassNotFoundException e) {
logInterceptor.onLog("HawkSerializer -> " + e.getMessage());
}
}
Class<?> valueClazz = null;
String secondElement = infos[1];
if (secondElement != null && secondElement.length() != 0) {
try {
valueClazz = Class.forName(secondElement);
} catch (ClassNotFoundException e) {
logInterceptor.onLog("HawkSerializer -> " + e.getMessage());
}
}
終於到最後一步了,那就是存數據了
Hawk給出的默認存儲實現是SharedPreferenceStorage。從類名都可以看出,其實就是用SharedPreferences來存儲數據。這裏就不多說啦。畢竟SharedPreferences用法很簡單。
final class SharedPreferencesStorage implements Storage {
private final SharedPreferences preferences;
SharedPreferencesStorage(Context context, String tag) {
preferences = context.getSharedPreferences(tag, Context.MODE_PRIVATE);
}
SharedPreferencesStorage(SharedPreferences preferences) {
this.preferences = preferences;
}
@Override public <T> boolean put(String key, T value) {
HawkUtils.checkNull("key", key);
return getEditor().putString(key, String.valueOf(value)).commit();
}
@SuppressWarnings("unchecked")
@Override public <T> T get(String key) {
return (T) preferences.getString(key, null);
}
@Override public boolean delete(String key) {
return getEditor().remove(key).commit();
}
@Override public boolean contains(String key) {
return preferences.contains(key);
}
@Override public boolean deleteAll() {
return getEditor().clear().commit();
}
@Override public long count() {
return preferences.getAll().size();
}
private SharedPreferences.Editor getEditor() {
return preferences.edit();
}
}
**存儲數據put的源碼就分析到這裏,總結下其實就是開始那幅圖顯示的,把數據轉換成字符串,加密,序列化,存儲四個步驟搞定。獲取數據get就是put過程反過來,沒什麼好說的了。至於其他的方法,delete(),deleteAll(),contains(),count()其實都是運用SharedPreferences在打交道。
Hawk存儲的源碼已經很清晰了。在分析中我們也提到了,有些實現是已經給出的默認實現,其實我們也可以根據需求來定義響應的接口實現。**
Hawk.init(context)
.setEncryption(new NoEncryption())
.setLogInterceptor(new MyLogInterceptor())
.setConverter(new MyConverter())
.setParser(new MyParser())
.setStorage(new MyStorage())
.build();
HawkBuilder中還有一些API是供開發者自定義的
public HawkBuilder setStorage(Storage storage) {
this.cryptoStorage = storage;
return this;
}
public HawkBuilder setParser(Parser parser) {
this.parser = parser;
return this;
}
public HawkBuilder setSerializer(Serializer serializer) {
this.serializer = serializer;
return this;
}
public HawkBuilder setLogInterceptor(LogInterceptor logInterceptor) {
this.logInterceptor = logInterceptor;
return this;
}
public HawkBuilder setConverter(Converter converter) {
this.converter = converter;
return this;
}
public HawkBuilder setEncryption(Encryption encryption) {
this.encryption = encryption;
return this;
}
看完代碼 也正如Orhan Obut大神說的
Secure, simple key-value storage for android 安全、簡單的Android存儲工具
有什麼寫的不好的地方可以在評論區評論