前言
我平时用java写的sp工具类,现在有两个需求:
第一个是要管理sp的文件名,虽然java可以通过Config的方式配置几个final静态的字符串常量来管理,但是总感觉不够优雅,而且可能存在随便写个文件名,不放在Config内的情况
第二个是要一次保存多个数据的话,java需要多次调用,emmm,不够优雅
效果
还是先看kt调用的代码
同时向USER_INFO_CACHE文件中写入两个数据
从USER_INFO_CACHE文件中读取数据,并设置默认值
清空USER_INFO_CACHE文件中的数据
同时从USER_INFO_CACHE文件读取多条String数据
使用属性委托,快捷操作sp中的数据
代码
工具类代码,直接粘贴到一个kt文件内
重构后的:
/*** creator: lt 2019/7/13--15:40 lt.dygzs@qq.com* effect : SharedPreferences工具类* warning:*//*** 往sp中写入数据,this为fileName*/
fun SpName.writeSP(bean: Pair<String, *>): SpName {App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE).edit().put(bean).apply()return this
}fun SpName.writeSPs(vararg beans: Pair<String, *>): SpName {if (beans.isEmpty()) {throw RuntimeException("writeSPs没有填写可变参数")}val editor = App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE).edit()beans.map(editor::put)editor.apply()return this
}//处理存储类型
private fun SharedPreferences.Editor.put(bean: Pair<String, *>): SharedPreferences.Editor {when (bean.second) {is Boolean -> this.putBoolean(bean.first, bean.second as Boolean)is Int -> this.putInt(bean.first, bean.second as Int)is Long -> this.putLong(bean.first, bean.second as Long)is Float -> this.putFloat(bean.first, bean.second as Float)is String -> this.putString(bean.first, bean.second as String)is Number -> this.putString(bean.first, bean.second.toString())else -> this.putString(bean.first, bean.second.toJson())//toJson()是利用Gosn或者fastJson把对象变为json数据}return this
}/*** 从sp中读取数据,this为fileName* 注意读取数值和boolean的时候最好给个默认值*/
fun <T> SpName.readSP(key: String, defaultValue: T): T {val preference = App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE)return when (defaultValue) {is Boolean -> preference.getBoolean(key, defaultValue)is Int -> preference.getInt(key, defaultValue)is Long -> preference.getLong(key, defaultValue)is Float -> preference.getFloat(key, defaultValue)is String -> preference.getString(key, defaultValue)is Byte -> preference.getString(key, defaultValue.toString()).toByte()is Short -> preference.getString(key, defaultValue.toString()).toShort()is Double -> preference.getString(key, defaultValue.toString()).toDouble()is UByte -> preference.getString(key, defaultValue.toString()).toUByte()is UShort -> preference.getString(key, defaultValue.toString()).toUShort()is UInt -> preference.getString(key, defaultValue.toString()).toUInt()is ULong -> preference.getString(key, defaultValue.toString()).toULong()else -> throw RuntimeException("如果读取对象,请使用string类型的默认值,或者使用内联的readSPOfAny方法")} as T
}/*** 从sp中读取对象,this为fileName* 注意,如果json读不到则会返回null对象*/
inline fun <reified T> SpName.readSPOfAny(key: String): T? {if (T::class.java == String::class.java|| T::class.java == Boolean::class.java|| T::class.java == Number::class.java)throw RuntimeException("读取sp中的数据时,类型不正确,readSPOfAny方法只能用来读取对象")return this.readSP(key, "").json2Any()
}/*** 同时读取多条数据* 注意:需要读取同一种类型,若读取不同类型需要分开读取* 传入的Bean的key为key value为默认值*/
inline fun <reified T> SpName.readSPs(vararg beans: Pair<String, T>): Array<T> {if (beans.isEmpty()) {throw RuntimeException("readSPs没有填写可变参数")}return Array(beans.size) {this@readSPs.readSP(beans[it].first, beans[it].second)}
}/*** 清空该sp文件中的所有内容*/
fun SpName.clearSP(): SpName {App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE).edit().clear().apply()return this
}/*** 移除相应key的数据*/
fun SpName.removeSP(vararg keys: String): SpName {if (keys.isEmpty()) {throw RuntimeException("removeSP没有填写可变参数")}val edit = App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE).edit()keys.map(edit::remove)edit.apply()return this
}/*** 快捷创建一个s - t 的Pair 使用方式:s/t*/
operator fun <T> String.div(value: T): Pair<String, T> = Pair(this, value)
重构前的(看你怎么选了):
/*** creator: lt 2019/7/13--15:40 lt.dygzs@qq.com* effect : SharedPreferences工具类* warning: 暂时只支持 int boolean String 三种类型*/
//todo 注:下面的App.getInstance()方法是获取Application的上下文,童鞋使用时请自行替换
/*** 往sp中写入数据,this为fileName* 注意只接收 int String boolean*/
fun SpName.writeSP(vararg beans: SPSaveBean<*>) {val editor = App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE).edit()beans.map {when (it.value) {is Int -> editor.putInt(it.key, it.value as Int)is Boolean -> editor.putBoolean(it.key, it.value as Boolean)is String -> editor.putString(it.key, it.value as String)else -> throw RuntimeException("向sp中写入时,类型不正确")}}editor.apply()
}/*** 从sp中读取数据,this为fileName* 注意只能读到 int String boolean* 注意读取int 和 boolean的时候最好给个默认值*/
fun <T> SpName.readSP(key: String, defaultValue: T): T {val preference = App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE)return when (defaultValue) {is Int -> preference.getInt(key, defaultValue)is Boolean -> preference.getBoolean(key, defaultValue)is String -> preference.getString(key, defaultValue)else -> throw RuntimeException("读取sp中的数据时,类型不正确")} as T
}inline fun <reified T> SpName.readSP(key: String): T =this.readSP(key, when (T::class.java) {Int::class.java -> 0Boolean::class.java -> falseString::class.java -> ""else -> throw RuntimeException("读取sp中的数据时,类型不正确")} as T)/*** 同时读取多条数据* 注意:需要读取同一种类型,若读取不同类型需要分开读取* 传入的SPSaveBean的key为key value为默认值*/
inline fun <reified T> SpName.readSPs(vararg spBeans: SPSaveBean<T>): Array<T> {return Array(spBeans.size) {return@Array this@readSPs.readSP(spBeans[it].key, spBeans[it].value)}
}/*** 清空该sp文件中的所有内容*/
fun SpName.clearSP() {val sp = App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE)sp.edit().clear().apply()
}/*** 移除相应key的数据*/
fun SpName.removeSP(vararg keys: String) {val sp = App.getInstance().getSharedPreferences(this, Context.MODE_PRIVATE)val edit = sp.edit()keys.map {edit.remove(it)}edit.apply()
}/*** creator: lt 2019/7/13--15:54 lt.dygzs@qq.com* effect : 快捷使用SPUtil存储的bean* warning:*/
class SPSaveBean<T>(val key: String, val value: T)/*** 快捷创建一个s - s 的SPSaveBean 使用方式:s/s 下同*/
operator fun String.div(value: String): SPSaveBean<String> = SPSaveBean(this, value)operator fun String.div(value: Int): SPSaveBean<Int> = SPSaveBean(this, value)
operator fun String.div(value: Boolean): SPSaveBean<Boolean> = SPSaveBean(this, value)
SPFileName文件
/*** creator: lt 2019/7/13--15:45 lt.dygzs@qq.com* effect : 存储sp文件的名字,写的时候加上用途* warning: 一个文件不要存储过多内容,影响效率*/
object SPFileName {@JvmFieldval USER_INFO_CACHE = SpName("userInfoCache")//用于存储用户信息
}class SpName(val value: String)
Kotlin委托的代码,可以更方便的使用sp
/*** 属性委托类,可以更方便的使用* 只可以获取基本类型* 如果key传空字符串,则使用自身的名字为key*/
class SP<T>(private val spName: SpName, private val defaultValue: T, private val key: String = "") {operator fun getValue(thisRef: Any?, property: KProperty<*>): T =spName.readSP(if (key == "") property.name else key, defaultValue)operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =spName.writeSP((if (key == "") property.name else key) / value)
}//只可以委托对象
class SPAny<T>(val spName: SpName, val key: String = "") {inline operator fun <reified T> getValue(thisRef: Any?, property: KProperty<*>): T? =spName.readSPOfAny(if (key == "") property.name else key)operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) =spName.writeSP((if (key == "") property.name else key) / value)
}
SP委托的使用
使用by关键字声明一个SP委托:
上面的每次获取都会从sp中获取,而每次赋值都会向sp中写入,比直接操作sp更方便
解析
SpUtil还是那个SpUtil,但是中间用了几种Kotlin特性来封装了一下,操作和管理更方便
可能有几个可能不是太常见的地方,我简单说一下
1.SP的性能问题
sp有性能问题,这是一般都知道的,由于底层是使用的xml文件存储数据,所以冷读取和冷存储都是需要通过io流的,其xml文件存储在 data/data/包名/shared_prefs 下,而由于sp#edit#commit方法就是冷提交:在主线程中直接阻塞并提交数据变更,所以,如果一个sp文件中存储过多或过大的话,就有很大的可能会阻塞主线程,所以提交改用sp#edit#apply方法提交,是一个异步提交方法,具体实现可以自行百度
2. 字符串 斜杠 字符串 是个什么写法?
是kt的操作符重载,参考下面链接第六条,当然你看会了可以改成其他的操作符
https://blog.csdn.net/qq_33505109/article/details/81031791
3.typealias SpName = String 是什么意思?
是kt的类型别名,参考下面链接第九条
https://blog.csdn.net/qq_33505109/article/details/81031791
4.val (v1,v2)=readSPs() 是什么操作?
是kt的解构声明,其实返回的就是一个泛型数组,参考下面链接第一条
https://blog.csdn.net/qq_33505109/article/details/81031791