让MMKV支持getAll

1,045 阅读3分钟

前言

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");
}

如何解决

  1. MMKV缺陷:不支持getAll? (juejin.cn)

    掘金这篇文章针对MMKV封装提供读写数据方法提供了解决方案,原理很简单在写入数据key时候拼接类型后缀,解析数据时候就知道key对应存储的类型了。

  2. 有了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

  1. 获取PreferenceHolder继承类的所有非扩展属性

     val properties = this::class.declaredMemberProperties
    
  2. 过滤出所有委托的属性

    val delegates = properties.filterIsInstance<KProperty1<PreferenceHolder, *>>()
    
  3. 循环遍历所有委托属性,反射调用属性的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)