Android Hawk的源码解析,一款基于SharedPreferences的存储框架

转载请标注:http://blog.csdn.net/friendlychen/article/details/76218033

一、概念

SharedPreferences的使用大家应该非常熟悉啦。这是一种轻量级的存储简单配置信息的存储机制,以key-value的形式保存数据。这里介绍一款基于SharedPreferences的的存储框架,是由Android开发大神Orhan Obut开源维护的,有名的日志框架logger就是出自他之手。使用非常简单,看看它在github上的介绍:Secure,simple key-value storage for Android。安全简单的Android存储工具。连介绍也是简单明了,那我们开看看它的用法吧。

二、用法

1.添加依赖:

compile 'com.orhanobut:hawk:2.0.1'

2、初始化:

Hawk.init(context).build();

上面两个步骤基本是框架类必须的了。接下来就可以使用啦。
3、API:
保存数据:

Hawk.put(key , T );

获取数据:

T value= Hawk.get(key);

删除数据:

Hawk.delete(key);

数据是否已经保存:

Hawk.contains(key);

检查已经存储的数据总数:

Hawk.count();

删除全部数据:

Hawk.deleteAll();

有没有很简单粗暴,这么几个API已经足够应付我们需求了。这里的数据T可以是任意数据。看看官方的一句话:Save any type(Any objece,primitives,lists,sets,maps…)是不是很厉害了,因为源码已经帮我们做好了许多事。接下来从源码的角度来看看内部干了什么吧。先看一张图,所谓一图胜千言嘛。。
《Android Hawk的源码解析,一款基于SharedPreferences的存储框架》
这张图也很清晰的帮我们做了简单分析。左边的PUT方法中,是把T value 存储到Disk当中。大致流程是,先将数据进行toString的转换,接下来是加密,然后进行序列化,最后是存储,用的就是SharePreference的存储。获取数据时就正好逆过来啦。

三、源码解析

先看看初始化的代码:

Hawk.init(context).build();

这是Hawk中的代码:

  /** * 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);
  }

这里简单做了初始化,在init()中创建了HawkBuilder对象,下面是HawkBuilder中的代码。

  public HawkBuilder(Context context) {
    HawkUtils.checkNull("Context", context);

    this.context = context.getApplicationContext();
  }
    public void build() {
    Hawk.build(this);
  }

这里是在build()中创建了DefaultHawkFacade的对象。这样就把HawkBuilder和DefaultHawkFacade两个重要的类对象创建了,后面我们的操作都是围绕这两个类上。接下来就是存储数据啦。

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

我们在存储数据put的时候,调用的是DefaultHawkFacade中的put方法。

 @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;
    }
  }

这里就是这个框架的存储核心。put(key,value)方法中,先对key和value做非空判断,key是空,则在HawkUtils.checkNull()方法中判处空指针异常。value为空,说明没有数据存储,则删除。

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

第一步是用Converter接口中的toString方法把数据转换成字符串类型。Hawk里的Converter接口给出了默认具体实现类,HawkConverter。这里是用到了Gson解析,把数据转换成字符串数据。

  @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);
  }

第二步是加密。Hawk里的默认加密实现是ConcealEncryption中,用到的加密算法是Facebook的一种加密算法。先是成是字节数组,然后进行Base64编码得到字符串数据。


@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;
  }

第三步是序列化。序列化的默认实现是HawkSerializer
类,运用反射获取原数据的数据类型。是List,map,set还是对象,根据不同类型保存不同数据。返回字符串类型的值,这个值是原始key的类型,原始数据的类型,数据类型和密文的拼接,这样就可以存储啦。

  @Override public <T> boolean put(String key, T value) {
    HawkUtils.checkNull("key", key);
    return getEditor().putString(key, String.valueOf(value)).commit();
  }

最后是存储。Hawk给出的默认存储实现是SharedPreferenceStorage。从类名都可以看出,其实就是用SharedPreferences来存储数据。这里就不多说啦。毕竟SharedPreferences用法很简单。
存储数据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,供开发者自己定义。这里都是在HawkBuilder中设置的。

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;
  }

正如Hawk的介绍一样,简单安全。源码设计也非常简单清晰。不过有人可能说了。这真的非常简单呀。我也可以设计呀。Android大神设计出来的东西肯定有它优秀的一面呀。毕竟github上2.3k的star以及体现了它的价值呀。使用起来简单上手,源码思路清晰,代码简介,总之是一款不错的轻量级存储框架。
写的不好的地方,欢迎留言交流啦。。。

    原文作者:chendroid
    原文地址: https://blog.csdn.net/friendlychen/article/details/76218033
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞