前言
我平时用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 [email protected]
* 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) {
[email protected](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 [email protected]
* 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 -> 0
Boolean::class.java -> false
String::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 [email protected](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 [email protected]
* 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 [email protected]
* effect : 存储sp文件的名字,写的时候加上用途
* warning: 一个文件不要存储过多内容,影响效率
*/
object SPFileName {
@JvmField
val 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的解构声明,其实返回的就是一个泛型数组,参考下面链接第一条