注册

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 缩减为一设计。

Github:KeyValueX

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 项目中全面使用,欢迎测试反馈。

Github:KeyValueX

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 项目,感兴趣可自行查阅。


作者:KunMinX
链接:https://juejin.cn/post/7121955840319291428
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册