Hawk源码解析

项目地址:https://github.com/orhanobut/hawk

处理流程

一、初探

Hawk是一种安全的、简单的做key、value存储的库。

先来看看Hawk的初始化

1
Hawk.init(this).build();

1.1 构造者模式

走进初始化,和大部分lib一样使用了构造者模式做些配置,其中先看看 HawkBuilder.java 所需要构造的变量:

1
2
3
4
5
6
7
private Context context;
private Storage cryptoStorage;
private Converter converter;
private Parser parser;
private Encryption encryption;
private Serializer serializer;
private LogInterceptor logInterceptor;

其实这些变量有些在第一张图上就诠释了,这些都是接口,具体的实现都可以自己定制。

之后我会具体的解释Put和Get操作,其中对整个图的过程以及这些变量会有对应的解释,总之,这几个变量是整个Hawk的核心。

1.2 外观模式

DefaultHawkFacade.java 这个类相当重要,是Hawk真正处理数据操作的,利用建造者模式创建好HawkBuilder然后作为参数传给DefaultHawkFacade。

EmptyHawkFacade.java 这是个空的类,如果没有调用Hawk.build()就用抛出异常 Hawk is not built.Please call build() and wait the initialisation finishes.

二、 put操作

1
2
3
public static <T> boolean put(String key, T value) {
return hawkFacade.put(key, value);
}

前面说了,其实是 DefaultHawkFacade 在处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Override public <T> boolean put(String key, T value) {
// 检查下key是不是为null
HawkUtils.checkNull("Key", key);
log("Hawk.put -> key: " + key + ", value: " + value);
// 如果value是null,则直接删除,delete()见2.1
if (value == null) {
log("Hawk.put -> Value is null. Any existing value will be deleted with the given key");
return delete(key);
}
// 1. 将value转成字符串,converter.toString()见2.2
String plainText = converter.toString(value);
log("Hawk.put -> Converted to " + plainText);
if (plainText == null) {
log("Hawk.put -> Converter failed");
return false;
}
// 2. 加密文本,encryption.encrypt()见2.3
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. 将value和上一步加密后的文本重新串成一个新的文本,这一步相当重要,见2.4
String serializedText = serializer.serialize(cipherText, value);
log("Hawk.put -> Serialized to" + serializedText);
if (serializedText == null) {
log("Hawk.put -> Serialization failed");
return false;
}
// 4. 存储
if (storage.put(key, serializedText)) {
log("Hawk.put -> Stored successfully");
return true;
} else {
log("Hawk.put -> Store operation failed");
return false;
}
}

2.1 delete()

删除操作最后落在这 SharedPreferencesStorage.java

1
2
3
4
5
6
7
8
9
private final SharedPreferences preferences;
@Override public boolean delete(String key) {
return getEditor().remove(key).commit();
}
private SharedPreferences.Editor getEditor() {
return preferences.edit();
}

可以看到,Hawk最后存储的方式其实还是用的SharedPreferences。

2.2 converter.toString()

转换操作在 HawkConverter.java 里:

1
2
3
4
5
6
7
8
private final Parser parser;
@Override public <T> String toString(T value) {
if (value == null) {
return null;
}
return parser.toJson(value);
}

parser是个什么鬼?来看看 GsonParser.java

1
2
3
4
5
private final Gson gson;
@Override public String toJson(Object body) {
return gson.toJson(body);
}

原来Hawk是直接将存储的value通过gson反序列化成String。

2.3 encryption.encrypt()

加密操作在 ConcealEncryption.java 里:

1
2
3
4
5
6
7
private final Crypto crypto;
@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);
}

Crypto 是facebook开源的高效加密验证方案conceal,Hawk将之前反序列化生成的文本经过conceal加密后再Base64一下返回加密后的文本。

2.4 串行操作

做这一步是为了解决泛型擦除后,想通过gson反序列化回原来的类型对象,那么这里就会通过自己的手段记录一下类型。

相关操作在 HawkSerializer.java 里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
private static final char DELIMITER = '@';
private static final String INFO_DELIMITER = "#";
private static final char NEW_VERSION = 'V';
@Override public <T> String serialize(String cipherText, T originalGivenValue) {
// 检查参数不为null
HawkUtils.checkNullOrEmpty("Cipher text", cipherText);
HawkUtils.checkNull("Value", originalGivenValue);
String keyClassName = "";
String valueClassName = "";
char dataType;
// 如果是value是List类型
if (List.class.isAssignableFrom(originalGivenValue.getClass())) {
List<?> list = (List<?>) originalGivenValue;
if (!list.isEmpty()) {
// keyClassName取第0个元素的类型
keyClassName = list.get(0).getClass().getName();
}
// dataType设为List
dataType = DataInfo.TYPE_LIST;
}
// 如果是value是Map类型
else if (Map.class.isAssignableFrom(originalGivenValue.getClass())) {
// dataType设为Map
dataType = DataInfo.TYPE_MAP;
Map<?, ?> map = (Map) originalGivenValue;
if (!map.isEmpty()) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
// keyClassName取第0个元素的key的类型
// valueClassName取第0个元素的value的类型
keyClassName = entry.getKey().getClass().getName();
valueClassName = entry.getValue().getClass().getName();
break;
}
}
}
// 如果是value是Set类型
else if (Set.class.isAssignableFrom(originalGivenValue.getClass())) {
Set<?> set = (Set<?>) originalGivenValue;
if (!set.isEmpty()) {
Iterator<?> iterator = set.iterator();
if (iterator.hasNext()) {
// keyClassName取第0个元素的类型
keyClassName = iterator.next().getClass().getName();
}
}
// dataType设为Set
dataType = DataInfo.TYPE_SET;
} else {
// dataType设为Object
dataType = DataInfo.TYPE_OBJECT;
// keyClassName取value的类型
keyClassName = originalGivenValue.getClass().getName();
}
//用上述统计到的信息拼接串行成一个新的String,这个结构会被之后deserialize中用到
return keyClassName + INFO_DELIMITER +
valueClassName + INFO_DELIMITER +
dataType + NEW_VERSION + DELIMITER +
cipherText;
}

可以看到,Hawk对value的类型在这里做了记录,以便之后反序列化时用到。

三、get操作

因为put和get是相对的,所以理解put后去看get那就是相当容易了。

1
2
3
public static <T> T get(String key) {
return hawkFacade.get(key);
}

具体操作还是在 DefaultHawkFacade

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@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. 先通过SharedPreferences取得之前存的文本
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. 将之前手动拼接的String进行解析,返回DataInfo,见3.1
DataInfo dataInfo = serializer.deserialize(serializedText);
log("Hawk.get -> Deserialized");
if (dataInfo == null) {
log("Hawk.get -> Deserialization failed");
return null;
}
// 3. 解密
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. 反序列化为原对象,见3.2
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;
}

3.1 解析串行文本

还是 HawkSerializer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override public DataInfo deserialize(String serializedText) {
// 先粘贴下之前拼接的规则
// keyClassName + INFO_DELIMITER + valueClassName + INFO_DELIMITER + dataType + NEW_VERSION + DELIMITER + cipherText
// 通过INFO_DELIMITER分割之前拼接的String
String[] infos = serializedText.split(INFO_DELIMITER);
// 获取之前存的 dataType
char type = infos[2].charAt(0);
// if it is collection, no need to create the class object
Class<?> keyClazz = null;
// 获取之前存的 keyClassName
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;
// 获取之前存的 valueClassName
String secondElement = infos[1];
if (secondElement != null && secondElement.length() != 0) {
try {
valueClazz = Class.forName(secondElement);
} catch (ClassNotFoundException e) {
logInterceptor.onLog("HawkSerializer -> " + e.getMessage());
}
}
// 获取之前存的 加密后的文本
String cipherText = getCipherText(infos[infos.length - 1]);
// 返回DataInfo对象
return new DataInfo(type, cipherText, keyClazz, valueClazz);
}

再来看看DataInfo对象:

1
2
3
4
5
6
7
8
9
10
11
final char dataType;
final String cipherText;
final Class keyClazz;
final Class valueClazz;
DataInfo(char dataType, String cipherText, Class keyClazz, Class valueClazz) {
this.cipherText = cipherText;
this.keyClazz = keyClazz;
this.valueClazz = valueClazz;
this.dataType = dataType;
}

总之,这一步就是把之前put时拼接存的信息又都解析出来了。

3.2 反序列化为原对象

3.1中所做的又都是为这一步做铺垫,这里是想拿到正确结果至关重要的一步,来看 HawkConverter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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;
}
}

以List为例,看看toList(value, keyType)做了啥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SuppressWarnings("unchecked")
private <T> T toList(String json, Class<?> type) throws Exception {
if (type == null) {
return (T) new ArrayList<>();
}
// 利用gson反序列化回来
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;
}

尾语

Hawk的代码写的很干净,流程中任何步骤都可以被开发人员定制,比如你如果觉得gson太慢,可以换成别的。