前言
mmkv 是腾讯开源的key-value组件库,优异的性能和跨进程等功能特点受到广大Android开发者的喜爱,并且支持sp迁移到mmkv,本身利用sp的getAll方法然后循环插入mmkv,但是mmkv 使用了pb做序列化提升性能,带来的问题就是写入后类型丢失了,导致MMKV不支持getAll。这样就会有数据迁移问题,可以迁移进来但是无法迁出去(城市户口???)
public Map<String, ?> getAll() {
throw new UnsupportedOperationException("use allKeys() instead, getAll() not implement because type-erasure inside mmkv");
}
如何解决
-
掘金这篇文章针对MMKV封装提供读写数据方法提供了解决方案,原理很简单在写入数据key时候
拼接类型后缀
,解析数据时候就知道key对应存储的类型了。 -
有了kotlin的属性委托出现,我借鉴并优化开发了 Kotlin属性委托简化SharePreferences使用,支持多进程、MMKV、数据加解密、getAll,并优化sp的ANR。 (github.com),特点就是可以在类创建的时候进行配置,使用读写的时候映射到属性对应的getter\setter
object TestSP : PreferenceHolder() { var value: Long by bindToPreferenceField(0L) } //读取sp val value = TestSP.value println(value) // 0 or 100 //存入sp TestSP.value = 100
好处就是用了属性名称做key,不用自定义key或者排查重复key(属性一样IDE直接报错),也不用写大量
get(..) set(..)
重复的代码,但是由于mmkv本身不支持getAll
,同样会有数据迁移问题。
属性委托的 MMKV 如何支持getAll
看了掘金这篇文章后,我立即想到为key增加后缀的方式来判断类型提供getAll,但是由于我的库是开源并且发布到 jitpack上面了,这样子其他同学升级我的库版本后,key拼接发生变化原来的数据无法支持了,兼容性问题产生。难道要判断版本??作为代码洁癖的我不允许这样的情况。终于在看到kotlin反射篇的时候来了灵感。由于委托方式的特殊性,所有的委托属性必须在PreferenceHolder
继承类中,通过反射可以拿到,那么获取其值后填充map不就可以了,go go go
-
获取PreferenceHolder继承类的所有非扩展属性
val properties = this::class.declaredMemberProperties
-
过滤出所有委托的属性
val delegates = properties.filterIsInstance<KProperty1<PreferenceHolder, *>>()
-
循环遍历所有委托属性,反射调用属性的getter获取value
for (p in delegates) { val prevAccessible = p.isAccessible if (!prevAccessible) p.isAccessible = true print( "name:${p.name} value:${p.get(this)}") p.isAccessible = prevAccessible }
升级Preferences支持getAll
由于Preferences支持加解密,针对加密的sp存储的数据如果直接使用preferences.all
获取会有没有解密的情况,也需要通过反射走属性的get方法进行解密,最终封装添加方法如下
/**
* 获取所有key-value 默认根据配置是否加解密决定
* @unRaw 熟肉 true 表示获取到真实数据 即解密后的 key-value
* 生肉 false 表示获取到的数据是sp xml真实数据可能会有加密数据 mmkv默认必须解密 此功能无效
* */
fun getAll(unRaw: Boolean = crypt != null): MutableMap<String, *>? =
if (this.isMMKV || unRaw) {
//MMKV不支持getAll 反射遍历所有属性
HashMap<String, Any?>().also {
val properties = this::class.declaredMemberProperties
.filterIsInstance<KProperty1<PreferenceHolder, *>>()
for (p in properties) {
val prevAccessible = p.isAccessible
if (!prevAccessible) p.isAccessible = true
it[p.name] = p.get(this)
p.isAccessible = prevAccessible
}
}
} else {
preferences.all
}
总结
本篇探讨MMKV的getAll支持方法,目前如果是新项目建议大家使用属性委托或者封装key拼接类型的方式,便于后期数据导出。清明假期肛代码实属不易大家给个start吧 Preferences
Kotlin属性委托简化SharePreferences使用,支持多进程、MMKV、数据加解密、getAll,并优化sp的ANR。 (github.com)