# Launcher 架构拆分评估报告 ## 1. 现状分析 ### 1.1 Launcher 当前职责清单 根据代码审查,[LanMountainDesktop.Launcher](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher) 当前承担 **6 个主要职责域**: | 职责域 | 核心文件 | 代码量 | 复杂度 | |--------|---------|--------|--------| | **OOBE 首次体验** | `OobeStateService`, `OobeWindow`, `WelcomeOobeStep`, `DataLocationOobeStep`, `PrivacyAgreementService` | ~28 KB | 中 | | **Splash / 启动协调** | `LauncherFlowCoordinator`, `SplashWindow`, `LoadingDetailsWindow`, `StartupAttemptRegistry` | ~120 KB | **极高** | | **更新引擎** | `UpdateEngineService`, `UpdateCheckService` | ~77 KB | **极高** | | **插件管理** | `PluginInstallerService`, `PluginUpgradeQueueService` | ~13 KB | 低 | | **部署/版本管理** | `DeploymentLocator`, `FlexibleHostLocator`, `DotNetRuntimeProbe`, `LegacyVersionDetector` | ~70 KB | 高 | | **Air APP 生命周期** | `AirApp/*`, `LauncherBackgroundService` | ~21 KB | 中 | **总计:~673 KB 源代码,95 个 .cs/.axaml 文件** ### 1.2 关键耦合热点 #### 热点 1:[LauncherFlowCoordinator.cs](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher/Services/LauncherFlowCoordinator.cs) — **90 KB / 2034 行** 这是整个 Launcher 最大的单文件,负责: - OOBE → Splash → Update → Plugin → Host Launch 的完整编排 - 多实例检测与协调 (IPC coordinator) - 主程序启动、进程监控、超时处理 - 激活恢复 (activation recovery) - 所有 UI 窗口的生命周期管理 > [!WARNING] > 这个文件是当前最大的架构债务。它同时了解所有职责域,是修改任何启动行为都必须触碰的瓶颈文件。 #### 热点 2:[UpdateEngineService.cs](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher/Services/UpdateEngineService.cs) — **72 KB / 1850 行** 包含两套完整的更新应用流程(Legacy 和 PLONDS),内嵌: - 签名验证 - 增量文件应用 - SHA-256/SHA-512 校验 - 回滚机制 - 快照管理 - 安装检查点与断点续传 #### 热点 3:[App.axaml.cs](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher/App.axaml.cs) — **34 KB / 850 行** App 入口承担了过多的运行时编排逻辑,包括: - Coordinator IPC 服务器的创建和管理 - Air APP IPC broker 模式 - 主程序进程存活监控 - 失败恢复 UI 流程 ### 1.3 职责域间的依赖关系 ```mermaid graph TD A["App.axaml.cs
(入口编排)"] --> B["LauncherFlowCoordinator
(流程协调)"] A --> C["Air APP Broker"] A --> D["Coordinator IPC"] B --> E["OobeStateService"] B --> F["UpdateEngineService"] B --> G["PluginInstallerService"] B --> H["DeploymentLocator"] B --> I["StartupAttemptRegistry"] B --> D F --> H G --> J["PluginUpgradeQueueService"] C --> K["LauncherAirAppLifecycleService"] style A fill:#ff6b6b,color:#fff style B fill:#ff6b6b,color:#fff style F fill:#ff9f43,color:#fff ``` > [!IMPORTANT] > 红色节点是耦合最严重的热点。`LauncherFlowCoordinator` 直接依赖几乎所有其他服务。 --- ## 2. 方案评估 ### Option A:多项目拆分 将 Launcher 拆分成独立的 .NET 项目/可执行文件: | 拆分后的项目 | 职责 | |-------------|------| | `LanMountainDesktop.Launcher` | 精简入口,仅做 OOBE + Splash + 编排调度 | | `LanMountainDesktop.UpdateService` | 更新检查、下载、应用、回滚 | | `LanMountainDesktop.PluginService` | 插件安装、升级队列 | | `LanMountainDesktop.DeploymentManager` | 版本目录管理、主机发现 | #### 优点 - 最大化隔离:每个服务可独立部署、独立更新 - 更新引擎可以在 Launcher 自身不运行时被调用(例如计划任务) - 故障隔离:插件安装崩溃不影响更新流程 #### 缺点 > [!CAUTION] > **这些缺点在当前阶段是致命的。** - **进程间通信成本巨大**:当前 `LauncherFlowCoordinator` 的 2034 行编排逻辑严重依赖同进程内的同步/异步调用和共享状态(`TaskCompletionSource`、进程对象引用、UI Dispatcher 调度)。拆成多进程意味着每个交互点都需要 IPC 管道 + 序列化 + 超时处理 + 错误恢复。 - **启动延迟增加**:当前 Launcher 启动到 Host 启动的路径已经很长(OOBE → 更新 → 插件 → 主机发现 → Host 进程启动 → IPC 握手)。多进程会在每个阶段增加进程启动开销。 - **安装包膨胀**:每个独立可执行文件都需要自己的运行时依赖,即使共享 Avalonia SDK。 - **复杂的部署协调**:Launcher 自身不可被拆分更新——它就是更新的入口。如果 `UpdateService` 是独立进程,谁来启动它?又需要一个 meta-launcher。 - **当前代码并未准备好**:`LauncherFlowCoordinator.RunAsync()` 是一个巨大的异步方法,内部有十几个局部变量和闭包在多个 await 之间共享状态。这些状态不可能简单地序列化为 IPC 消息。 #### 改造工程量估算 - **IPC 层**:需新增 ~8-10 个 IPC 契约、每个有请求/响应/通知消息 - **进程管理**:需要为每个子服务编写进程启动、健康检查、重启逻辑 - **状态同步**:`StartupAttemptRegistry` 的锁文件机制需要扩展为跨进程锁 - **估计 3-5 人周**,且引入大量新的故障模式 --- ### Option B:单项目内部解耦(推荐) 保持单一 Launcher 可执行文件,通过以下手段实现内部解耦: #### 阶段 1:职责分层(重构目录结构) ``` LanMountainDesktop.Launcher/ ├── Program.cs # 入口(保持精简) ├── App.axaml.cs # Avalonia 应用(精简到 <200 行) ├── Core/ # 核心编排层 │ ├── LauncherOrchestrator.cs # 从 App.axaml.cs 提取的运行时编排 │ ├── StartupPipeline.cs # 从 FlowCoordinator 提取的阶段管道 │ └── StartupPhase.cs # 每个阶段的抽象接口 ├── Deployment/ # 版本管理域 │ ├── DeploymentLocator.cs │ ├── FlexibleHostLocator.cs │ ├── HostLaunchPlan.cs │ ├── DotNetRuntimeProbe.cs │ └── LegacyVersionDetector.cs ├── Update/ # 更新引擎域 │ ├── UpdateEngineService.cs # 重构后 <400 行 │ ├── LegacyUpdateApplier.cs # 从 UpdateEngine 提取 │ ├── PlondsUpdateApplier.cs # 从 UpdateEngine 提取 │ ├── SignatureVerifier.cs # 从 UpdateEngine 提取 │ ├── UpdateCheckService.cs │ └── UpdateSnapshotManager.cs # 从 UpdateEngine 提取 ├── Plugin/ # 插件管理域 │ ├── PluginInstallerService.cs │ └── PluginUpgradeQueueService.cs ├── Oobe/ # OOBE 域 │ ├── OobeStateService.cs │ ├── IOobeStep.cs │ ├── WelcomeOobeStep.cs │ ├── DataLocationOobeStep.cs │ └── PrivacyAgreementService.cs ├── AirApp/ # Air APP 域(已部分独立) │ └── ... ├── Coordination/ # 多实例协调域 │ ├── StartupAttemptRegistry.cs │ ├── LauncherCoordinatorIpcServer.cs │ └── LauncherCoordinatorIpcClient.cs ├── Views/ # UI 层 │ └── ... ├── ViewModels/ # ViewModel 层 │ └── ... └── Models/ # 数据模型 └── ... ``` #### 阶段 2:拆分 LauncherFlowCoordinator 将 2034 行的 `RunAsync()` 重构为 Pipeline + Phase 模式: ```csharp // 启动管道定义(伪代码) public class StartupPipeline { private readonly IReadOnlyList _phases; public async Task ExecuteAsync(StartupContext context) { foreach (var phase in _phases) { var result = await phase.ExecuteAsync(context); if (!result.Continue) return result.LauncherResult; } return LauncherResult.Success(); } } // 各阶段独立实现 public class CleanupPhase : IStartupPhase { ... } public class OobePhase : IStartupPhase { ... } public class UpdatePhase : IStartupPhase { ... } public class PluginUpgradePhase : IStartupPhase { ... } public class HostLaunchPhase : IStartupPhase { ... } public class StartupMonitorPhase : IStartupPhase { ... } ``` #### 阶段 3:拆分 UpdateEngineService 将 1850 行的更新引擎拆分为独立的策略类: ```csharp // 更新引擎成为协调者,不再包含实现 public class UpdateEngineService { private readonly IUpdateApplier _legacyApplier; private readonly IUpdateApplier _plondsApplier; private readonly ISignatureVerifier _signatureVerifier; private readonly IUpdateSnapshotManager _snapshotManager; ... } ``` #### 阶段 4:精简 App.axaml.cs 将 850 行的 App 入口精简为纯粹的 Avalonia 应用初始化 + 委托给 `LauncherOrchestrator`: ```csharp public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var orchestrator = new LauncherOrchestrator(desktop, LauncherRuntimeContext.Current); _ = orchestrator.RunAsync(); } base.OnFrameworkInitializationCompleted(); } ``` #### 优点 - **零部署风险**:不改变安装包结构、不引入新进程、不改变 IPC 拓扑 - **增量重构**:可以一个职责域一个域地逐步重构,每次重构都可编译验证 - **测试友好**:拆分后的各 Phase 和 Service 可以独立单元测试 - **保持启动性能**:单进程内的函数调用无 IPC 开销 - **为未来多进程做准备**:如果将来真的需要拆分进程,接口已经清晰 #### 缺点 - 仍然是单进程:更新引擎崩溃会影响 Launcher 进程 - 需要自律维持架构边界(没有编译级隔离) #### 改造工程量估算 - 阶段 1(目录重组):~0.5 人天 - 阶段 2(FlowCoordinator 拆分):~2-3 人天 - 阶段 3(UpdateEngine 拆分):~1-2 人天 - 阶段 4(App.axaml.cs 精简):~0.5-1 人天 - **总计 ~4-7 人天**,且风险可控 --- ## 3. 决策矩阵 | 维度 | Option A (多项目拆分) | Option B (内部解耦) | |------|---------------------|-------------------| | **改造风险** | 🔴 高:引入新 IPC、新故障模式 | 🟢 低:纯重构,行为不变 | | **改造工期** | 🔴 3-5 人周 | 🟢 4-7 人天 | | **启动性能** | 🔴 多进程启动开销 | 🟢 零额外开销 | | **故障隔离** | 🟢 进程级隔离 | 🟡 需靠代码纪律 | | **独立更新** | 🟢 各服务可独立版本 | 🔴 单一二进制 | | **可测试性** | 🟢 天然隔离 | 🟢 接口拆分后等效 | | **安装包大小** | 🔴 膨胀(多 EXE) | 🟢 不变 | | **部署复杂度** | 🔴 谁更新 Launcher? | 🟢 VeloPack 现有流程 | | **团队人力需求** | 🔴 需长期维护多套 IPC | 🟢 维护成本低 | --- ## 4. 推荐方案 > [!IMPORTANT] > **推荐 Option B:单项目内部解耦**,原因如下: 1. **当前阶段的核心瓶颈不是项目边界,而是文件级别的职责混乱**。`LauncherFlowCoordinator` 一个文件 2034 行,`UpdateEngineService` 一个文件 1850 行,`App.axaml.cs` 一个文件 850 行——这才是真正影响可维护性的问题。 2. **Launcher 的核心约束决定了它必须是单一入口**。根据 [ARCHITECTURE.md](file:///d:/github/LanMountainDesktop/docs/ARCHITECTURE.md) 的定义,Launcher 是 "应用的唯一入口",负责版本选择、原子化更新和安全启动。这个约束使得多进程拆分的价值大打折扣。 3. **已经存在良好的外部进程隔离**。Host 主程序 (`LanMountainDesktop.exe`)、Air APP Host (`LanMountainDesktop.AirAppHost`) 都是独立进程。Launcher 只需要作为协调者存在,它不需要自己也拆成多个进程。 4. **改造投入产出比**。Option A 需要 3-5 人周且引入新风险,Option B 需要 4-7 人天且零风险,效果(可维护性、可测试性)几乎等效。 --- ## 5. Open Questions 1. **是否考虑将 Launcher 的 CLI 模式(`update check`、`update apply`、`plugin install`)独立成一个无 UI 的命令行工具?** 这是一个比全面拆分轻量得多的拆分点,可以让 CI/CD 和脚本调用不依赖 Avalonia 运行时。 2. **`UpdateEngineService` 是否打算支持 Launcher 自身的自更新?** 如果是,可能需要一个极简的 "bootstrap updater" 组件,这是唯一可能需要独立进程的场景。 3. **内部解耦后,是否要引入 `Microsoft.Extensions.DependencyInjection`?** 当前所有服务都是手动 `new` 的,引入 DI 容器可以自然地约束依赖方向,但也会增加 Launcher 启动路径的复杂度。