diff --git a/app/src/main/java/com/haoge/sample/easyandroid/activities/EasySharedPreferencesActivity.kt b/app/src/main/java/com/haoge/sample/easyandroid/activities/EasySharedPreferencesActivity.kt index fef106c..e3d5711 100644 --- a/app/src/main/java/com/haoge/sample/easyandroid/activities/EasySharedPreferencesActivity.kt +++ b/app/src/main/java/com/haoge/sample/easyandroid/activities/EasySharedPreferencesActivity.kt @@ -18,11 +18,16 @@ class EasySharedPreferencesActivity:BaseActivity() { .edit() .putBoolean("mBool", true) .putFloat("mFloat", 4f) - .putInt("mInt", 100) + .putInt("age", 18) .putLong("mLong", 24L) - .putString("mStr", "Hello") + .putString("mStr", "modified by Editor") .putStringSet("mStrSet", setOf("Hello", "World", "123")) .apply() + + val value = null + var number = value as? Int?:0 + println("number = ${number}") + } @OnClick(R.id.createByEasy) @@ -35,12 +40,13 @@ class EasySharedPreferencesActivity:BaseActivity() { } } -@PreferenceAnnotation("example_shared_data") +@PreferenceRename("example_shared_data") class Entity:PreferenceSupport() { var mBool:Boolean = true var mFloat:Float = 0f var mInt:Int = 0 var mLong:Long = 0L - @PreferenceIgnore var mStr:String = "" + @PreferenceIgnore + var ignore:String = "This is ignore text" } \ No newline at end of file diff --git a/docs/EasySharedPreferences.md b/docs/EasySharedPreferences.md new file mode 100644 index 0000000..23d1b5e --- /dev/null +++ b/docs/EasySharedPreferences.md @@ -0,0 +1,195 @@ +# EasySharedPreferences + +EasySharedPreferences对`SharedPreferences`的操作进行封装,将SP的操作 + +其做法为:将SP的数据映射到指定的实体类中去。避免到处去指定key。进行硬编码存储 + +[Sample Activity](../app/src/main/java/com/haoge/sample/easyandroid/activities/EasySharedPreferencesActivity.kt) + +## 特性 + +1. 通过具体的实体类进行SP数据存储操作。避免`key值硬编码` +2. 自动同步,即使别的地方是`直接使用SharedPreferences进行赋值`,也能自动同步相关数据。 +3. 打破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`的数据存取。则方便多了: + +1. 创建映射实体类 + +``` +@PreferenceRename("user_info") +class User:PreferenceSupport() { + var username:String + var age:Int + var address:String +} +``` + +2. 进行读取 + +``` +// 直接加载即可 +val user = EasySharedPreferences.load(User::class.java) +``` + +3. 进行修改 + +``` +// 直接使用load出来的user实例进行数值修改 +user.age = 16 +user.username = "haoge" + +// 修改完毕后,apply更新修改到SharedPreferences文件。 +user.apply() +``` + +### 实体类的定义说明 + +在上面的示例中。我们已经定义了一个对应的映射实体类了: + +``` +@PreferenceRename("user_info") +class User:PreferenceSupport() { + var username:String + var age:Int + var address:String +} +``` + +下方的配置说明,可结合此具体的映射实体类进行理解: + +1. 映射实体类,必须继承自`PreferenceSupport`类。且提供`无参构造器`。 + +``` +class User:PreferenceSupport() +``` + +2. 当需要指定使用的SP的文件名时。使用`PreferenceRename`注解进行指定。否则将使用类名作为文件名: + +比如这里需要使用的SP文件名为user_info: + +``` +@PreferenceRename("user_info") +class User:PreferenceSupport() +``` + +3. 通过直接在实体类中添加不同的成员变量,进行SP的属性配置: + +``` +var name:String // 代表此SP文件中。新增key值为name, 类型为String的属性 +``` + +4. 也可以指定属性的key值:同样使用`PreferenceRename`注解 + +``` +@PreferenceRename("rename_key") +var name:String +``` + +5. 有时候。我们会需要定义一下中间存储变量(此部分数据不需要同步存储到SP中的)。可以使用`PreferenceIgnore`注解 + +``` +@PreferenceIgnore +val ignore:Address +``` + +### 打破存储类型限制 + +我们都知道。原生的`SharedPreferences`只支持很少量的数据类型进行存储:`Int`, `Float`, `Boolean`, `Long`, `String`,`Set` + +而有时候我们会需要存储一些其他类型的数据进行缓存。比如`Array`,`List`,`Bean`对象。这个时候`SharedPreferences`的存储功能就捉襟见肘了。 + +所以,这时就会需要:不然`重选存储方式(数据库存储)`, 不然`将数据转为SP支持的数据格式`来进行存储。 + +`EasySharedPreferences`组件即是采用的`第二种方式`来进行的存储: + +所以。当我们需要指定存储的其他类型数据时。直接添加即可:(比如存储一个列表数据) + +``` +var list:List +``` + +对此数据进行存储时。将会自动将其转换为`JSON`数据再进行存储;同样在进行读取时,也会进行`JSON反序列化`后再进行赋值。 + +### 缓存加速 + +在上面的例子中可以看到。加载`SharedPreferences`数据并读取到实体类中去。只需要调用一行代码即可: + +``` +// 直接加载即可 +val user = EasySharedPreferences.load(User::class.java) +``` + +看到这里的时候。肯定会有很多人担心使用时的性能问题。所以我先贴一个`load`的源码进行说明: + +``` +fun load(clazz: Class):T { + synchronized(container) { + container[clazz]?.let { return it.entity as T} + + val instance = EasySharedPreferences(clazz) + container[clazz] = instance + return instance.entity as T + } +} +``` + +可以看到:只有当`第一次使用此clazz`进行加载时。才会走加载流程。后面的都是直接读取的缓存。所以请放心使用 + +### 自动同步 + +`EasySharedPreferences`组件,其本质是对`SharedPreferences`的存取操作进行封装。 + +但是很难避免的是:会有部分朋友在写的时候,还是在上层使用`SharedPreferences`直接数据存储。 + +而在上面的也展示了。其实我们在load的时候并没有每次都去重新加载。而是读取的`已存在的缓存`。 + +`EasySharedPreferences`组件则对此场景做了兼容。并不会导致数据不同步的问题。 + +#### 自动同步原理说明 + +首先需要了解的一点是。在系统层面,同一个文件名的`SharedPreferences`实例。其实都是一样的。因为系统本身就是对SP进行了缓存处理: + +所以我们就可以直接使用`SharedPreferences`本身提供的`OnSharedPreferenceChangeListener`去进行数据改变时的监听操作: + +``` +public interface SharedPreferences { + // 提供的数据改变时的监听器。当此SharedPreferences对应的某个属性被改变时。将会被触发进行回调 + public interface OnSharedPreferenceChangeListener { + + void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key); + } + // 注册监听器。 + void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); + + void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); +``` + +所以。自动同步其实很简单:直接接入此监听器。同步回调中指定的key的数据即可 + diff --git a/utils/src/main/java/com/haoge/easyandroid/easy/EasySharedPreferences.kt b/utils/src/main/java/com/haoge/easyandroid/easy/EasySharedPreferences.kt index 15d4bb9..d2d453e 100644 --- a/utils/src/main/java/com/haoge/easyandroid/easy/EasySharedPreferences.kt +++ b/utils/src/main/java/com/haoge/easyandroid/easy/EasySharedPreferences.kt @@ -15,9 +15,13 @@ import java.lang.reflect.Field */ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPreferenceChangeListener { + // 绑定的具体实体类。 internal val entity:PreferenceSupport - internal val fields = mutableMapOf() - internal val preferences:SharedPreferences + // 所有的可操作变量 + private val fields = mutableMapOf() + // 绑定的SharedPreference实例 + private val preferences:SharedPreferences + // 存储待同步的数据的key值 private val modifierKeys = mutableListOf() init { @@ -26,7 +30,7 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe } entity = clazz.newInstance() as PreferenceSupport - val name:String = clazz.getAnnotation(PreferenceAnnotation::class.java)?.value?:clazz.simpleName + val name:String = getValid(clazz.getAnnotation(PreferenceRename::class.java)?.value, clazz.simpleName) preferences = EasyAndroid.getApplicationContext().getSharedPreferences(name, Context.MODE_PRIVATE) // 注册内容变动监听器 preferences.registerOnSharedPreferenceChangeListener(this) @@ -40,9 +44,11 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe continue } - if (!fields.containsKey(field.name)) { + val key = getValid(field.getAnnotation(PreferenceRename::class.java)?.value, field.name) + + if (!fields.containsKey(key)) { // 对于父类、子类均存在的字段。使用子类的数据进行存储 - fields[field.name] = field + fields[key] = field if (!field.isAccessible) { field.isAccessible = true } @@ -82,7 +88,7 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe }) // 从SP中读取数据。注入到实体类中。 - internal fun read() { + private fun read() { synchronized(this) { val map = preferences.all for ((name, field) in fields) { @@ -118,8 +124,9 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe // ignore 只过滤此类异常。其他异常正常抛出 } } + // 将实体类中的数据。注入到SP容器中。 - internal fun write() { + private fun write() { synchronized(this) { preferences.unregisterOnSharedPreferenceChangeListener(this) val editor = preferences.edit() @@ -127,11 +134,11 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe val value = field.get(entity) val type = field.type when { - type == Int::class.java -> editor.putInt(name, value as Int) - type == Long::class.java -> editor.putLong(name, value as Long) - type == Boolean::class.java -> editor.putBoolean(name, value as Boolean) - type == Float::class.java -> editor.putFloat(name, value as Float) - type == String::class.java -> editor.putString(name, value as String) + 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?:"") type == Byte::class.java || type == Char::class.java || type == Double::class.java @@ -139,8 +146,8 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe || type == StringBuilder::class.java || type == StringBuffer::class.java -> editor.putString(name, (value as Byte).toString()) - GSON -> editor.putString(name, Gson().toJson(value)) - FASTJSON -> editor.putString(name, JSON.toJSONString(value)) + GSON -> value?.let { editor.putString(name, Gson().toJson(it)) } + FASTJSON -> value?.let { editor.putString(name, JSON.toJSONString(value)) } } } editor.apply() @@ -165,8 +172,12 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe fun apply() { if (handler.hasMessages(WRITE)) return handler.sendEmptyMessageDelayed(WRITE, 100) + handler.apply { } } + private fun getValid(value:String?, default:String):String = + if (value.isNullOrEmpty()) default else value as String + companion object { private const val READ = 1 @@ -194,15 +205,17 @@ class EasySharedPreferences(val clazz: Class<*>):SharedPreferences.OnSharedPrefe @Suppress("UNCHECKED_CAST") @JvmStatic fun load(clazz: Class):T { - container[clazz]?.let { return it.entity as T} + synchronized(container) { + container[clazz]?.let { return it.entity as T} - val instance = EasySharedPreferences(clazz) - container[clazz] = instance - return instance.entity as T + val instance = EasySharedPreferences(clazz) + container[clazz] = instance + return instance.entity as T + } } internal fun find(clazz: Class<*>):EasySharedPreferences { - return container[clazz]?:throw RuntimeException("") + return container[clazz]?:throw RuntimeException("Could not find EasySharedPreferences by this clazz:[${clazz.canonicalName}]") } } } @@ -216,6 +229,9 @@ abstract class PreferenceSupport { } @Retention(AnnotationRetention.RUNTIME) -annotation class PreferenceAnnotation(val value:String = "") +@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD) +annotation class PreferenceRename(val value:String = "") + @Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FIELD) annotation class PreferenceIgnore \ No newline at end of file