SharedPreferences的一种极简优雅且安全的用法
迪丽瓦拉
2025-05-30 17:49:22
0

针对Android平台键值对的持久化存储,虽然Jetpack出了新的DataStore,但实际项目中SharedPreferences还是有大量使用,本文结合以前的使用经验给出一种极简且优雅且安全的实践。(示例项目见 https://gitee.com/spectre1225/neo-preference-demo)

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 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 versionCode();
}

我们还可以指定值的范围和默认值:


@Config.Name("my_demo_config")
public interface DemoConfig extends Config {@IntItem(key = "my_version_code", start = 1, to = 10000, defaultValue = 1)Property 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 getConfig(Class

pClass); public

P getConfig(Class

pClass, int mode);

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

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

public void addListener(String preferenceName, WeakReference 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 opt();    //以Optional的形式返回属性值
public final void addListener(WeakReference> listenerRef)    //类似ConfigManager,不过只监听该属性的值变化
public final void addListener(LifecycleOwner owner, Listener listener)//类似ConfigManager,不过只监听该属性的值变化

泛型参数支持LongIntegerFloatBooleanStringSetSharedPreferences支持的几种类型,以及额外的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. 完整实现

见:https://gitee.com/spectre1225/neo-preference-demo

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...