Hawk源码解析

作者: 神来一巴掌 | 来源:发表于2017-08-23 18:34 被阅读151次

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

    处理流程处理流程

    一、初探

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

    相关文章

      网友评论

        本文标题:Hawk源码解析

        本文链接:https://www.haomeiwen.com/subject/srdjdxtx.html