1. 程式人生 > >Android Hawk資料庫的原始碼解析,Github開源專案,基於SharedPreferences的的儲存框架

Android Hawk資料庫的原始碼解析,Github開源專案,基於SharedPreferences的的儲存框架

今天看了朋友一個專案用到了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
我們存資料之前應該怎麼做??跟買東西一個道理先檢查一下有沒有開封,有沒有用過。它這也是

  1. 驗證,如果是空的刪除
  2. 轉換 資料轉成字串 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;
  }

  1. 加密 這裡用到的是臉書的加密
    先是成是位元組陣列,然後進行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儲存工具
有什麼寫的不好的地方可以在評論區評論