优雅地使用SharedPreferences

Hannibal

一种简单优雅的方法来使用android SharePreference。github地址

在项目中使用Hannibai

  1. 如果您的项目使用 Gradle 构建, 只需要在您的build.gradle文件添加如下到 dependencies :

    compile 'com.kevin:hannibai:0.5.1'
    annotationProcessor 'com.kevin:hannibai-compiler:0.5.1'
  2. 引入JSON序列化

    由于可以保存对象甚至集合到SharePreference,根据项目使用引入具体转换器。

    1. Gson

      compile 'com.kevin:hannibai-converter-gson:0.2.6'
    2. Jackson

      compile 'com.kevin:hannibai-converter-jackson:0.2.6'
    3. FastJson

      compile 'com.kevin:hannibai-converter-fastjson:0.2.6'
  3. 这里仅仅实现了Gson、Jackson及FastJson的实现,后续会扩展,或者你也可以扩展。

简单使用

  1. 在Application 中初始化

    Hannibai.init(this);
    if (debug) {
        Hannibai.setDebug(true);
    }
    Hannibai.setConverterFactory(GsonConverterFactory.create());
  2. 创建一个类,使用SharePreference进行注解

    @SharePreference
    public class AppPreference {
    }
  3. 加入一个成员变量

    这里使用public修饰,当然你也可以使用privateprotected或者不写,任何一种姿势都可以。

    @SharePreference
    public class AppPreference {
        public String name;
    }
  4. Build —> Rebuild Project

    这个过程会自动生成一堆你觉得写起来很恶心的东西。

  5. 在代码中使用

     // 获取AppPreference操作类
    AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class);
    
    // 设置name到SharePreference
    preferenceHandle.setName("Kevin");
    // 从SharePreference中获取name
    String name = preferenceHandle.getName();
    Toast.makeText(this, "name = " + name, Toast.LENGTH_SHORT).show();

是不是跟简单的就完成了保存数据到SharePreference,已经从SharePreference读取数据,之前恶心的一堆东西已经替你偷偷生成而且藏起来啦~

进阶使用

  1. 初始化

    在进行初始化的时候有如下两个方法

    Hannibai.init(this);
    Hannibai.init(this, false);

    这两个方法的区别就是是否对数据进行加密,如果第二个参数为true则代表要进行加密存储。默认为true,这样可以减小文件的大小。

  2. 获取操作类

    有两种方式,一种是获取通用的,另一种是可以传入ID参数,为不同用户建立不同SharePreference文件。

    假设类为AppPreference:

    1. 通用

      AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class);
    2. 区分用户

      AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class, "ID_123");
  3. 生成的方法

    假设你的成员变量是name,那么会生成以下方法:

    1. 判断SharePreference中是否包含name

      @DefString("")
      public boolean containsName() {
          return Hannibai.contains1(mSharedPreferencesName, mId, "name", "");
      }
    2. SharePreference中获取name

      @DefString("")
      public String getName() {
          return Hannibai.get1(mSharedPreferencesName, mId, "name", "");
      }
    3. 设置name值到SharePreference

      @Apply
      public void setName(final String name) {
          Hannibai.set1(mSharedPreferencesName, mId, "name", -1L, false, name);
      }
    4. SharePreference中移除name

      @Apply
      public void removeName() {
          Hannibai.remove1(mSharedPreferencesName, mId, "name");
      }
    5. SharePreference中移除所有数据

      @Apply
      public void removeAll() {
          Hannibai.clear(mSharedPreferencesName, mId);
      }
  4. 支持的类型

    类型sample
    StringString name;
    intint age;
    IntegerInteger age;
    longlong timestamp;
    LongLong timestamp;
    floatfloat salary;
    FloatFloat salary;
    doubledouble salary;
    DoubleDouble salary;
    UserUser user;
    List<xxx>List userList;
    Map<xxx, xxx>Map

原理

原理比较简单,相信聪明的你早就想到啦。不就是编译时注解搞的鬼嘛,恭喜你答对了。

  1. 所有的数据都封装到BaseModel中然后转换为JSON存储字符串到“

    final class BaseModel<T> {
    
        public long createTime;
        public long updateTime;
        public long expireTime;
        public long expire;
        public T data;
    
        // ... ...
    
    }

    通过几个时间字段来标记过期信息。

  2. 仿照Retrofit定义的JSON转换接口

    由于大家项目中使用JSON转换工具存在差异,有人喜欢使用GSON,有同学觉得FastJson是速度最快的,也有同学感觉Jackson最优。这里都可以根据自己的情况灵活配置,当然如果使用其他的你也可以自己去写转换器,或者告诉我我去添加支持。

    public interface Converter<F, T> {
    
        T convert(F value) throws Exception;
    
        interface Factory {
            <F> Converter<F, String> fromType(Type fromType);
    
            <T> Converter<String, T> toType(Type toType);
        }
    
    }

    初始化依旧模仿:

    Hannibai.setConverterFactory(GsonConverterFactory.create());
  3. 生成接口

    1. 首先生成IHandle接口

      只有一个removeAll()方法,其实主要是为了混淆好配置而进行的抽取。

      public interface IHandle {
          @Apply
          void removeAll();
      }
    2. 然后根据AppPreference生成AppPreferenceHandle接口

      这里为对AppPreference变量生成操作方法。

      public interface AppPreferenceHandle extends IHandle {
      
        @DefString("zwenkai")
        boolean containsName();
      
        @DefString("zwenkai")
        String getName();
      
        @Apply
        void setName(final String name);
      
        @Apply
        void removeName();
      }
    3. 最后根据AppPreference生成AppPreferenceHandleImpl

      该类为AppPreference接口的实现以及单例模式的封装。

    final class AppPreferenceHandleImpl implements AppPreferenceHandle, IHandle {
        private final String mSharedPreferencesName = "com.haha.hannibaitest.AppPreference";
    
        private final String mId;
    
        private AppPreferenceHandleImpl() {
            this.mId = "";
        }
    
        public AppPreferenceHandleImpl(String id) {
            this.mId = id;
        }
    
        public static AppPreferenceHandleImpl getInstance() {
            return Holder.INSTANCE;
        }
    
        @DefString("zwenkai")
        public boolean containsName() {
            return Hannibai.contains1(mSharedPreferencesName, mId, "name", "zwenkai");
        }
    
        @DefString("zwenkai")
        public String getName() {
            return Hannibai.get1(mSharedPreferencesName, mId, "name", "zwenkai");
        }
    
        @Apply
        public void setName(final String name) {
            Hannibai.set1(mSharedPreferencesName, mId, "name", -1L, false, name);
        }
    
        @Apply
        public void removeName() {
            Hannibai.remove1(mSharedPreferencesName, mId, "name");
        }
    
        @Apply
        public void removeAll() {
            Hannibai.clear(mSharedPreferencesName, mId);
        }
    
        private static class Holder {
            private static final AppPreferenceHandleImpl INSTANCE = new AppPreferenceHandleImpl();
        }
    }
  4. 数据加密

    数据加密比较简单,就是对SharePreferenceKey对应的Value进行亦或运算。加密解密的方法为同一个,很巧妙,有兴趣的同学可以研究下。

    static final String endecode(String input) {
        char[] key = "Hannibai".toCharArray();
        char[] inChars = input.toCharArray();
        for (int i = 0; i < inChars.length; i++) {
            inChars[i] = (char) (inChars[i] ^ key[i % key.length]);
        }
        return new String(inChars);
    }

    为什么之前说这样加密可以减小存储文件的大小呢?

    未加密:

    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <map>
        <string name="name">{&quot;data&quot;:&quot;Kevin&quot;,&quot;createTime&quot;:1509687733860,&quot;expire&quot;:-1,&quot;expireTime&quot;:0,&quot;updateTime&quot;:1509946500232}</string>
    </map>

    加密:

    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <map>
        <string name="name">3C CSj* CEj   =! LSSTYqWVY^QRQ~QBL :LTDSMK-5%LTYNC8 -CT_\RXP|WYV]VPQ5</string>
    </map>

    其实主要是把"的转义字符&quot;给替换掉了,压缩点在这里。

  5. 具体操作类

    在生成的操作类中,可以看到都是调用了Hannibai类的方法,那么这里面是怎么封装的呢?

    public final class Hannibai {
    
        static boolean debug = false;
    
        public static final void init(Context context) {
            RealHannibai.getInstance().init(context, true);
        }
    
        public static final void init(Context context, boolean encrypt) {
            RealHannibai.getInstance().init(context, encrypt);
        }
    
        public static final void setDebug(boolean debug) {
            Hannibai.debug = debug;
        }
    
        public static final <T> T create(final Class<T> preference) {
            return RealHannibai.getInstance().create(preference);
        }
    
        public static final <T> T create(final Class<T> preference, String id) {
            return RealHannibai.getInstance().create(preference, id);
        }
    
        public static final void setConverterFactory(Converter.Factory factory) {
            RealHannibai.getInstance().setConverterFactory(factory);
        }
    
        public static final <T> boolean contains1(String name, String id, String key, T defValue) {
            return RealHannibai.getInstance().contains(name, id, key, defValue.getClass());
        }
    
        public static final <T> boolean contains2(String name, String id, String key, Type type) {
            return RealHannibai.getInstance().contains(name, id, key, type);
        }
    
        public static final <T> T get1(String name, String id, String key, T defValue) {
            return RealHannibai.getInstance().get(name, id, key, defValue, defValue.getClass());
        }
    
        public static final <T> T get2(String name, String id, String key, Type type) {
            return RealHannibai.getInstance().get(name, id, key, null, type);
        }
    
        public static final <T> void set1(String name, String id, String key, long expire, boolean updateExpire, T newValue) {
            RealHannibai.getInstance().set1(name, id, key, expire, updateExpire, newValue);
        }
    
        public static final <T> boolean set2(String name, String id, String key, long expire, boolean updateExpire, T newValue) {
            return RealHannibai.getInstance().set2(name, id, key, expire, updateExpire, newValue);
        }
    
        public static final void remove1(String name, String id, String key) {
            RealHannibai.getInstance().remove1(name, id, key);
        }
    
        public static final boolean remove2(String name, String id, String key) {
            return RealHannibai.getInstance().remove2(name, id, key);
        }
    
        public static final void clear(String name, String id) {
            RealHannibai.getInstance().clear(name, id);
        }
    
    }

    好吧,Hannibai类可以说啥事没干,大部分工作是对RealHannibai的封装。

  6. 不骗你,真的操作类

    1. 如何获取定义变量操作类的?

      通过之前的介绍,定义的AppPreference首先生成AppPreferenceHandle接口,然后生成AppPreferenceHandle接口的实现类AppPreferenceHandleImpl

      在使用AppPreference的时候是这样的:

      AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class);

      那是怎么通过接口获取的实现类呢?在RealHannibai中:

      final public <T> T create(final Class<T> preference) {
          Utils.validateHandleInterface(preference);
          try {
              return (T) Class.forName(preference.getName() + "Impl")
                      .getMethod("getInstance")
                      .invoke(null);
              } catch (Exception e) {
                  Log.e(TAG, "Something went wrong!");
                  throw new RuntimeException(e);
          }
      }

      跟简单,就是根据AppPreferenceHandle拼接Impl找到AppPreferenceHandleImpl类,然后反射调用它的getInstance()静态方法。

      获取带id的实现类:

      final public <T> T create(final Class<T> preference, String id) {
          Utils.validateHandleInterface(preference);
          try {
              return (T) Class.forName(preference.getName() + "Impl")
                  .getConstructor(String.class)
                  .newInstance(id);
          } catch (Exception e) {
              Log.e(TAG, "Something went wrong!");
              throw new RuntimeException(e);
          }
      }

      这个也是拼接到类名,然后反射它的构造方法获取实例。

    2. 判断是否包含Key

      首先获取Key对应的Value,如果Value为空则不包含Key,如果Value不为空则将数据转换为BaseModel实体看是否过期,过期也为不包含,不过期则为包含该Key

      final <T> boolean contains(String name, String id, String key, Type type) {
          String value = getSharedPreferences(name, id).getString(key, null);
          if (value == null || value.length() == 0) {
              if (Hannibai.debug)
                  Log.d(TAG, String.format("Value of %s is empty.", key));
              return false;
          } else {
              ParameterizedType parameterizedType = type(BaseModel.class, type);
              BaseModel<T> model = null;
              try {
                  model = (BaseModel<T>) getConverterFactory().toType(parameterizedType).convert(mEncrypt ? Utils.endecode(value) : value);
              } catch (Exception e) {
                  if (mEncrypt) {
                      Log.e(TAG, "Convert JSON to Model failed,will use unencrypted retry again.");
                  } else {
                      Log.e(TAG, "Convert JSON to Model failed,will use encrypted retry again.");
                  }
                  if (Hannibai.debug) {
                      e.printStackTrace();
                  }
                  try {
                      model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? value : Utils.endecode(value));
                  } catch (Exception e1) {
                      Log.e(TAG, "Convert JSON to Model complete failure.");
                      if (Hannibai.debug) {
                          e1.printStackTrace();
                      }
                  }
              }
      
              if (null == model) {
                  return false;
              }
      
              if (model.dataExpired()) {
                  if (Hannibai.debug)
                      Log.d(TAG, String.format("Value of %s is %s expired, return false.", key, model.data));
                  return false;
              } else {
                  return true;
              }
          }
      }

      这里进行了解密的尝试,比如之前配置的为加密,存储了数据,然后又配置了未加密,这时按照配置读取是错误的,配置说未加密实际上是加密的,这里进行了容错处理。

    3. 获取Key对应Value

      首先获取Key对应的Value,如果Value为空则返回默认值,如果Value不为空则将数据转换为BaseModel实体看是否过期,过期也返回默认值,不过期则返回BaseModel中对应数据。

      final <T> T get(String name, String id, String key, T defValue, Type type) {
          if (Hannibai.debug) Log.d(TAG, String.format("Retrieve the %s from the preferences.", key));
          String value = getSharedPreferences(name, id).getString(key, null);
          if (value == null || value.length() == 0) {
              if (Hannibai.debug)
                  Log.d(TAG, String.format("Value of %s is empty, return the default %s.", key, defValue));
              return defValue;
          } else {
              ParameterizedType parameterizedType = type(BaseModel.class, type);
              BaseModel<T> model = null;
              try {
                  model = (BaseModel<T>) getConverterFactory().toType(parameterizedType).convert(mEncrypt ? Utils.endecode(value) : value);
              } catch (Exception e) {
                  if (mEncrypt) {
                      Log.e(TAG, "Convert JSON to Model failed,will use unencrypted retry again.");
                  } else {
                      Log.e(TAG, "Convert JSON to Model failed,will use encrypted retry again.");
                  }
                  if (Hannibai.debug) {
                      e.printStackTrace();
                  }
                  try {
                      model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? value : Utils.endecode(value));
                  } catch (Exception e1) {
                      Log.e(TAG, String.format("Convert JSON to Model complete failure, will return the default %s.", defValue));
                      if (Hannibai.debug) {
                          e1.printStackTrace();
                      }
                  }
              }
      
              if (null == model) {
                  return defValue;
              }
      
              if (Hannibai.debug) {
                  Log.d(TAG, String.format("Value of %s is %s, create at %s, update at %s.", key, model.data, model.createTime, model.updateTime));
                  if (!model.dataExpired()) {
                      if (model.expire > 0) {
                          Log.d(TAG, String.format("Value of %s is %s, Will expire after %s seconds.", key, model.data, (model.expireTime - System.currentTimeMillis()) / 1000));
                      } else {
                          Log.d(TAG, String.format("Value of %s is %s.", key, model.data));
                      }
      
                  }
              }
              if (model.dataExpired()) {
                  if (Hannibai.debug)
                      Log.d(TAG, String.format("Value of %s is %s expired, return the default %s.", key, model.data, defValue));
                  return defValue;
              } else {
                  return model.data;
              }
          }
      }
    4. 设置Key对应值

      首先获取Key对应的Value,如果Value不为空,转换为BaseModel实体,更新对应数据,过期时间信息,然后转化为JSON字符串存储,如果Value为空则创建BaseModel并转化为JSON字符串存储。

      private final <T> SharedPreferences.Editor set(String name, String id, String key, long expire, boolean updateExpire, T newValue) throws Exception {
          if (Hannibai.debug) Log.d(TAG, String.format("Set the %s value to the preferences.", key));
          BaseModel<T> model = null;
          ParameterizedType type = type(BaseModel.class, newValue.getClass());
          SharedPreferences sharedPreferences = getSharedPreferences(name, id);
          String value = sharedPreferences.getString(key, null);
          if (value != null && value.length() != 0) {
              try {
                  model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? Utils.endecode(value) : value);
              } catch (Exception e) {
                  if (mEncrypt) {
                      Log.e(TAG, "Convert JSON to Model failed,will use unencrypted retry again.");
                  } else {
                      Log.e(TAG, "Convert JSON to Model failed,will use encrypted retry again.");
                  }
                  if (Hannibai.debug) {
                      e.printStackTrace();
                  }
                  try {
                      model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? value : Utils.endecode(value));
                  } catch (Exception e1) {
                      Log.e(TAG, "Convert JSON to Model complete failure.");
                      if (Hannibai.debug) {
                          e1.printStackTrace();
                      }
                  }
              }
              if (null == model) {
                  model = new BaseModel<>(newValue, expire);
              } else {
                  if (model.dataExpired()) {
                      model = new BaseModel<>(newValue, expire);
                      if (Hannibai.debug)
                          Log.d(TAG, String.format("Value of %s is %s expired", key, model.data));
                  } else {
                      model.update(newValue, updateExpire);
                  }
              }
          } else {
              model = new BaseModel<>(newValue, expire);
          }
          String modelJson = getConverterFactory().fromType(type).convert(model);
          return sharedPreferences.edit().putString(key, mEncrypt ? Utils.endecode(modelJson) : modelJson);
      }

混淆

如果使用了混淆,添加如下到混淆配置文件

-dontwarn com.kevin.hannibai.**
-keep class com.kevin.hannibai.** { *; }
-keep class * implements com.kevin.hannibai.IHandle { *; }
-keep @com.kevin.hannibai.annotation.SharePreference class * { *; }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值