EasySharedPreferences是开源基础组件集成库EasyAndroid中的基础组件之一。java
其做用是:使用具体的实体类去进行SharedPreferences数据存取。避免key值硬编码android
EasyAndroid做为一款集成组件库,此库中所集成的组件,均包含如下特色,你能够放心使用~~git
1. 设计独立github
组件间独立存在,不相互依赖,且若只须要集成库中的部分组件。也能够很方便的
只copy对应的组件文件
进行使用sql
2. 设计轻巧json
由于是组件集成库,因此要求每一个组件的设计尽可能精练、轻巧。避免由于一个小功能而引入大量无用代码.缓存
每一个组件的方法数均
不超过100
. 大部分组件甚至不超过50
。bash
得益于编码时的高内聚性
,若你只须要使用EasySharedPreferences. 那么能够直接去copy EasySharedPreferences源码文件到你的项目中,直接进行使用,也是没问题的。markdown
EasyAndroid开源库地址:https://github.com/yjfnypeu/EasyAndroidapp
key值硬编码
直接使用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() 复制代码
var name:String // 表明此SP文件中。新增key值为name, 类型为String的属性
复制代码
PreferenceRename
注解进行指定@PreferenceRename("rename_key") var name:String 复制代码
PreferenceIgnore
注解@PreferenceIgnore
val ignore:Address
复制代码
都知道,原生的SP只支持几种特定的数据进行存储:Int
, Float
, Boolean
, Long
, String
, Set<String>
.
而EasySharedPreferences
组件,经过提供中间类型
的方式。打破了此数据限制:
核心源码
// 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)) } } 复制代码
核心源码
// 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) } } } 复制代码
在映射实体类的定义这一节的最后。咱们有提到使用PreferenceIgnore
注解配置中间存储变量。当时只是简单提了一句,因此可能会有部分朋友对此注解的使用场景存在疑惑
这里我将经过举一个具体的例子进行使用场景说明:
好比说须要存储登陆用户的信息,好比登陆时的密码
(固然只是举例,对于密码类型的数据。推荐的存储容器仍是使用sql)。咱们想把它存储到SharedPreferences
中去:
@PreferenceRename("login_info") class Login:PreferenceSupport() { var password:String } 复制代码
可是咱们又不能直接对密码进行明文存储。因此咱们须要在每次进行使用的时候,主动的去再进行加密
、解密
:
// 读取时进行解密:
var password = EncryptTool.decode(user.password)
// 存储时进行加密:
user.password = EncryptTool.encode(password)
复制代码
可是这样的用法至关不优雅。因此咱们推荐使用PreferenceIgnore
建立一个中间存储数据出来:
@PreferenceRename("login_info") class Login:PreferenceSupport() { // 将实际存储的密码使用private修饰,避免外部直接修改 private var password:String @PreferenceIgnore var passwordWithEncrypt:String get() { return EncryptTool.decode(password) } set(value:String) { this.password = EncryptTool.encode(value)} } 复制代码
经过配置一个中间的存储变量,自动去进行存取时的加解密操做。对上层隐藏具体的加解密逻辑。这样上层使用起来就至关优雅了:
// 读取
var password = user.passwordWithEncrypty
// 存储
user.passwordWithEncrypty = password
复制代码
最后,为了不混淆后致使使用异常,请添加如下混淆配置:
-keep class * implements com.haoge.easyandroid.easy.PreferenceSupport
复制代码