NeoPreference:一个简化SharedPreferences使用的工具

项目地址:https://github.com/Nagi1225/NeoPreference

过段时间发现之前那篇《SharedPreferences的一种极简优雅且安全的用法》有蛮多人看,就修复了一些问题,把代码整理了下,并发布到jitpack以方便使用。本文是之前的总结归纳优化,思考过程可查阅前文,部分内容重复,添加以json格式存储JavaBean数据类的示例。

注意:作者比较懒,而且现在精力重心偏向PL和AI那边,这个项目维护会很不及时(除非有重大问题),所以特意控制了下规模,整个库大约900行左右,希望看懂代码以后再使用。

新手入门

设置依赖

把下面配置添加到你的根目录build.gradle中在repositories的末尾:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

使用的模块中添加依赖:

dependencies {
        implementation 'com.github.Nagi1225:NeoPreference:0.1.0' //以实际版本为准
}

简单使用

创建配置:

@Config.Name(DemoConfig.NAME)
public interface DemoConfig extends Config {
    String NAME = "demo_config";

    Property<Integer> intProperty();

    @StringItem(supportEmpty = true)
    Property<String> stringProperty();

    @FloatItem(key = "height")
    Property<Float> floatProperty();

    @LongItem(key = "last_save_time")
    Property<Long> longProperty();

    @BooleanItem
    Property<Boolean> boolProperty();

    @StringSetItem(key = "collection_media_set", valueOf = {"mp3", "mp4", "png", "jpg", "mkv"})
    Property<Set<String>> collectMediaSet();
}

读写配置:

// 1. Get config instance
DemoConfig config = ConfigManager.getInstance().getConfig(DemoConfig.class);
// 2. Get property value
float fp = config.floatProperty().get();
// 3. Get property value or default value
int num = config.intProperty().get(-1);
// 4. Determines whether the property value exists
boolean present = config.longProperty().isPresent();
// 5. Use Optional to handle property 
config.stringProperty().opt().ifPresent((String str) -> {
    //handle string property
});

NeoPreference API 说明

这个工具的API除了ConfigManager类以外主要分两部分:Property类以及类型对应的注解。

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对应SharedPreferencesmode

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

public void addListener(String preferenceName, Listener listener);
public void addListener(LifecycleOwner lifecycleOwner, String preferenceName, Listener listener);
public void removeListener(String preferenceName, Listener listener);
public void removeListeners(String preferenceName);

第一个方法接受一个Listener,需要手动调用removeListenerremoveListeners,否则可能会内存溢出。第二个方法额外添加LifecycleOwner,这个监听器的声明周期采用LifecycleOwner对应的生命周期,在onDestroy时自动移除。

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 boolean exists();     //判断属性当前是否存在,没有set过就是false,set后即便是null也为true
public final void addListener(Listener<T> listener)    //类似ConfigManager,不过只监听该属性的值变化,需要手动remove
public final void addListener(LifecycleOwner owner, Listener<T> listener)//类似ConfigManager,不过只监听该属性的值变化,在owner onDestroy时自动remove

泛型参数支持LongIntegerFloatBooleanStringSetSharedPreferences支持的几种类型。

类型相关注解介绍

这些注解对应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.MAX_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 "";
}

扩展存储类型

除了SharedPreferences原本支持的类型外,可以通过PropertyFactory来扩展类型,例如我们想以json的格式存储JavaBean:

  • 第一步:创建对应示意性接口和注解:
public interface JsonData {

    @interface JsonItem {
        String key();

        String description() default "";
    }
}
  • 第二步:创建对应的PropertyFactory并在使用前注册:
public class JsonPropertyFactory extends PropertyFactory<JsonData.JsonItem, JsonData> {
    private final static Gson gson = new Gson();

    @Override
    public Property<JsonData> createProperty(String key, JsonData.JsonItem annotation, String preferenceName, SharedPreferences preferences) {
        return new JsonProperty<>(key, annotation, preferenceName, preferences);
    }

    static class JsonProperty<T extends JsonData> extends Property.BaseProperty<T> {
        final String description;

        public JsonProperty(String key, JsonData.JsonItem annotation, String preferenceName, SharedPreferences preferences) {
            super(annotation != null ? annotation.key() : key, preferenceName, preferences);
            description = annotation != null ? annotation.description() : "no description";
        }

        @Override
        public String getValueString() {
            return getPreferences().getString(getKey(), "");
        }

        @Override
        public String getDescription() {
            return description;
        }

        @Override
        public T get(T defValue) {
            String data = getPreferences().getString(getKey(), "");
            if (TextUtils.isEmpty(data)) {
                return defValue;
            } else {
                int classEndIndex = data.indexOf(":");
                try {
                    Class<?> clazz = Class.forName(data.substring(0, classEndIndex));
                    String jsonStr = data.substring(classEndIndex + 1);
                    return (T) gson.fromJson(jsonStr, clazz);
                } catch (ClassNotFoundException | JsonSyntaxException e) {
                    throw new RuntimeException("parse data failed:", e);
                }
            }
        }

        @Override
        public T get() {
            return get(null);
        }

        @Override
        public void set(T value) {
            String className = value.getClass().getCanonicalName();
            getPreferences().edit().putString(getKey(), className + ":" + gson.toJson(value)).apply();
        }
    }
}

在使用前注册到ConfigManager

ConfigManager.registerFactory(new JsonPropertyFactory());
  • 第三步:让对应数据类实现前面定义的接口:
public class UserInfo implements JsonData{
    private String id;
    private String name;
    private int age;

    public UserInfo(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public UserInfo() {
    }

    //getter and setter...
}

这样就可以以json的格式存储JavaBean数据类了:

@Config.Name(DemoConfig.NAME)
public interface DemoConfig extends Config {
    String NAME = "demo_config";

    @JsonData.JsonItem(key = "current_user_info")
    Property<UserInfo> userInfo();
}
/* sample usage */
DemoConfig config = ConfigManager.getInstance().getConfig(DemoConfig.class);
config.userInfo().opt().ifPresentOrElse(userInfo -> {
    userInfo.setAge(userInfo.getAge() + 1);
    config.userInfo().set(userInfo);
    }, () -> {
    config.userInfo().set(new UserInfo("1", "Alice", 1));
});

你可能感兴趣的:(Java,SP,Android,Java,Preferences,keyvalue)