Kotlin优雅的使用sp(SharedPreferences)

前言

我平时用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的解构声明,其实返回的就是一个泛型数组,参考下面链接第一条

https://blog.csdn.net/qq_33505109/article/details/81031791

发布了42 篇原创文章 · 获赞 192 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_33505109/article/details/97917905
今日推荐