Hawk源码解析

1,902 阅读5分钟

项目地址:github.com/orhanobut/h…

处理流程

一、初探

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

先来看看Hawk的初始化

Hawk.init(this).build();

1.1 构造者模式

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

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操作

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

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

@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

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 里:

private final Parser parser;
@Override public <T> String toString(T value) {
  if (value == null) {
    return null;
  }
  return parser.toJson(value);
}

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

private final Gson gson;
@Override public String toJson(Object body) {
  return gson.toJson(body);
}

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

2.3 encryption.encrypt()

加密操作在 ConcealEncryption.java 里:

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 里:

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那就是相当容易了。

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

具体操作还是在 DefaultHawkFacade

@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

@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对象:

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

@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)做了啥:

@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太慢,可以换成别的。