1摩尔配置项要弄,怎么破?

576 阅读4分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了

关于

MoleConfig 希望即使有1摩尔配置项也能轻松搞定!

在网上看到有人写了个 KeyValueX 来快捷处理大量配置项问题,个人觉得还是不够方便。刚好有个想法,所以也写了个,而且项目中也用得上。

全局初始化

项目中使用MMKV来存储数据,也可以修改为其它工具,自行实现DataStore接口即可。

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        MMKV.initialize(this)
        val kv = MMKV.defaultMMKV()
        val store = KvStore(kv)
        MoleConfig.initTypeHandler(store)
    }
}

定义配置组

接口化定义配置组,每组配置相互独立,支持同名配置项。比如UserConfig.name和AppConfig.name可以同时存在。 支持定义配置项默认值,@DefaultConfig即可。默认值数据类型为了简单,固定为String类型。不然就得一一定义,如IntDefaultConfig,LongDefaultConfig等,感觉这样有点烦,所以就用了固定的。

interface UserConfig : KeepMoleConfig {
    var serializable: AccountS?
    var parcelable: AccountP?

    var string: String
    var int: Int
    var long: Long
    var float: Float
    var double: Double
    var boolean: Boolean

    @DefaultConfig("123")
    var stringDefaultConfig: String

    @DefaultConfig("123")
    var intDefaultConfig: Int

    @DefaultConfig("123")
    var longDefaultConfig: Long

    @DefaultConfig("123")
    var floatDefaultConfig: Float

    @DefaultConfig("123")
    var doubleDefaultConfig: Double

    @DefaultConfig("true")
    var booleanDefaultConfig: Boolean
}

使用示例

以下使用示例,都测试通过。

    @Test
    fun usage() {
        val config: UserConfig = getMoleConfigInstance()

        assertEquals("123", config.stringDefaultConfig)
        assertEquals(123, config.intDefaultConfig)
        assertEquals(123L, config.longDefaultConfig)
        assertEquals(123f, config.floatDefaultConfig)
        assertEquals(123.0, config.doubleDefaultConfig, 0.0)
        assertEquals(true, config.booleanDefaultConfig)

        assertEquals(null, config.string)//default value
        config.string = "1"
        assertEquals("1", config.string)

        assertEquals(0, config.int)//default value
        config.int = 1
        assertEquals(1, config.int)

        assertEquals(0L, config.long)//default value
        config.long = 1
        assertEquals(1L, config.long)

        assertEquals(0f, config.float)//default value
        config.float = 1f
        assertEquals(1f, config.float)

        assertEquals(0.0, config.double, 0.0)//default value
        config.double = 1.0
        assertEquals(1.0, config.double, 0.0)

        assertEquals(false, config.boolean)//default value
        config.boolean = true
        assertEquals(true, config.boolean)

        assertEquals(null, config.serializable?.name)//default value
        val accountS = AccountS().apply { name = "123" }
        config.serializable = accountS
        assertEquals(accountS.name, config.serializable?.name)

        assertEquals(null, config.parcelable?.name)//default value
        val accountP = AccountP().apply { name = "123" }
        config.parcelable = accountP
        assertEquals(accountP.name, config.parcelable?.name)
    }

原理

Kotlin属性代理

用Kotlin定义的属性会自动生成相应的getXXX和setXXX方法,然后通过Java的动态代理Proxy具体实现功能。

interface ConfigInvocationHandler {
    fun invoke(method: Method, args: Array<*>?): Any? {
        val typeHandler = MoleConfig.typeHandlerFinder.find(method.configType)
        val key = MoleConfig.configKeyGenerator.gen(method)
        return if (method.isGet) {
            typeHandler.get(method, key)
        } else {
            typeHandler.set(method, key, args!!.first())
        }
    }
}

configType 配置项数据类型

如果是get方法,可通过返回类型来得知配置项的数据类型。如果是set方法,则为第一个参数类型。

val Method.isGet: Boolean get() = name.startsWith("get")
private val Method.configType: Class<*>
    get() {
        return if (isGet) returnType
        else parameterTypes.first()
    }

TypeHandler 配置项数据类型处理器

通过接口化的方式来处理配置项的每个数据类型,满足自定义需要。也提供了常见数据类型的实现,满足普通需要。

interface TypeHandler<T> {
    fun get(method: Method, key: String): T?
    fun set(method: Method, key: String, value: Any?)
}

class IntHandler(private val store: DataStore) : TypeHandler<Int> {
    override fun get(method: Method, key: String): Int {
        return store.getInt(key, method.defaultConfig?.value?.toInt() ?: 0)
    }

    override fun set(method: Method, key: String, value: Any?) {
        if (value != null) store.putInt(key, value as Int)
        else store.remove(key)
    }
}

配置项键名生成器

默认为全路径名,有特别需要的,可自行实现,然后设置给MoleConfig.configKeyGenerator。

interface ConfigKeyGenerator {
    fun gen(method: Method): String {
        val key = method.name.substring(3)//getXXX or setXXX
        return "${method.declaringClass.name}/$key"
    }
}

TypeHandlerFinder 配置项数据类型处理器之查找器

如果要增加配置项新数据类型,需要自行实现TypeHandlerFinder和相应的TypeHandler,然后配置给MoleConfig.typeHandlerFinder和MoleConfig.typeHandlers。以下是默认实现。

interface TypeHandlerFinder {
    @Suppress("IfThenToElvis")
    fun find(configType: Class<*>): TypeHandler<out Any?> {
        val typeHandler = MoleConfig.typeHandlers[configType]
        return if (typeHandler != null) {
            typeHandler
        } else if (configType.isAssignableTo(Parcelable::class.java)) {
            MoleConfig.typeHandlers[Parcelable::class.java]!!
        } else if (configType.isAssignableTo(Serializable::class.java)) {
            MoleConfig.typeHandlers[Serializable::class.java]!!
        } else {
            throw IllegalStateException("no fit typeHandler found for :$configType")
        }
    }
}

默认配置

项目中尽量都使用接口的方式开发,并提供默认实现,而不是写死,以便适配自定义需求。 以下是默认配置,可根据需要适当调整。数据类型实现了常用的以下几种,其它类型可自行实现。

object MoleConfig {
    val typeHandlers: MutableMap<Class<*>, TypeHandler<*>> = mutableMapOf()
    var typeHandlerFinder: TypeHandlerFinder = object : TypeHandlerFinder {}
    var configKeyGenerator: ConfigKeyGenerator = object : ConfigKeyGenerator {}
    var configInvocationHandler: ConfigInvocationHandler = object : ConfigInvocationHandler {}

    fun initTypeHandler(store: DataStore) {
        StringHandler(store).install(String::class)
        IntHandler(store).install(Integer::class)
        LongHandler(store).install(java.lang.Long::class)
        FlotHandler(store).install(java.lang.Float::class)
        BooleanHandler(store).install(java.lang.Boolean::class)
        DoubleHandler(store).install(java.lang.Double::class)
        ParcelableHandler(store).install(Parcelable::class)
        SerializableHandler(store).install(Serializable::class)
    }
}

关于混淆

已经集成了混淆规则,配置组和Serializable数据类型需要实现KeepMoleConfig,以自动处理混淆。 Parcelable数据类型,安卓已经自动处理了,不需要额外的规则。

-keep,allowshrinking class * implements  com.dhy.moleconfig.KeepMoleConfig{*;}

其它说明

  • 当前方案比较灵活,未实现的数据类型不能在编码和编译阶段发现。建议所有配置组都写测试用例,以确保功能正常。
  • 根据Java Serializable原理分析可知,Serializable类似Json,增删字段没问题(新旧数据存取正常),更名不行。(未测试,只是理论分析)
  • Parcelable(常用实现)是顺序存取数据,更名没问题,改类型、增删字段不行。(未测试,只是理论分析)
  • 基于以上原因,请谨慎使用Serializable和Parcelable数据类型。
  • 个人觉得现在已经可以非常放心的使用Kotlin了,老项目中使用也不会出现兼容性问题。请放轻松,安心享受Kotlin的服务吧!

最后

开头也说了:“ 我正在参加「创意开发 投稿大赛」”,如果觉得还不错,就帮忙点个赞吧!如果觉得有问题,也欢迎评论提出来!感谢你的认可!

这人代码竟然不写注释,快来吐槽他!(我觉得不写注释的注释才是最好的注释!)