什么是 EasySharedPreferences
EasySharedPreferences 是开源基础组件集成库 https://github.com/yjfnypeu/EasyAndroid 中的基础组件之一.
其作用是: 使用具体的实体类去进行 SharedPreferences 数据存取. 避免 key 值硬编码
https://github.com/yjfnypeu/EasyAndroid 作为一款集成组件库, 此库中所集成的组件, 均包含以下特点, 你可以放心使用~~
1. 设计独立
组件间独立存在, 不相互依赖, 且若只需要集成库中的部分组件. 也可以很方便的
只 copy 对应的组件文件
进行使用
2. 设计轻巧
因为是组件集成库, 所以要求每个组件的设计尽量精练, 轻巧. 避免因为一个小功能而引入大量无用代码.
每个组件的方法数均不超过 100. 大部分组件甚至不超过 50.
得益于编码时的高内聚性, 若你只需要使用 EasySharedPreferences. 那么可以直接去 copy EasySharedPreferences 源码文件 https://github.com/yjfnypeu/EasyAndroid/blob/master/utils/src/main/java/com/haoge/easyandroid/easy/EasySharedPreferences.kt 到你的项目中, 直接进行使用, 也是没问题的.
EasyAndroid 开源库地址: https://github.com/yjfnypeu/EasyAndroid
特性
通过具体的实体类进行 SharedPreferences 数据存取操作. 避免 key 值硬编码
自动同步, 即使别的地方是
直接使用 SharedPreferences 进行赋值
, 也能自动同步相关数据.
打破 SharedPreferences 限制. 支持几乎任意类型数据存取
用法与原理
用法概览
这里先来通过一个例子来先进行一下大致的了解:
比如现在有这么个配置文件: 文件名为 user_info, 内部存储了一些用户特有的信息:
使用原生的方式. 读取时, 我们需要这样写:
- val preference = context.getSharedPreferences("user_info", Context.MODE_PRIVATE)
- val username = preference.getString("username")
- val address = preference.getString("address")
- val age = preference.getInt("age")
而在需要进行数据修改时: 我们需要这样写:
- val editor = context.getSharedPreferences("user_info", Context.MODE_PRIVATE).edit()
- editor.putString("username", newName)
- editor.putString("address", newAddress)
- editor.putInt("age", newAge)
可以看到. 原生的写法中含有很多的硬编码的 key 值, 这在进行大量使用时, 其实是很容易出问题的.
而如果使用组件
EasySharedPreferences
来进行 SharedPreferences 的数据存取. 则方便多了:
创建映射实体类
- @PreferenceRename("user_info")
- class User:PreferenceSupport() {
- var username:String
- var age:Int
- var address:String
- }
进行读取
- // 直接加载即可
- val user = EasySharedPreferences.load(User::class.java)
进行修改
- // 直接使用 load 出来的 user 实例进行数值修改
- user.age = 16
- user.username = "haoge"
- // 修改完毕后, apply 更新修改到 SharedPreferences 文件.
- user.apply()
可以看到. 不管是进行读取数据. 还是修改数据.
EasySharedPreferences
的操作方式都是比原生的方式方便很多的.
下面开始对
EasySharedPreferences
组件的用法做更详细的说明:
映射实体类的定义
映射实体类即是上方示例中的 User 类: 通过将 SP 中需要的关键数据映射到具体的实体类中, 可以有效的避免 key 值硬编码的问题.
映射实体类的定义, 需要遵循以下一些规则:
实体类
必须继承 PreferenceSupport
, 且提供无参构造.
class Entity:PreferenceSupport()
默认采用实体类的类名作为
SP 的缓存文件名
, 当需要指定特殊的缓存文件名时. 需要使用 PreferenceRename 注解进行指定
- @PreferenceRename("rename_shared_name")
- class Entity:PreferenceSupport()
通过直接在实体类中添加不同的成员变量, 进行 SP 的属性配置:
var name:String // 代表此 SP 文件中. 新增 key 值为 name, 类型为 String 的属性
也可以指定属性的 key 值: 同样使用 PreferenceRename 注解进行指定
- @PreferenceRename("rename_key")
- var name:String
有时候. 我们会需要定义一下中间存储变量 (此部分数据不需要同步存储到 SP 中的). 可以使用 PreferenceIgnore 注解
- @PreferenceIgnore
- val ignore:Address
支持存储任意数据
都知道, 原生的 SP 只支持几种特定的数据进行存储: Int, Float, Boolean, Long, String, Set<String>.
而
EasySharedPreferences
组件, 通过提供中间类型的方式. 打破了此数据限制:
存储时: 将不支持的数据类型, 转换为 String 格式. 再进行存储:
核心源码
- // type 为接收者类型
- // value 为从 SP 中读取出的数据
- when {
- type == Int::class.java -> editor.putInt(name, value as? Int?:0)
- type == Long::class.java -> editor.putLong(name, value as? Long?:0L)
- type == Boolean::class.java -> editor.putBoolean(name, value as? Boolean?:false)
- type == Float::class.java -> editor.putFloat(name, value as? Float?:0f)
- type == String::class.java -> editor.putString(name, value as? String?:"")
- // 不支持的类型. 统统转换为 String 进行存储
- type == Byte::class.java
- || type == Char::class.java
- || type == Double::class.java
- || type == Short::class.java
- || type == StringBuilder::class.java
- || type == StringBuffer::class.java
- -> editor.putString(name, value.toString())
- GSON -> value?.let { editor.putString(name, Gson().toJson(it)) }
- FASTJSON -> value?.let { editor.putString(name, JSON.toJSONString(value)) }
- }
读取时: 接收者类型与取出数据格式不匹配 (此种场景取出的数据格式均为 String). 进行自动转换后再赋值:
核心源码
- // type 为接收者类型
- // value 为从 SP 中读取出的数据
- val result:Any? = when {
- type == Int::class.java -> value as Int
- type == Long::class.java -> value as Long
- type == Boolean::class.java -> value as Boolean
- type == Float::class.java -> value as Float
- type == String::class.java -> value as String
- // 不支持的类型. 读取出的都是 String, 直接进行转换兼容
- type == Byte::class.java -> (value as String).toByte()
- type == Short::class.java -> (value as String).toShort()
- type == Char::class.java -> (value as String).toCharArray()[0]
- type == Double::class.java -> (value as String).toDouble()
- type == StringBuilder::class.java -> StringBuilder(value as String)
- type == StringBuffer::class.java -> StringBuffer(value as String)
- GSON -> Gson().fromJson(value as String, type)
- FASTJSON -> JSON.parseObject(value as String, type)
- else -> null
- }
有细心的可以看到. 这里有对 GSON 与 FASTJSON 进行兼容.
EasySharedPreference
组件. 会在运行时判断当前运行环境是否存在具体的 JSON 解析库. 然后选择存在的解析库进行中间类型数据的生成器与解析器: 而组件本身是没有直接强制依赖此两种解析库的:
- private val FASTJSON by lazy { return@lazy exist("com.alibaba.fastjson.JSON") }
- private val GSON by lazy { return@lazy exist("com.google.gson.Gson") }
所以. 如果你需要存储一个原生不支持的类型. 直接添加即可, 比如需要存储一个 address_detail:
- @PerferenceRename("address_detail")
- var detail:Address
缓存加速
在上面的例子中. 我们是直接通过 load 方法进行的数据加载读取:
val user = EasySharedPreferences.load(User::class.java)
这样一行代码, 起到的效果即是:
加载 User 类所对应的 SharedPreferences 文件数据
创建 User 实例, 并将 SP 文件中的数据. 注入到 User 类中的对应变量中去.
所以相对来说. load 方法其实是会有一定的耗时. 毕竟注入操作都离不开反射, 当然, 如果你不在同一个 SP 文件中去
存储大量的数据内容
的话, 其实对于现在的机型来说. 影响还是可以忽略不计的.
但是毕竟如果每次去读取都去读取注入的话. 总归是一种性能影响, 也不便于体验.
所以组件提供了对应的缓存控制处理: 只在首次加载时进行读取与注入:
- fun <T> load(clazz: Class<T>):T {
- container[clazz]?.let { return it.entity as T}
- val instance = EasySharedPreferences(clazz)
- container[clazz] = instance
- return instance.entity as T
- }
所以. 通过同一个 clazz 加载读取出来的实例, 都是同一个实例!
自动同步
因为缓存加速的原因, 我们通过 load 方法加载出来的实例都是一样的, 所以应该会有人担心: 当在使用
EasySharedPreferences
组件的同时. 如果在别的业务线上, 有人对此 SP 文件
直接使用原生的方式进行了修改
, 会不会导致数据出现不同步? 即数据污染现象?
讲道理. 这是不会的! 因为
EasySharedPreferences
组件, 专门针对此种场景进行了兼容:
原理说明
原生的 SharedPreferences 提供了
OnSharedPreferenceChangeListener
监听器. 此监听器的作用为: 对当前的 SharedPreferences 容器中的数据做监听. 当容器中有数据改变了. 则通过此接口对外通知. 便于进行刷新
- public interface OnSharedPreferenceChangeListener {
- void onSharedPreferenceChanged(
- SharedPreferences sharedPreferences, // 被监听的容器实例
- String key);// 被修改的数据的 key.
- }
然后, 需要指出的是: 其实系统本身也有对 SharedPreferences 容器实例做缓存. 所以: 通过同样的文件名获取到的 SharedPreferences 实例, 其实都是同一个对象实例
所以, 同步的流程即是: 只要对组件中自身绑定的 SharedPreferences 容器, 注册此监听器, 即可在外部进行修改时. 同步获取到被修改的 key 值. 再相对的进行指定 key 的数据同步即可:
所以, 最终的自动同步逻辑核心逻辑代码即是:
- class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPreferenceChangeListener {
- // 绑定的 SharedPreference 实例
- private val preferences:SharedPreferences
- init {
- // 创建时, 注册内容变动监听器
- preferences.registerOnSharedPreferenceChangeListener(this)
- ...
- }
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
- // 回调中进行数据同步处理
- }
- fun write() {
- synchronized(this) {
- // 自身的修改需要更新到文件中去时, 暂时注销掉监听器. 不对自身的数据处理做监听
- preferences.unregisterOnSharedPreferenceChangeListener(this)
- ...
- preferences.registerOnSharedPreferenceChangeListener(this)
- }
- }
- }
来源: https://juejin.im/post/5b34a970f265da59567953a3