KeyValueX:消除样板代码,让 Android 项目不再 KV 爆炸
背景
源于深夜一段独白:
Key Value 定义几十上百个是常见事,目前有更简便方式么,
此为项目中为数不多不受控制之地,指数膨胀,且易埋下一致性隐患,
每新增一 value,需兼顾 key、get、put、init,5 处 …
public class Configs {
...
private static int TEST_ID;
public final static String KEY_TEST_ID = "KEY_TEST_ID";
public static void setTestId(int id) {
TEST_ID = id;
SPUtils.getInstance().put(KEY_TEST_ID, id);
}
public static int getTestId() {
return TEST_ID;
}
public static void initConfigs() {
TEST_ID = SPUtils.getInstance().getInt(KEY_TEST_ID, 0);
}
}
随后陆续收到改善建议,有小伙伴提到 “属性代理”,并推荐了群友 DylanCai 开源库 MMKV-KTX
与此同时,受 “属性代理” 启发,本人萌生 Java 下 key、value、get、put、init 缩减为一设计。
V1.0
V1.0 使用 3 步曲
1.如读写 POJO,需实现 Serializable 接口
public class User implements Serializable {
public String title;
public String content;
}
2.像往常一样,创建项目配置管理类,如 Configs
//Configs 中不再定义一堆 KEY、VALUE 常量和 get、put、init 静态方法,
//只需一条条 KeyValue 静态变量:
public class Configs {
public final static KeyValueString accountId = new KeyValueString("accountId");
public final static KeyValueSerializable<User> user = new KeyValueSerializable<>("user");
}
3.在页面等处通过 get( ) set( ) 方法读写 KeyValue
public class MainActivity extends AppCompatActivity {
...
//测试持久化写入
Configs.user.set(u);
//测试读取
Log.d("---title", Configs.user.get().title);
Log.d("---content", Configs.user.get().content);
}
V1.0 复盘
KeyValueX v1.0 一出,很快在群里引发热议,群友 DylanCai 提到多模块或存在 KeyName 重复问题,群友彭旭锐提议通过 “注解” 消除重复等问题。
与此同时我也发现,KeyValueX 虽然已消除 key、value、get、put、init 样板代码,但还是有两处一致性问题:
final 修饰符和 KeyName 一致性,
—— final 在 Java 下必写,以免开发者误给 KeyValue 直接赋值:
public class MainActivity extends AppCompatActivity {
...
//正常使用
Configs.user.set(u);
//误用
Configs.user = u;
}
那么有开发者可能说,我每次新增 KeyValue,通过 ctrl + D 复制行不就行了
public class Configs {
public final static KeyValueString accountId = new KeyValueString("accountId");
public final static KeyValueBoolean isAdult = new KeyValueBoolean("accountId");
}
确实这样可解决 final 一致性,但同时也会滋生 KeyName 一致性问题,也即记得改 KeyValue 变量名,忘改 KeyName,且这种疏忽编译器无法发现,唯有线上出事故专门排查时才可发现。
故综合多方面因素考虑,v2.0 采取注解设计:
V2.0
V2.0 使用 3 步曲
1.创建 KeyValueGroup 接口
@KeyValueGroup
public interface KeyValues {
@KeyValue KeyValueInteger days();
@KeyValue KeyValueString accountId();
@KeyValue KeyValueSerializable<User> user();
}
2.像往常一样,创建项目配置管理类,如 Configs
//Configs 中不再定义一堆 KEY、VALUE 常量和 get、put、init 静态方法,
//只需一条 KeyValueGroup 静态变量:
public class Configs {
public final static KeyValues keyValues = KeyValueCreator.create(KeyValues.class);
}
3.在页面等处通过 get( ) set( ) 方法读写 KeyValue
public class MainActivity extends AppCompatActivity {
...
//测试持久化写入
Configs.keyValues.user().set(u);
//测试读取
Log.d("---title", Configs.keyValues.user().get().title);
Log.d("---content", Configs.keyValues.user().get().content);
}
V2.0 复盘
V2.0 通过接口 + 注解设计,一举消除 final 和 KeyName 一致性问题,
且通过无参反射实现 KeyValues 的实例化,使写代码过程中无需特意 build 生成 Impl 类,对 build 一次便需数分钟的巨型项目较友好。
根据上图可见,无参反射加载类,耗时仅次于 new,故 V2.0 设计本人还算满意,已在 Java 项目中全面使用,欢迎测试反馈。
KeyValue-Dispatcher
期间群友 DylanCai 提出可改用动态代理实现,也即效仿 Retrofit,根据接口定义运行时动态生成方法,
如此无需声明注解,使接口定义更简洁,看起来就像:
public interface KeyValues {
KeyValueInteger days();
KeyValueString accountId();
KeyValueSerializable<User> user();
}
与此同时,可根据适配器模式实现个转换器,例如转换为 UnPeek-LiveData,如此即可顺带完成高频操作 —— 更新配置后通知部分页面刷新 UI。
public interface KeyValues {
Result<Integer> days();
Result<String> accountId();
Result<User> user();
}
Configs.keyValues.days().observer(this, result -> {
...
});
不过动态代理有个硬伤,即类名方法名不可混淆,不然运行时难调到对应方法,
故动态代理方式最终未考虑,不过转换器设计我甚是喜欢,加之 Java 后端 Properties 启发,故萌生 Dispatcher 设计 ——
基于 MVI-Dispatcher 实现 KeyValue-Dispatcher。具体思路即通过 HashMap 内聚 KeyValue,如此只需声明 Key,而无需考虑 value、getter、setter、init:
KV-D 使用 3 步曲
1.定义 Key 列表
public class Key {
public final static String TEST_STRING = "test_string";
public final static String TEST_BOOLEAN = "test_boolean";
}
2.读写
//读
boolean b = GlobalConfigs.getBoolean(Key.TEST_BOOLEAN);
//写
GlobalConfigs.put(Key.TEST_STRING, value);
3.顺带可通知 UI 刷新
GlobalConfigs.output(this, keyValueEvent -> {
switch (keyValueEvent.currentKey) {
case Key.TEST_STRING: ... break;
case Key.TEST_BOOLEAN: ... break;
}
});
依托 MVI-Dispatcher 消息聚合设计,任何由配置变更引发的 UI 刷新,皆从这唯一出口响应。
目前已更新至 MVI-Dispatcher 项目,感兴趣可自行查阅。
KV-D 复盘
KV-D 旨在消除学习成本,让开发者像 SPUtils 一样使用,与此同时自动达成内存快读、消除样板代码、规避不可预期错误。
不过 KV-D 只适合 Java 项目用。如欲于 Kotlin 实现属性代理,还需基于 KeyValueX 那类设计。
于是 KeyValueX 再做升级:
1.简化注解:只需接口处声明此为 KeyValueX 接口,
2.自动分组:以 KeyValueX 接口为单位生成路径 MD5,KeyName 根据 MD5 自动分组,
3.全局内存快读:如 ViewModelProvider 使用,并提供全局内存快读。
V3.0
V3.0 使用 2 步曲
1.创建 KeyValueGroup 接口,例如
@KeyValueX
public interface Configs {
KeyValueInteger days();
KeyValueString accountId();
KeyValueSerializable<User> user();
}
2.在页面等处通过 get( ) set( ) 方法读写 KeyValue
public class MainActivity extends AppCompatActivity {
private final Configs configs = KeyValueProvider.get(Configs.class);
...
//写
configs.user().set(user);
//读
configs.user().get().title;
configs.user().get().content;
}
已更新至 KeyValueX 项目,感兴趣可自行查阅。
链接:https://juejin.cn/post/7121955840319291428
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。