我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了
关于 
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的服务吧!
最后
开头也说了:“ 我正在参加「创意开发 投稿大赛」”,如果觉得还不错,就帮忙点个赞吧!如果觉得有问题,也欢迎评论提出来!感谢你的认可!
这人代码竟然不写注释,快来吐槽他!(我觉得不写注释的注释才是最好的注释!)