针对Android渠道键值对的耐久化存储,虽然Jetpack出了新的DataStore,但实际项目中SharedPreferences仍是有大量运用,本文结合以前的运用经历给出一种极简且高雅且安全的实践。(示例项目见 gitee.com/spectre1225…

1. SharedPreferences的运用与改善

SharedPreferences的基本读写代码如下:

preferences.edit().putInt("intKey", 1).apply();//写
preferences.getInt("intKey", 0);//读,0为默许值

代码中直接这么用的话,键会很不好管理,不清楚一个键值对到底有多少地方运用,当键产生改变需要修改的时分,也简略遗漏。于是就有了以下改善:

public interface XXXConfig{
    String KEY_PROPERTY_AA = "key_aa";
    String KEY_PROPERTY_BB = "key_bb";
    String KEY_PROPERTY_CC = "key_cc";
    //more key.......
}
//运用的地方
preferences.edit().putInt(XXXConfig.KEY_PROPERTY_AA, 1).apply();//写
preferences.getInt(XXXConfig.KEY_PROPERTY_AA, 0);//读,0为默许值

但这样写仍然有问题,就是短少值类型的束缚:一个key对应的value,或许有很多种类型。这种状况下,需要额外的注释或文档来记载每一个key对应的value的类型信息。于是,有人想到能够像JavaBean相同,选用getter和setter办法的方式:

public class XXXConfig {
    private SharedPreferences preferences;
    private String KEY_PROPERTY_AA = "key_aa";
    private String KEY_PROPERTY_BB = "key_bb";
    //中间省略初始化......
    public int getPropertyAA() {
        return preferences.getInt(KEY_PROPERTY_AA, 0);
    }
    public void setPropertyAA(int value) {
        preferences.edit().putInt(KEY_PROPERTY_AA, value);
    }
    public int getPropertyBB() {
        return preferences.getInt(KEY_PROPERTY_BB, 0);
    }
    public void setPropertyBB(int value) {
        preferences.edit().putInt(KEY_PROPERTY_BB, value);
    }
}

这种写法改善了类型安全,但每次新增就需要写一个特点和两个办法,过程比较繁琐。理想状况,我仍是希望像写文档相同只需要写下面这样的信息:

特点1:类型 int
特点2:类型 String

然后运用的地方能够直接取值。因此,就有了下面介绍的新的封装办法:暂且称为NeoPreference。

2. NeoPreference简略运用

首先,咱们需要需要创立一个inferface来承继Config接口,这个新的接口对应一个SharedPreferences,默许接口名即为SharedPreferences的称号,例如:

public interface DemoConfig extends Config {
}

这儿的DemoConfig即为SharedPreferences的称号。有时分咱们想要自己另外指定称号,则能够运用Config.Name注解:

@Config.Name("my_demo_config")
public interface DemoConfig extends Config {
}

这个时分SharedPreferences称号就是my_demo_config

然后咱们就能够经过ConfigManager来获取DemoConfig的实例:

DemoConfig config = ConfigManager.getInstance().getConfig(DemoConfig.class);

到目前为止还没有什么新鲜的,接下来咱们往里面增加新的配置项/特点:

@Config.Name("my_demo_config")
public interface DemoConfig extends Config {
    Property<Integer> versionCode();
}

在上述基础上,只需要增加一行代码,就增加了新的键值对:key的值为versionCode,value的类型为Integer。然后咱们的读写代码能够这么写:

DemoConfig config = ConfigManager.getInstance().getConfig(DemoConfig.class);
Integer versionCode = config.versionCode().get();//读
config.versionCode().set(versionCode + 1);//写

如果咱们想要独自定key的姓名,咱们能够运用对应特点的注解:

@Config.Name("my_demo_config")
public interface DemoConfig extends Config {
    @IntItem(key = "my_version_code")
    Property<Integer> versionCode();
}

咱们还能够指定值的范围和默许值:


@Config.Name("my_demo_config")
public interface DemoConfig extends Config {
    @IntItem(key = "my_version_code", start = 1, to = 10000, defaultValue = 1)
    Property<Integer> versionCode();
}

这样,在值不符合规范的时分会抛出反常:

DemoConfig config = ConfigManager.getInstance().getConfig(DemoConfig.class);
config.versionCode().set(-1);//throw exeception

3. NeoPreference API阐明

这个东西的API除了ConfigManager类以外首要分两部分:Property类以及类型对应的注解。

3.1 ConfigManager接口阐明

ConfigManager是单例完成,维护一个SharedPreferencesConfig的注册表,提供getConfigaddListener两个办法。

以下是getConfig办法签名:

public <P extends Config> P getConfig(Class<P> pClass);
public <P extends Config> P getConfig(Class<P> pClass, int mode);

参数pClass是承继Config类的接口class,可选参数mode对应SharedPreferences的mode。

addListener的办法监听指定preferenceName中内容的改变,签名如下:

public void addListener(String preferenceName, WeakReference<Listener> listenerRef);
public void addListener(LifecycleOwner lifecycleOwner, String preferenceName, Listener listener);

第一个办法接受一个Listener的弱引证,需要调用者自己持有监听器的引证,自己管理生命周期,否则或许被回收。第二个办法不选用弱引证参数,而是额外增加LifecycleOwner,这个监听器的声明周期选用LifecycleOwner对应的生命周期。

3.2 Property类接口阐明

Property接口包含:

public final String getKey();//获取特点对应的key
public T get(T defValue);    //获取特点值,defValue为默许值
public T get();              //获取特点值,选用缺省默许值
public void set(T value);    //设置特点值
public Optional<T> opt();    //以Optional的方式回来特点值
public final void addListener(WeakReference<Listener<T>> listenerRef)    //相似ConfigManager,不过只监听该特点的值改变
public final void addListener(LifecycleOwner owner, Listener<T> listener)//相似ConfigManager,不过只监听该特点的值改变

泛型参数支撑LongIntegerFloatBooleanStringSet<String>SharedPreferences支撑的几种类型,以及额外的Serializable

3.3 类型相关注解介绍

这些注解对应SharedPreferences支撑的几种类型(其中description字段暂时不用)。

@interface StringItem {
    String key() default "";
    boolean supportEmpty() default true;
    String[] valueOf() default {};
    String defaultValue() default "";
    String description() default "";
}
@interface BooleanItem {
    String key() default "";
    boolean defaultValue() default false;
    String description() default "";
}
@interface IntItem {
    String key() default "";
    int defaultValue() default 0;
    int start() default Integer.MIN_VALUE;
    int to() default Integer.MAX_VALUE;
    int[] valueOf() default {};
    String description() default "";
}
@interface LongItem {
    String key() default "";
    long defaultValue() default 0;
    long start() default Long.MIN_VALUE;
    long to() default Long.MAX_VALUE;
    long[] valueOf() default {};
    String description() default "";
}
@interface FloatItem {
    String key() default "";
    float defaultValue() default 0;
    float start() default -Float.MIN_VALUE;
    float to() default Float.MAX_VALUE;
    float[] valueOf() default {};
    String description() default "";
}
@interface StringSetItem {
    String key() default "";
    String[] valueOf() default {};
    String description() default "";
}
@interface SerializableItem {
    String key() default "";
    Class<?> type() default Object.class;
    String description() default "";
}

4. 完整完成

见:gitee.com/spectre1225…