ArkConfig 配置模型与默认配置
ArkConfig 是 ArkPets 桌宠应用的核心配置模型类,以纯公有字段 + fastjson2 注解的方式定义了全部用户可配置项(行为、画布、显示、物理、渲染、窗口样式等),并通过内置默认配置资源 ArkPetsConfigDefault.json 实现"首次运行自动生成外部配置文件"的机制。
Purpose and Scope
本页面覆盖以下内容:
ArkConfig类的完整字段模型、默认值与版本演进(@since标注)- 配置的加载流程(内部默认配置 → 外部自定义配置)与保存流程
- 内置默认配置文件
assets/ArkPetsConfigDefault.json的作用与结构 - 敏感字段(MirrorChyan CDK)的弱加密存取
getMcCdk()/setMcCdk() - 值转换辅助方法(缓动函数、颜色、描边模式)与
RenderOutline枚举 - 失败模式、边界情况与并发注意事项
以下相关主题由兄弟页面承接,本页不展开:
- 配置文件路径常量(
Const.configExternal/Const.configInternal)与其它路径常量:参见 常量定义与路径约定 - 桌面启动器中读写配置的 UI 交互(
StartupConfig、控制器模块):参见 启动器与桌面端模块 - 遥测系统对
@PrivacyField标注字段的处理逻辑:参见 遥测与隐私处理
Overview
ArkPets 需要在跨平台(Windows/macOS/Linux)的桌面环境中持久化用户偏好。设计上采用扁平化的 JSON 配置模型:
- 单文件单模型:所有配置项收敛在一个
ArkConfig实例中,对应一个外部 JSON 文件ArkPetsConfig.json(位于工作目录,见Const.configExternal)。 - 默认配置内置于 Jar:通过 Java 资源
/ArkPetsConfigDefault.json(Const.configInternal,实际文件为assets/ArkPetsConfigDefault.json)随包分发,保证任何环境下都能恢复出厂默认。 - 双层默认值机制:每个字段通过 fastjson2 的
@JSONField(defaultValue = "...")声明代码级默认值;同时内置 JSON 文件提供文件级默认值。二者保持一致,前者兜底解析缺失字段,后者作为生成外部文件的模板。 - 纯公有字段:
ArkConfig使用 snake_case 公有字段而非 getter/setter Bean 风格,由 fastjson2 直接按字段名反序列化,减少样板代码;构造函数为private,实例只能通过静态工厂方法获得。
这种设计的意图是:用户可以手工编辑 JSON 文件(字段名即文档),而程序在字段缺失、值非法时仍能以安全默认值运行,避免启动崩溃。
Architecture
架构要点说明:
ArkConfig是唯一配置入口:它同时持有内部默认资源 URL(configDefault,类加载时通过Objects.requireNonNull保证存在)与外部自定义文件File(configCustom)两个静态定位符。- fastjson2 是唯一的序列化引擎:读取用
JSONObject.parseObject(text, ArkConfig.class),写出用JSON.toJSONString(this, PrettyFormat),保证字段名与 JSON 键完全对应。 Const常量类解耦路径知识:ArkConfig不硬编码任何路径或正则,全部来自Const,便于集中维护。- 敏感字段旁路序列化:CDK 的读写方法都标注
@JSONField(serialize = false)或(deserialize = false),避免加密/解密方法被 fastjson2 当作 Bean 属性序列化。
配置加载与保存的完整控制流
getConfig():主加载入口
1public static ArkConfig getConfig() {
2 if (!configCustom.exists()) {
3 // Use the default config if the external config file does not exist.
4 isNewcomer = true;
5 ArkConfig config = getDefaultConfig();
6 if (config != null)
7 config.save();
8 return getDefaultConfig();
9 } else {
10 return getConfig(configCustom);
11 }
12}Source: ArkConfig.java
关键行为逐行解析:
- 外部文件不存在:置静态标志
isNewcomer = true,随后读取内部默认配置、立即调用save()把默认值物化为外部ArkPetsConfig.json(这一步完成"首次运行生成配置文件"),最后再返回一份默认配置实例。 - 外部文件存在:走
getConfig(File)分支解析用户自定义配置。 - 注意
isNewcomer是类级静态布尔而非实例字段——启动器可据此判断是否为首次使用(例如弹出新手引导)。
getConfig(File):解析指定配置文件
1public static ArkConfig getConfig(File configFile) {
2 if (configFile.exists()) {
3 // Read and parse the config file.
4 try {
5 return Objects.requireNonNull(
6 JSONObject.parseObject(FileUtil.readString(configFile, charsetDefault), ArkConfig.class),
7 "JSON parsing returns null."
8 );
9 } catch (IOException | NullPointerException e) {
10 Logger.error("Config", "Failed to get the custom config, details see below.", e);
11 }
12 }
13 return null;
14}Source: ArkConfig.java
- 以
charsetDefault(即UTF-8)读取,规避平台默认编码差异。 Objects.requireNonNull(..., "JSON parsing returns null.")把 fastjson2 可能返回 null 的情况统一转为带消息的NullPointerException,再被同一catch捕获记录——失败不抛出,返回null交由调用方决策。- 解析时,JSON 中缺失的字段会取
@JSONField(defaultValue = "...")声明的代码级默认值。
getDefaultConfig():读取内置默认配置
1public static ArkConfig getDefaultConfig() {
2 try (InputStream inputStream = configDefault.openStream()) {
3 return Objects.requireNonNull(
4 JSONObject.parseObject(new String(inputStream.readAllBytes(), charsetDefault), ArkConfig.class),
5 "JSON parsing returns null."
6 );
7 } catch (IOException e) {
8 Logger.error("Config", "Failed to get the default config, details see below.", e);
9 }
10 return null;
11}Source: ArkConfig.java
使用 try-with-resources 打开类路径资源流,读完即关闭。由于 configDefault URL 在类初始化时已 requireNonNull 校验,此处的 IOException 主要覆盖流读取失败而非资源缺失。
save():持久化到外部文件
1@JSONField(serialize = false)
2public void save() {
3 try {
4 FileUtil.writeString(configCustom, charsetDefault, JSON.toJSONString(this, PrettyFormat), false);
5 Logger.debug("Config", "Config saved");
6 } catch (IOException e) {
7 Logger.error("Config", "Config saving failed, details see below.", e);
8 }
9}Source: ArkConfig.java
PrettyFormat 让生成的 ArkPetsConfig.json 带缩进、便于用户手工编辑;save() 同样吞掉 IOException 仅记日志,不会中断程序流程——配置保存失败属于可降级错误(下次启动仍可用旧文件或默认值)。
时序图:首次运行 vs 常规运行
字段模型与分组
全部字段为公有字段、snake_case 命名、按字母序排列,并通过 Javadoc @since ArkPets X.Y 标注引入版本。以下按功能域分组(字段总数约 47 个,完整清单见源码):
1/** @since ArkPets 1.0 */ @JSONField(defaultValue = "4")
2public int behavior_ai_activation;
3/** @since ArkPets 1.0 */ @JSONField(defaultValue = "true")
4public boolean behavior_allow_interact;
5/** @since ArkPets 3.6 */ @JSONField(defaultValue = "false")
6public boolean behavior_allow_sleep;
7/** @since ArkPets 3.9 */ @JSONField(defaultValue = "30.0")
8public float behavior_walk_speed;
9/** @since ArkPets 3.3 */ @JSONField(defaultValue = "#00000000")
10public String canvas_color;
11/** @since ArkPets 2.0 */ @JSONField()
12public String character_asset;
13/** @since ArkPets 3.5 */ @JSONField()
14public JSONObject character_favorites;
15/** @since ArkPets 3.9 */ @JSONField() @PrivacyField()
16public String download_mc_cdk;
17/** @since ArkPets 3.13 */ @JSONField(defaultValue = "true")
18public boolean enable_telemetry;
19/** @since ArkPets 2.2 */ @JSONField(defaultValue = "800.0")
20public float physic_gravity_acc;
21/** @since ArkPets 3.3 */ @JSONField(defaultValue = "#FFFF00FF")
22public String render_outline_color;
23/** @since ArkPets 3.5 */ @JSONField(defaultValue = "EASE_OUT_CUBIC")
24public String transition_type;Source: ArkConfig.java
设计意图分析:
defaultValue以字符串书写:fastjson2 会按字段类型把字符串转换为对应数值/布尔值,使注解形式统一("4"、"true"、"#FFFF00FF")。- 无默认值的字段(
character_asset、character_favorites、character_files、character_label、download_mc_cdk、user_announcement_read)都是"用户数据"而非"偏好设置",语义上没有出厂默认,反序列化后由调用方判空处理。 @PrivacyField()标注(download_mc_cdk、user_announcement_read)标记这些字段包含隐私数据,遥测上报时会被过滤(详见遥测相关页面)。JSONObject类型的字段(character_favorites、character_files、user_announcement_read)用于存储动态键值结构,模型刻意不为其定义强类型子模型,保持 schema 演进弹性。
内置默认配置文件
assets/ArkPetsConfigDefault.json 是打包进 Jar 的默认配置模板,与代码级 defaultValue 完全对齐(节选):
1{
2 "behavior_ai_activation":4,
3 "behavior_allow_interact":true,
4 "behavior_allow_sleep":false,
5 "behavior_walk_speed": 30.0,
6 "canvas_color":"#00000000",
7 "character_asset":"",
8 "character_favorites":{},
9 "display_fps":60,
10 "display_scale":1.0,
11 "enable_telemetry":true,
12 "logging_level":"INFO",
13 "physic_gravity_acc":800.0,
14 "render_outline_color":"#FFFF00FF",
15 "transition_duration":0.3,
16 "transition_type":"EASE_OUT_CUBIC",
17 "window_style_toolwindow":true,
18 "window_style_topmost":true
19}Source: ArkPetsConfigDefault.json
它与 Const 中定义的路径常量一一对应:
1// Encoding presets
2public static final String charsetDefault = "UTF-8";
3
4// Paths of static files and internal files
5public static final String configExternal = "ArkPetsConfig.json";
6public static final String configInternal = "/ArkPetsConfigDefault.json";Source: Const.java
即:外部文件固定为工作目录下的 ArkPetsConfig.json,内置资源固定为类路径根的 /ArkPetsConfigDefault.json。
值转换与工具方法
由于 JSON 中只能保存原始类型(String / int / float / boolean),运行期所需的枚举与 libGDX 类型由静态转换方法按需转换,转换失败一律降级为安全默认值并告警,而非抛异常:
1/** @see EasingFunction */
2public static EasingFunction getEasingFunctionFrom(String string) {
3 try {
4 return EasingFunction.valueOf(string);
5 } catch (IllegalArgumentException e) {
6 Logger.warn("Config", "Invalid easing function, using linear");
7 return EasingFunction.LINEAR;
8 }
9}
10
11/** @see Color */
12public static Color getGdxColorFrom(String string) {
13 Color color;
14 if (hexColorRegex.matcher(string).matches()) {
15 color = Color.valueOf(string);
16 } else {
17 Logger.warn("Config", "Invalid color config, using transparent");
18 color = Color.CLEAR;
19 }
20 return color;
21}
22
23/** @see RenderOutline */
24public static RenderOutline getRenderOutlineFrom(int ordinal) {
25 if (ordinal >= RenderOutline.values().length)
26 ordinal = 0;
27 return RenderOutline.values()[ordinal];
28}Source: ArkConfig.java
getGdxColorFrom依赖Const.hexColorRegex(^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$,见 Const.java)校验#RRGGBB(AA)形式的颜色串。getRenderOutlineFrom通过序数(ordinal)映射,越界时回退到NEVER(序数 0)。注意如果传入负数,values()[ordinal]会抛出ArrayIndexOutOfBoundsException,源码只防御了上界——这是一个已知的边界行为。
render_outline 等配置项使用嵌套枚举 RenderOutline 表示描边显示时机:
1/** Config options for render outline. */
2public enum RenderOutline {
3 NEVER,
4 DRAGGING,
5 PRESSING,
6 FOCUSED,
7 _RESERVED,
8 ALWAYS
9}Source: ArkConfig.java
序数值即 JSON 中存储的整数值(render_outline 默认 1 对应 DRAGGING,render_outline_emphasis 默认 3 对应 FOCUSED)。_RESERVED 是被移除功能的保留占位,用于维持后续枚举序数的向后兼容——这是以 ordinal 做持久化格式时必须保留历史槽位的典型做法。
敏感字段:MirrorChyan CDK 的弱加密存取
download_mc_cdk 存储的是资源站 MirrorChyan 的 CDK(兑换码)。出于"避免明文落盘"的考虑,字段中保存的是经 SecretUtils.WeakEncryptionV0 加密后的密文:
1/** Gets the MirrorChyan CDK.
2 * @return The decrypted CDK, or {@code null} if the CDK is not set or decryption failed.
3 */
4@JSONField(serialize = false)
5public String getMcCdk() {
6 if (download_mc_cdk != null && !download_mc_cdk.isEmpty()) {
7 try {
8 String result = new SecretUtils.WeakEncryptionV0().decrypt(download_mc_cdk);
9 Logger.debug("Config", "Decrypt MirrorChyan CDK okay");
10 return result;
11 } catch (GeneralSecurityException e) {
12 Logger.error("Config", "Failed to decrypt MirrorChyan CDK, details see below.", e);
13 }
14 }
15 return null;
16}
17
18/** Sets the MirrorChyan CDK.
19 * @param string The CDK to set. If the string is {@code null} or empty, the CDK will be cleared.
20 */
21@JSONField(deserialize = false)
22public void setMcCdk(String string) throws GeneralSecurityException {
23 if (string != null && !string.isEmpty()) {
24 try {
25 download_mc_cdk = new SecretUtils.WeakEncryptionV0().encrypt(string);
26 Logger.debug("Config", "Encrypt MirrorChyan CDK okay");
27 return;
28 } catch (GeneralSecurityException e) {
29 Logger.error("Config", "Failed to encrypt MirrorChyan CDK, details see below.", e);
30 throw e;
31 }
32 }
33 download_mc_cdk = "";
34}Source: ArkConfig.java
不对称的错误语义值得注意:
getMcCdk():解密失败仅记录错误并返回null(视为"未设置"),不抛出——保证读取路径永不阻塞。setMcCdk():加密失败记录错误后重抛GeneralSecurityException,让调用方(通常是设置界面的保存流程)感知失败并提示用户。- 两个方法分别标注
@JSONField(serialize = false)/(deserialize = false),防止 fastjson2 把getMcCdk/setMcCdk识别为 Bean 属性而在序列化时双重处理(否则 JSON 里会出现多余的mcCdk键)。字段本身仍以download_mc_cdk密文形式正常序列化。 - 类名
WeakEncryptionV0自述其安全强度:这是防君子不防小人的混淆级加密(密钥内嵌于开源代码),目的是防止普通用户在共享配置截图/文件时意外泄露 CDK。
配置项参考表
按功能域整理主要配置项(以代码级 defaultValue 与内置 JSON 为准):
| 配置项 | 类型 | 默认值 | 引入版本 | 说明 |
|---|---|---|---|---|
behavior_ai_activation | int | 4 | 1.0 | AI 行为激进度 |
behavior_allow_interact | boolean | true | 1.0 | 允许点击交互 |
behavior_allow_sit | boolean | true | 1.0 | 允许坐下行为 |
behavior_allow_sleep | boolean | false | 3.6 | 允许睡眠行为 |
behavior_allow_special | boolean | true | 3.6 | 允许特殊行为 |
behavior_allow_walk | boolean | true | 1.0 | 允许行走行为 |
behavior_direction_switching | int | 1 | 3.11 | 朝向切换策略 |
behavior_do_peer_repulsion | boolean | true | 1.6 | 多桌宠之间互斥 |
behavior_walk_speed | float | 30.0 | 3.9 | 行走速度 |
canvas_color | String | #00000000 | 3.3 | 采样画布颜色 |
canvas_coverage | float | 0.8 | 3.8 | 画布覆盖率 |
canvas_sampling_interval | int | 4 | 3.8 | 画布采样间隔 |
character_asset | String | (无默认) | 2.0 | 当前角色资源标识 |
character_favorites | JSONObject | (无默认) | 3.5 | 角色收藏夹 |
character_files | JSONObject | (无默认) | 2.2 | 角色模型文件映射 |
character_label | String | (无默认) | 2.0 | 当前角色显示名 |
display_fps | int | 60 | 1.0 | 渲染帧率 |
display_margin_bottom | int | 0 | 1.0 | 底边距(悬空高度) |
display_multi_monitors | boolean | true | 2.1 | 允许跨多显示器 |
display_scale | float | 1.0 | 1.0 | 缩放比例 |
download_mc_cdk | String | (无默认) | 3.9 | MirrorChyan CDK(弱加密,@PrivacyField) |
eco_mode | boolean | false | 3.9 | 节能模式 |
enable_telemetry | boolean | true | 3.13 | 启用遥测 |
initial_position_x | float | 0.2 | 3.2 | 初始横坐标(相对比例) |
initial_position_y | float | 0.2 | 3.2 | 初始纵坐标(相对比例) |
launcher_solid_exit | boolean | true | 3.0 | 启动器退出时结束桌宠进程 |
logging_level | String | INFO | 2.0 | 日志级别 |
opacity_dim | float | 0.75 | 3.3 | 失焦暗淡不透明度 |
opacity_normal | float | 1.0 | 3.3 | 正常不透明度 |
physic_gravity_acc | float | 800.0 | 2.2 | 重力加速度 |
physic_air_friction_acc | float | 100.0 | 2.2 | 空气摩擦加速度 |
physic_static_friction_acc | float | 500.0 | 2.2 | 静摩擦加速度 |
physic_speed_limit_x | float | 1000.0 | 2.2 | 水平速度上限 |
physic_speed_limit_y | float | 1000.0 | 2.2 | 垂直速度上限 |
render_animation_mixture | float | 0.3 | 3.5 | 动画过渡混合度 |
render_enable_mipmap | boolean | true | 3.8 | 启用 mipmap |
render_outline | int | 1 | 3.3 | 描边时机(RenderOutline 序数) |
render_outline_color | String | #FFFF00FF | 3.3 | 描边颜色 |
render_outline_emphasis | int | 3 | 3.9 | 强调描边时机(序数) |
render_outline_emphasis_color | String | #FFBB00FF | 3.9 | 强调描边颜色 |
render_outline_width | float | 2.0 | 3.3 | 描边宽度 |
render_shader_high_quality | boolean | true | 3.12 | 高质量着色器 |
render_shadow_color | String | #000000BB | 3.6 | 阴影颜色 |
transition_duration | float | 0.3 | 3.5 | 过渡动画时长(秒) |
transition_type | String | EASE_OUT_CUBIC | 3.5 | 缓动函数名(EasingFunction) |
user_announcement_read | JSONObject | (无默认) | 3.7 | 已读公告(@PrivacyField) |
window_style_toolwindow | boolean | true | 3.2 | 工具窗口样式(不在任务栏显示) |
window_style_topmost | boolean | true | 3.2 | 置顶显示 |
API Reference
public static ArkConfig getConfig()
读取默认外部配置文件 ArkPetsConfig.json 并返回 ArkConfig 实例;若文件不存在,则先以内置默认配置生成该文件(isNewcomer = true)。
返回: ArkConfig 实例;任何读取/解析失败时返回 null。
public static ArkConfig getConfig(File configFile)
解析指定配置文件。
参数:
configFile(java.io.File):配置文件路径
返回: ArkConfig 实例;文件不存在或读取/解析失败(IOException / NullPointerException)时返回 null,失败详情写入 Logger。
public static ArkConfig getDefaultConfig()
从类路径资源 /ArkPetsConfigDefault.json 读取默认配置。
返回: 默认 ArkConfig 实例;IOException 时返回 null。
public void save()
以 UTF-8 + PrettyFormat 将当前实例序列化并覆盖写入外部文件 ArkPetsConfig.json。
抛出: 无(IOException 被捕获并记录,不外抛)。
public boolean isNewcomer()
返回: 最近一次 getConfig() 调用时外部配置文件是否为新生成。
public String getMcCdk() / public void setMcCdk(String string)
MirrorChyan CDK 的解密读取与加密写入(详见前文)。
setMcCdk 抛出: GeneralSecurityException——加密失败时。
public static EasingFunction getEasingFunctionFrom(String string)
返回: 对应的缓动函数枚举;非法名返回 EasingFunction.LINEAR(仅告警)。
public static Color getGdxColorFrom(String string)
返回: libGDX Color;不匹配 hexColorRegex 时返回 Color.CLEAR(仅告警)。
public static RenderOutline getRenderOutlineFrom(int ordinal)
返回: 按序数取 RenderOutline;ordinal 越上界时回退为序数 0(NEVER)。负数序数未防御,会抛出 ArrayIndexOutOfBoundsException。
失败模式、边界情况与并发
| 场景 | 行为 | 源码依据 |
|---|---|---|
| 外部配置文件不存在 | 置 isNewcomer = true,用内置默认生成并落盘 | getConfig() 分支 1 |
| 外部 JSON 语法损坏 / 字段类型不符 | JSONObject.parseObject 抛异常被捕获,记错误日志,返回 null | getConfig(File) |
| JSON 中缺失某字段 | fastjson2 填充 @JSONField(defaultValue) | 注解声明 |
| 内置资源读取失败(极少见,资源缺失会在类加载时 NPE) | 记错误日志,返回 null | getDefaultConfig() 与静态初始化 requireNonNull |
save() 写盘失败(磁盘满/权限) | 记错误日志,不抛出,程序继续 | save() |
| 颜色串非法 | 返回 Color.CLEAR 透明色 + 告警 | getGdxColorFrom |
| 缓动函数名非法 | 返回 LINEAR + 告警 | getEasingFunctionFrom |
render_outline 序数越上界 | 回退 NEVER;负数未防御会抛 AIOOBE | getRenderOutlineFrom |
| CDK 解密失败 | 返回 null 视为未设置;设置失败则重抛 GeneralSecurityException | getMcCdk / setMcCdk |
并发注意事项:
isNewcomer是静态可变字段且未做同步保护;并发调用getConfig()可能产生竞态。当前实际调用场景(应用启动单线程)不会触发,但新增并发加载逻辑时需注意。ArkConfig实例本身未做任何同步;多线程同时读写字段(尤其character_favorites这类JSONObject)需要调用方自行保证可见性与互斥。save()是全量覆盖写,无原子写/临时文件交换,写盘中断可能留下截断的 JSON 文件——下次启动将走"解析失败返回 null"路径。
性能与扩展说明
- 全量内存模型 + 全量序列化:配置整体很小(几十个标量字段 + 3 个
JSONObject),读取即一次parseObject,保存即一次toJSONString,无任何缓存层,性能不构成瓶颈。 - 新增配置项的扩展步骤:在 ArkConfig.java 中按字母序添加公有字段并标注
@since与@JSONField(defaultValue = "...");同时同步更新 ArkPetsConfigDefault.json 以保持双层默认值一致。旧配置文件因字段缺失会自动落回默认值,天然向后兼容。 - 扩展为强类型子模型:
character_favorites等当前用JSONObject存动态结构;若需强类型校验,可定义 POJO 替换,fastjson2 反序列化无需其它改动。
Related Links
- 源码:ArkConfig.java
- 源码:ArkPetsConfigDefault.json
- 源码:Const.java
- 相关主题(兄弟页面):常量定义与路径约定、启动器与桌面端模块、遥测与隐私处理