前言
本文主要包括以下内容
1.MMKV有什么缺陷?
2.使用kotlin委托优化MMKV调用的实用技巧
如果觉得本文对您有所帮助,请帮忙点赞,谢谢~
MMKV有什么缺陷
我们在之前介绍过,MMKV相比SharedPreferences有很多优点,比如
1.mmap防止数据丢失,提高读写效率;
2.精简数据,以最少的数据量表示最多的信息,减少数据大小;
3.增量更新,避免每次进行相对增量来说大数据量的全量写入。
详情可见:SharedPreferences替换:MMKV集成与原理
那么MMKV有没有什么缺点?
MMKV的主要缺点就在于它不支持getAll
public Map<String, ?> getAll() {
throw new UnsupportedOperationException("use allKeys() instead, getAll() not implement because type-erasure inside mmkv");
}
MMKV都是按字节进行存储的,实际写入文件把类型擦除了,这也是MMKV不支持getAll的原因
虽然说getAll用的不多问题不大,但是MMKV因此就不具备导出和迁移的能力了。
比如说,以后出了更优秀的存储框架,例如DataStore正式发布后,是没有办法直接从MMKV批量迁移到新框架的,除非代码里面写死一个个key迁移,这样是很麻烦的
所以在我们引入MMKV时,就应该考虑到将来可能的数据迁出
如何让MMKV支持getAll?
既然MMKV不支持getAll的原因是因为类型被擦除了,那最简单的思路就是加上类型
我们可以在key上添加一个类型后缀
但是如果我们强制规定写key时后面都添加上后缀是难以维护的,并且也很麻烦
一个更加优雅地方式是添加一个代理层,所有读写操作都代理给这个代理类,并在这里为key添加类型
class SpProxy(private val mmkv: MMKV?) : SharedPreferences, SharedPreferences.Editor {
override fun getAll(): MutableMap<String, *> {
val keys = mmkv?.allKeys()
val map = mutableMapOf<String, Any>()
keys?.forEach {
if (it.contains("@")) {
val typeList = it.split("@")
when (typeList[typeList.size - 1]) {
String::class.simpleName -> map[it] = getString(it, "") ?: ""
Int::class.simpleName -> map[it] = getInt(it, 0)
Long::class.simpleName -> map[it] = getLong(it, 0L)
Float::class.simpleName -> map[it] = getFloat(it, 0f)
Boolean::class.simpleName -> map[it] = getBoolean(it, false)
}
}
}
return map
}
override fun getString(key: String?, defValue: String?): String? {
val typeKey = getTypeKey<String>(key)
return mmkv?.getString(typeKey, defValue)
}
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
val typeKey = getTypeKey<Boolean>(key)
return mmkv?.getBoolean(typeKey, defValue) ?: defValue
}
...
override fun contains(key: String?): Boolean {
val realKey = getRealKey(key)
return realKey.isNotEmpty()
}
override fun edit(): SharedPreferences.Editor? {
return mmkv?.edit()
}
override fun putString(key: String?, value: String?): SharedPreferences.Editor? {
val typeKey = getTypeKey<String>(key)
return mmkv?.putString(typeKey, value)
}
override fun putBoolean(key: String?, value: Boolean): SharedPreferences.Editor? {
val typeKey = getTypeKey<Boolean>(key)
return mmkv?.putBoolean(typeKey, value)
}
override fun remove(key: String?): SharedPreferences.Editor? {
val realKey = getRealKey(key)
if (realKey.isNotEmpty()){
return mmkv?.remove(realKey)
}
return null
}
override fun clear(): SharedPreferences.Editor? {
return mmkv?.clear()
}
inline fun <reified T> getTypeKey(key: String?): String {
val type = "@" + T::class.simpleName
return if (key?.contains(type) == true) {
type
} else {
key + type
}
}
private fun getRealKey(key: String?):String{
val typeKys = listOf(getTypeKey<String>(key),getTypeKey<Long>(key),getTypeKey<Float>(key),getTypeKey<Int>(key),getTypeKey<Boolean>(key))
typeKys.forEach {
if (mmkv?.containsKey(it)==true){
return it
}
}
return ""
}
}
如上所示:
1.所有读写操作都基于SpProxy来做
2.key尾部添加类型字段通过getTypeKey方法实现
3.支持getAll方法,方便后续迁移
4.mmkv的操作全都封装在SpProxy类里了,后续如果要迁移到其他类,修改SpProxy即可,外部可完全不用修改
计算机软件中所有问题都可以通过添加一个中间层来解决,为SharedPreferences封装一个代理层,可以有效地拓展我们项目的扩展性
如何迁移老数据
MMKV为我们提供了importFromSharedPreferences方法来从SharedPreferences迁移到MMKV
但如果直接调用这个方法迁移,那么数据类型就丢失了,下面提供一个新的迁移方法
fun migrate(migrateSp:SpProxy,preferences: SharedPreferences){
val kvs = preferences.all
if (kvs != null && kvs.isNotEmpty()) {
val iterator = kvs.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val key = entry.key
val value = entry.value
if (key != null && value != null) {
migrateSp.run {
when (value) {
is Boolean -> this.putBoolean(key, value)
is Int -> this.putInt(key,value)
is Long -> this.putLong(key,value)
is Float -> this.putFloat(key, value)
is String -> this.putString(key,value)
else -> {}
}
}
}
}
kvs.size
}
}
如上所示:支持从SharedPreferences迁移数据到MMKV并保留类型
利用委托优化存取操作
我们一般利用SharedPreferences存取数据是这样写的
private val KEY_DEMO_STR = "key_demo_STR"
fun setDemoStr(str: String) {
edit.putString(KEY_DEMO_STR, str).apply()
}
fun getDemoStr(): String? {
return preferences.getString(KEY_DEMO_STR, "")
}
首先我们需要定义一个key,然后再定义set与get方法
这样一个简单的操作就需要8行代码了,但如果我们利用委托机制可以实现一行搞定
一行搞定数据存取
优化后的代码实现数据存取可以一行搞定
object TestSP : PreferenceHolder() {
var value: Long by bindToPreferenceField(0L)
}
//读取sp
val value = TestSP.value
println(value) // 0 or 100
//存入sp
TestSP.value = 100
如上所示,通过变量value的读取与赋值即可实现数据的存取
这背后的原理是根据变量名生成key,然后将数据地存取操作委托给了ReadWriteProperty
然后在其中的getValue与setValue中调用mmkv或SharedPreferences相关方法来实现真正的存储与读取
这样做的优点在于
1.避免定义大量字符串key和出现重复key
2.简洁的委托模式不用再书写get(..) set(..)
3.后续如果需要修改代码,修改委托类即可,项目中的其他代码不需要变动,增强了扩展性
详情可见:Preferences委托优化
总结
本文主要讲述了MMKV的缺陷:即不支持getAll导致后续迁移困难及解决方案
同时介绍了利用委托优化数据存取API调用的经验
Show Me The Code
本文代码可见:SpProxy