Repository Wiki
isHarryh/Ark-Pets

参数解析与开机自启动

本文介绍 Ark-Pets 桌面端的"开机自启动"能力:它如何通过 StartupConfig 平台抽象层在 Windows 上以 COM 接口创建启动文件夹快捷方式(.lnk),并向 ArkPets.exe 注入 --direct-start 启动参数,以及在启动器设置界面(SettingsModule)中的交互流程与失败处理。

Purpose and Scope

本页覆盖以下内容(均以 v3.x 分支源码为依据):

  • 开机自启动的完整链路:从设置界面复选框 → StartupConfig 抽象工厂 → WindowsStartupConfig 的 COM 快捷方式创建 → 启动文件夹中的 ArkPetsStartup.lnk。
  • --direct-start 启动参数:该参数由自启动快捷方式写入并传给 ArkPets.exe,是连接"自启动"与"启动参数"两个主题的关键纽带。
  • 平台判定与空实现:非 Windows 平台下通过 NullStartupConfig 空对象模式优雅降级。
  • 旧版本迁移逻辑:从 ArkPetsStartupService.vbs 脚本方案迁移到快捷方式方案。

本页不覆盖(留给兄弟页面):

  • 启动器主程序对全部命令行参数的完整解析流程(本页仅验证 --direct-start 参数的注入侧;桌面端 Desktop 启动器内部的参数分发实现未纳入本页证据范围,属于启动器启动流程页面)。
  • ArkConfig 配置文件的持久化与序列化(见配置文件相关页面)。
  • 自动更新的下载与安装流程(isAutoUpdateAvailable() 仅是本类暴露的一个可用性探测,见更新机制页面)。

Overview

Ark-Pets 是一款桌面宠物软件,"开机自启动"让用户登录 Windows 后自动启动宠物程序。其设计要点:

关注点实现方式
启用机制在用户启动文件夹(Start Menu/Programs/Startup)创建快捷方式 ArkPetsStartup.lnk
目标程序ArkPets.exe,工作目录为当前运行目录(user.dir)
启动参数--direct-start(跳过启动器确认环节的直达启动信号)
底层技术JNA + Windows COM(IShellLinkW / IPersistFile)原生调用
跨平台StartupConfig.getInstance() 工厂 + NullStartupConfig 空对象
UI 入口启动器设置面板 SettingsModule 中的"开机自启动"复选框

为什么用"启动文件夹快捷方式"而不是写注册表 Run 键? 从源码可推断的设计意图是:快捷方式方案无需管理员权限、不污染注册表,且与 Ark-Pets 的绿色/便携安装方式兼容(只需 ArkPets.exe 存在于工作目录即可用);同时 .lnk 文件天然对用户可见、可手工删除,失败模式温和。

为什么需要 --direct-start 参数? 开机自启场景下不应再弹出启动器窗口等待用户操作,快捷方式通过 SetArguments("--direct-start") 把"直达启动"意图编码进命令行,由可执行文件侧解析后直接进入宠物运行流程。

Architecture

整体架构分为三层:桌面端 UI 层(desktop 模块)、平台抽象层(core 模块 cn.harryh.arkpets.platform)、操作系统层(Windows COM 与文件系统)。

Loading diagram...

各组件职责:

  • SettingsModule(desktop 模块):JavaFX 设置面板控制器,持有"开机自启动"复选框 configAutoStartup,负责初始状态回显(isSetStartup())、切换事件(addStartup()/removeStartup())以及成功/失败对话框提示。
  • StartupConfig(core 模块):抽象基类,同时承担工厂角色——静态方法 getInstance() 依据 JNA 的 Platform.isWindows() 返回平台实现,对上层屏蔽平台差异。
  • WindowsStartupConfig:Windows 实现,封装可用性探测、旧版 VBS 脚本迁移、COM 快捷方式创建/删除。其内部包含两个私有静态 COM 包装类 IShellLink 与 IPersistFile。
  • NullStartupConfig:空对象实现,addStartup() 恒返回 true、removeStartup() 为空操作,保证非 Windows 平台调用链安全。
  • 启动文件夹:%USERPROFILE%/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup,Windows 登录时会执行其中的快捷方式。

类型层级如下(方法签名均取自源码):

Loading diagram...

注意 IShellLink 与 IPersistFile 是 WindowsStartupConfig 的私有静态内部类,继承 JNA 的 Unknown,通过 COM vtable 原生调用(_invokeNativeInt)执行方法,是整个功能中唯一触及原生代码的部分。

Core Flow

工厂与可用性探测(构造阶段)

StartupConfig.getInstance() 是全功能唯一入口。上层(如 SettingsModule)调用后即得到一个已完成自检的实例——这是设计上的巧妙之处:WindowsStartupConfig 的构造函数把"目录存在、可执行文件存在"的检查前置到构造期,失败则置 available = false,后续所有操作都以该标志短路:

java
1public static StartupConfig getInstance() { 2 if (Platform.isWindows()) { 3 return new WindowsStartupConfig(); 4 } 5 return new NullStartupConfig(); 6}

Source: StartupConfig.java

构造函数中同时完成旧版本迁移检查——若启动文件夹里存在历史遗留的 ArkPetsStartupService.vbs 脚本,则删除并以新方案重建,整个迁移过程被独立 try/catch 包裹,失败仅记日志、不影响主流程:

java
1public WindowsStartupConfig() { 2 try { 3 File startupDir = new File(System.getProperty("user.home") + "/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup"); 4 if (!startupDir.isDirectory()) 5 throw new FileNotFoundException("Startup dir not found: " + startupDir.getAbsolutePath()); 6 if (!new File(startupTarget).exists()) 7 throw new FileNotFoundException("Executable not found."); 8 9 this.startupFile = new File(startupDir.getAbsolutePath(), startupShortcut); 10 this.available = true; 11 12 File oldStartup = new File(startupDir.getAbsolutePath(), oldStartupScript); 13 try { 14 if (oldStartup.exists()) { 15 Logger.info("Config", "Found old version startup, migrate to new approach."); 16 if (oldStartup.delete()) 17 addStartup(); 18 } 19 } catch (Exception e) { 20 Logger.error("Config", "Cannot migrate startup, details see below.", e); 21 } 22 } catch (Exception e) { 23 this.startupFile = null; 24 this.available = false; 25 Logger.debug("Config", "Auto-startup is unavailable."); 26 } 27}

Source: WindowsStartupConfig.java

启用自启动(COM 快捷方式创建)

用户勾选复选框后,addStartup() 通过 COM 创建快捷方式。关键点在于参数注入:SetArguments("--direct-start") 把直达启动意图写入 .lnk,工作目录显式设为当前目录以保证相对资源可寻址;路径中的双引号被转义(\" → \"\")以防御含空格/特殊字符的安装路径。COM 资源按 IPersistFile → IShellLink 的顺序手动 Release(),避免 COM 对象泄漏:

java
1@Override 2public boolean addStartup() { 3 if (!this.available) return false; 4 try { 5 IShellLink lnk = IShellLink.create(); 6 IPersistFile pf = lnk.getPF(); 7 String cd = System.getProperty("user.dir"); 8 cd = cd.replace("\"", "\"\""); 9 lnk.SetPath(cd + "\\" + startupTarget); 10 lnk.SetArguments("--direct-start"); 11 lnk.SetWorkingDirectory(cd); 12 pf.Save(startupFile.getAbsolutePath().replace("\"", "\"\"\"")); 13 pf.Release(); 14 lnk.Release(); 15 Logger.info("Config", "Auto-startup added."); 16 return true; 17 } catch (Exception e) { 18 Logger.error("Config", "Auto-startup adding failed, details see below.", e); 19 return false; 20 } 21}

Source: WindowsStartupConfig.java

UI 层交互与失败反馈

SettingsModule 在初始化时回显状态(isSetStartup() 决定复选框选中态),切换时调用对应方法。失败场景(权限不足、反病毒拦截等)弹出警告对话框并把复选框强制回退为未选中,防止 UI 与真实状态不一致:

java
1StartupConfig startup = StartupConfig.getInstance(); 2configAutoStartup.setSelected(startup.isSetStartup()); 3configAutoStartup.setOnAction(e -> { 4 if (configAutoStartup.isSelected()) { 5 if (startup.addStartup()) { 6 GuiPrefabs.Dialogs.createCommonDialog(app.body, 7 GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_SUCCESS_ALT, GuiPrefabs.COLOR_SUCCESS), 8 "开机自启动", 9 "开机自启动设置成功。", 10 /* ... */).show(); 11 } else { 12 // 失败:弹出警告并将复选框回退为未选中 13 configAutoStartup.setSelected(false); 14 } 15 } else { 16 startup.removeStartup(); 17 } 18});

Source: SettingsModule.java

(注:上文为节选重排,注释行与省略的对话框文案以源文件为准。)

端到端时序

Loading diagram...

数据模型 / 持久化

本能力不使用数据库或 JSON 配置文件,快捷方式文件本身就是持久化状态。相关常量与文件对照:

常量值含义
startupTargetArkPets.exe快捷方式目标可执行文件(相对工作目录)
startupShortcutArkPetsStartup.lnk写入启动文件夹的快捷方式文件名
oldStartupScriptArkPetsStartupService.vbs旧版 VBS 脚本方案遗留文件(触发自动迁移)
uninstallTargetunins000.exeInno Setup 卸载器,存在与否判定 isAutoUpdateAvailable()

Source: WindowsStartupConfig.java

状态判定逻辑很直接:isSetStartup() 返回 available && startupFile.exists()——即"能力可用且快捷方式已存在"。这意味着用户手工删除 .lnk 后,下次打开设置面板复选框会自动变为未选中,UI 与文件系统天然同步,无需缓存失效处理。

Source: WindowsStartupConfig.java

API Reference

StartupConfig.getInstance(): StartupConfig

平台工厂入口。依据 JNA Platform.isWindows() 返回 WindowsStartupConfig 或 NullStartupConfig。

Returns: 平台对应的 StartupConfig 实例(构造期内已完成可用性自检)。

Source: StartupConfig.java

addStartup(): boolean

启用开机自启动。

Returns: true = 成功;false = 失败(不可用或 COM 调用抛异常,异常会写入 Logger 后被吞掉,不会向上传播)。

Throws: 无(内部 catch 全部异常)。

Source: WindowsStartupConfig.java

removeStartup(): void

禁用开机自启动,通过 IOUtils.FileUtil.delete(startupFile.toPath(), false) 删除快捷方式。

Throws: 无(内部 catch 全部异常,仅记录日志)。

Source: WindowsStartupConfig.java

isSetStartup(): boolean / isStartupAvailable(): boolean

前者返回"是否已启用"(available && startupFile.exists()),后者返回"该平台是否具备此能力"。SettingsModule 用前者回显 UI,理论上应结合后者禁用复选框。

Source: WindowsStartupConfig.java

isAutoUpdateAvailable(): boolean

探测 ArkPets.exe 与 unins000.exe 是否同时存在,作为自动更新能力的前置判定(本页不展开更新流程)。

Source: WindowsStartupConfig.java

NullStartupConfig(空对象实现)

java
1@Override 2public boolean addStartup() { 3 return true; 4} 5 6@Override 7public void removeStartup() { 8}

Source: NullStartupConfig.java

注意 addStartup() 返回 true 而非 false——设计意图是让"启用自启动"在非 Windows 平台静默成功,不触发失败警告对话框;而 isSetStartup() 未在节选证据中确认返回值,具体行为以源文件其余部分为准。

Failure Modes, Edge Cases & Concurrency

场景触发条件处理方式证据
启动目录不存在user.home 下 Startup 目录缺失构造抛 FileNotFoundException,available=false,后续 addStartup() 直接返回 falseWindowsStartupConfig.java
可执行文件缺失工作目录无 ArkPets.exe(如纯 jar 运行)同上,构造期自检失败WindowsStartupConfig.java
COM 调用失败权限不足、COM 组件注册异常、反病毒拦截COMUtils.checkRC 抛异常被捕获,记日志并返回 false,UI 弹警告并回退复选框WindowsStartupConfig.java
路径含引号/特殊字符安装路径异常cd.replace("\"", "\"\"") 手工转义,防止 COM 参数注入破坏WindowsStartupConfig.java
旧版 VBS 迁移失败删除 ArkPetsStartupService.vbs 或重建失败内层独立 try/catch,仅记 Logger.error,不影响构造结果WindowsStartupConfig.java
删除快捷方式失败文件被占用removeStartup() 内部 catch,记日志,不抛出WindowsStartupConfig.java

并发:WindowsStartupConfig 每次由 getInstance() 新建实例,无共享可变状态;唯一的外部状态(.lnk 文件)由文件系统串行化。COM 调用发生在调用线程上,未使用后台线程——因此 UI 线程在 COM 异常(如单线程套间问题)极端场景下存在理论上的卡顿风险,属于当前实现的已知边界。

资源管理:COM 对象通过显式 Release()(pf.Release() → lnk.Release())释放,addStartup() 抛异常的路径上没有 finally 保护,异常时可能泄漏 COM 引用——这是源码可见的一个实现细节,调用方应以返回值为准而非异常。

Performance / Operational Notes

  • 零开销探测:可用性检查只做两次 File.exists(),无注册表读写、无进程调用,getInstance() 可安全地在每次打开设置面板时调用。
  • 运维排查路径:Logger 频道为 "Config",关键日志包括 Auto-startup added.、Auto-startup removed.、Auto-startup is unavailable.(debug 级),可据此定位失败环节。
  • 手工干预:用户可直接删除 %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\ArkPetsStartup.lnk 禁用自启,程序下次读取状态会自动感知。
  • 打包前提:快捷方式目标固定为 ArkPets.exe(Inno Setup 安装版布局),意味着从 IDE 或 jar 方式运行时该功能自动不可用——这是有意的行为,避免开发环境误注册自启。

Extension Points

  • 新平台支持:继承 StartupConfig 并在 getInstance() 工厂中按 Platform.isXxx() 分派即可(例如 macOS 可用 LaunchAgents、Linux 可用 ~/.config/autostart/*.desktop),上层 SettingsModule 无需改动。
  • 空对象模式:NullStartupConfig 展示了"能力不可用平台"的标准降级写法,可复用于其他平台特性。
  • 参数扩展:若自启动需要携带更多参数,只需在 addStartup() 中追加 SetArguments 内容;解析侧逻辑属于启动器启动流程(见兄弟页面)。