Skip to content

OpenClaw 源码解读(十八)原生应用架构

一、导读

OpenClaw 不只是一个命令行工具——它拥有完整的 三端原生应用:macOS 菜单栏应用(Swift/SwiftUI)、iOS 移动端(Swift/SwiftUI)、Android 移动端(Kotlin/Jetpack Compose)。这三端应用共享同一个 Gateway WebSocket 协议,但各自有独特的平台能力和架构风格。

┌──────────────────────────────────────────────────────┐
│                    Gateway (Node.js)                  │
│                    WebSocket RPC                      │
└───────┬──────────────────┬──────────────────┬────────┘
        │                  │                  │
   ┌────┴────┐       ┌────┴────┐       ┌────┴────┐
   │  macOS  │       │   iOS   │       │ Android │
   │ SwiftUI │       │ SwiftUI │       │ Compose │
   │ MenuBar │       │  @Observable │  │ MVVM    │
   │  Actor  │       │ 双连接  │       │ StateFlow │
   └─────────┘       └─────────┘       └─────────┘

代码位于 apps/macos/apps/ios/apps/android/,总计约 400+ 源文件

![三端原生应用架构总览](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(十八)原生应用架构/01-infographic-three-platform-overview-1775150787440.png)


二、macOS 应用 —— 菜单栏 + 进程管理

macOS 版是三端中功能最丰富的,因为它不仅是 Gateway 的客户端,还是 Gateway 的宿主——它负责启动、管理和维护本地 Gateway 进程。

2.1 入口 —— MenuBar.swift

swift
@main
struct OpenClawApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate
    @State private var state: AppState

    var body: some Scene {
        MenuBarExtra { MenuContent(state: self.state, updater: ...) } label: {
            CritterStatusLabel(...)  // 动画菜单栏图标
        }
        .menuBarExtraStyle(.menu)
        
        Settings {
            SettingsRootView(state: self.state, ...)
        }
    }
}

macOS 应用是一个 MenuBarExtra(纯菜单栏应用),没有 Dock 图标(可选开启)。入口只有两个 Scene:菜单栏下拉菜单 + 设置窗口。

鼠标交互精巧设计:

  • 左键点击 → 弹出 WebChat 面板(浮动聊天窗口)
  • 右键点击 → 弹出系统菜单(设置/状态)
  • 悬停 → 显示 HoverHUD(快速状态)

2.2 AppState —— 状态中枢

AppState 使用 Swift Observation 框架(@Observable),管理 50+ 个持久化设置:

设置类别典型属性持久化
连接模式connectionMode(local/remote/unconfigured)UserDefaults
语音唤醒swabbleEnabled, swabbleTriggerWordsUserDefaults
外观iconAnimationsEnabled, showDockIconUserDefaults
安全execApprovalModeExecApprovalsStore
CanvascanvasEnabled, canvasPanelVisibleUserDefaults
TalktalkEnabledUserDefaults

每个属性的 didSet 都通过 ifNotPreview 保护——Preview 模式下不触发持久化,避免 Xcode 预览污染数据。

2.3 GatewayProcessManager —— 进程生命周期

macOS 独有的核心组件,管理本地 Gateway 进程:

swift
enum Status: Equatable {
    case stopped
    case starting
    case running(details: String?)
    case attachedExisting(details: String?)  // 附着到已有进程
    case failed(String)
}

启动策略(4 步):

  1. 模式检查:Remote 模式跳过(不在本地启动 Gateway)
  2. 附着探测:尝试连接已运行的 Gateway(attachExistingGatewayIfAvailable()
  3. Launchd 启动:通过 GatewayLaunchAgentManager 启用 launchd 服务
  4. 健康探测:连接成功后执行 health RPC 验证

自动恢复机制:当 request() 失败时,分三档延迟重试(150ms → 400ms → 900ms),并尝试 Tailscale 回退路径。

2.4 GatewayConnection —— Actor 模式

swift
actor GatewayConnection {
    static let shared = GatewayConnection()
    // ...
}

使用 Swift Actor 隔离并发访问,保证线程安全。定义了 40+ 个 RPC Method(agent, status, chatSend, cronList...),通过类型安全的枚举避免字符串硬编码。

2.5 Canvas 生态

macOS 端拥有完整的 Canvas 画布系统:

  • CanvasManager — Canvas 窗口生命周期
  • CanvasWindowController — 窗口控制器(位置/大小/导航)
  • CanvasSchemeHandler — 自定义 URL Scheme 处理
  • CanvasA2UIActionMessageHandler — A2UI 动作消息桥接
  • CanvasFileWatcher — 文件变更监听

2.6 语音唤醒(VoiceWake)

macOS 端独有的语音唤醒系统:

  • VoiceWakeRuntime — 语音识别运行时
  • VoiceWakeForwarder — 识别文本转发到 Agent
  • VoiceWakeOverlay* — 浮动 UI 叠层
  • 支持多语言识别、自定义触发词、快捷键 Push-to-Talk

![macOS 菜单栏应用组件架构](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(十八)原生应用架构/02-infographic-macos-architecture-1775150788408.png)


三、iOS 应用 —— 双连接 + 能力路由

iOS 版是一个功能丰富的移动客户端,同时充当 Gateway 的"远程节点"。

3.1 NodeAppModel —— 双连接架构

iOS 最独特的架构设计是双 Gateway 连接

swift
private let nodeGateway = GatewayNodeSession()      // 节点连接(设备能力)
private let operatorGateway = GatewayNodeSession()   // 操作连接(聊天/配置)

为什么需要两个连接?

  • Node 连接:注册设备能力(相机、位置、屏幕截图),处理 Gateway 的 node.invoke 请求
  • Operator 连接:处理聊天、配置、语音等交互操作

分离的好处是:Node 连接可以保持后台活跃(处理设备指令),而 Operator 连接只在前台使用。

3.2 能力路由器

iOS 端注册了 15+ 种设备能力:

能力服务说明
CameraCameraServicing拍照/录像/闪光灯
LocationLocationServicingGPS/显著位置变化
ScreenScreenController截屏/录屏
ContactsContactsServicing通讯录读取
CalendarCalendarServicing日历事件
RemindersRemindersServicing提醒事项
PhotosPhotosServicing相册访问
MotionMotionServicing运动传感器
NotificationsNotificationCentering推送通知
VoiceWakeVoiceWakeManager语音唤醒
TalkModeTalkModeManager对话模式
WatchMessagingWatchMessagingServicingApple Watch 消息

所有服务通过 Protocol 注入(依赖注入),支持测试替换。

3.3 后台生命周期

swift
var isBackgrounded: Bool = false
private var backgroundGraceTaskID: UIBackgroundTaskIdentifier = .invalid
private var backgroundReconnectSuppressed: Bool = false
private var backgroundReconnectLeaseUntil: Date?

iOS 后台策略:

  • 前台 → 后台:暂停语音唤醒、Talk 模式
  • 后台宽限期:使用 UIApplication.beginBackgroundTask() 保持短暂活跃
  • 显著位置变化:通过 significantLocationChange 在后台唤醒并执行任务
  • 推送唤醒:通过 APNS 静默推送唤醒处理消息
  • Watch 快速回复:Apple Watch 的回复排队,在连接恢复时发送
swift
enum IOSDeepLinkAgentPolicy {
    static let maxMessageChars = 20000
    static let maxUnkeyedConfirmChars = 240
}

支持 openclaw://agent?message=... 格式的 Deep Link,用于 Shortcuts/快捷指令集成。安全限制:超过 240 字符的未授权消息需要用户确认。

3.5 Gateway 信任机制

首次连接新 Gateway 时触发信任审批:

swift
var pendingGatewayTrust: GatewayNodeSession.TrustPromptEvent?
var gatewayPairingPaused: Bool = false  // 暂停重连循环
var gatewayPairingRequestId: String?

信任流程:Gateway TLS 指纹 → 用户确认 → 持久化信任 → 建立连接。


四、Android 应用 —— MVVM + StateFlow

Android 版使用 Kotlin + Jetpack Compose + MVVM 架构。

4.1 三层架构

MainActivity (Compose UI)

MainViewModel (状态暴露)

NodeRuntime (业务核心)
    ├── GatewaySession (WebSocket)
    ├── CanvasController
    ├── CameraCaptureManager
    ├── LocationCaptureManager
    ├── MicCaptureManager
    ├── 12+ Handler (能力分发)
    └── InvokeDispatcher (命令路由)

4.2 MainViewModel —— 状态透传

kotlin
class MainViewModel(app: Application) : AndroidViewModel(app) {
    private val runtime: NodeRuntime = (app as NodeApp).runtime
    
    val isConnected: StateFlow<Boolean> = runtime.isConnected
    val chatMessages = runtime.chatMessages
    val cameraEnabled: StateFlow<Boolean> = runtime.cameraEnabled
    // ... 60+ StateFlow 属性
}

ViewModel 是一个纯粹的状态透传层——所有 StateFlow 直接引用 NodeRuntime 的同名属性。方法也是一比一代理。这种设计让 ViewModel 极其轻薄(~200 行),所有逻辑集中在 NodeRuntime

4.3 NodeRuntime —— 业务核心

NodeRuntime 是 Android 端的业务中枢(~600+ 行),管理:

组件类型职责
GatewaySession × 2operatorSession / nodeSession双连接(同 iOS)
GatewayDiscovery服务发现局域网 Gateway 发现
InvokeDispatcher命令路由12+ Handler 的路由分发
ConnectionManager连接管理参数构造/能力声明
ChatController聊天控制消息/流式/会话管理

4.4 GatewaySession —— OkHttp WebSocket

kotlin
class GatewaySession(
    private val scope: CoroutineScope,
    private val identityStore: DeviceIdentityStore,
    private val deviceAuthStore: DeviceAuthTokenStore,
    private val onConnected: (serverName, remoteAddress, mainSessionKey) -> Unit,
    private val onDisconnected: (message: String) -> Unit,
    private val onEvent: (event, payloadJson) -> Unit,
    private val onInvoke: (suspend (InvokeRequest) -> InvokeResult)?,
)

基于 OkHttp WebSocket,协议帧格式与 Web UI 完全一致(req/res/event)。

关键差异:

  • Android 端使用协程CoroutineScope + Dispatchers.IO),而非回调
  • Mutex 写锁writeLock = Mutex() 保证 WebSocket 写操作串行化
  • Pending 请求ConcurrentHashMap<String, CompletableDeferred<RpcResponse>>,线程安全
  • 连接超时:12s(高于默认值,适应低端设备)

4.5 InvokeDispatcher —— 能力处理

Android 端通过 12 个 Handler 实现设备能力:

Handler职责
CameraHandler拍照/录像/HUD 控制
LocationHandlerGPS 定位
DeviceHandler设备信息(电量/存储/网络)
NotificationsHandler通知权限/列表
SystemHandler系统操作(音量/亮度)
PhotosHandler相册读取
ContactsHandler通讯录
CalendarHandler日历
MotionHandler加速度/计步器
ScreenHandler截屏/录屏
SmsHandler短信发送
A2UIHandlerCanvas A2UI 动作
DebugHandler调试信息
AppUpdateHandler应用更新

4.6 TLS 指纹信任

kotlin
private val onTlsFingerprint: ((stableId: String, fingerprint: String) -> Unit)?

Android 端支持 TLS 证书指纹信任——首次连接时计算并存储证书指纹,后续连接验证指纹匹配。


五、三端共同架构模式

5.1 双连接模式

三端都使用 Operator + Node 双连接

连接角色用途
Operatorrole: "operator"聊天、配置、语音
Noderole: "node"设备能力、Canvas、node.invoke

Operator 连接在前台交互使用,Node 连接可在后台保持。

5.2 Gateway 发现

三端都支持局域网自动发现 Gateway:

  • macOS:通过 GatewayDiscoveryHelpers
  • iOS/Android:通过 GatewayDiscovery(mDNS/广播探测)

5.3 设备身份

三端都使用设备级密钥对进行认证:

  • macOS:Keychain 存储
  • iOS:Keychain 存储(DeviceIdentityStore
  • Android:加密 SharedPreferences(DeviceIdentityStore

5.4 Canvas A2UI

三端都支持 Canvas A2UI 渲染:

  • macOS:CanvasWindowController(独立窗口 + WKWebView)
  • iOS:ScreenController(WebView 嵌入)
  • Android:CanvasController(WebView 嵌入)

六、平台差异对比

维度macOSiOSAndroid
语言SwiftSwiftKotlin
UI 框架SwiftUI + AppKitSwiftUI + UIKitJetpack Compose
状态管理@Observable@ObservableStateFlow
并发模型Swift ActorSwift Structured ConcurrencyKotlin Coroutines
Gateway 角色宿主(启动+管理进程)纯客户端纯客户端
后台策略常驻(菜单栏应用)显著位置+推送唤醒前台服务
WebSocketURLSessionURLSessionOkHttp
密钥存储KeychainKeychain加密 SharedPreferences
Canvas独立窗口嵌入 WebView嵌入 WebView
语音唤醒✅(Speech Framework)✅(Speech Framework)❌(计划中)
Talk 模式
特有能力进程管理/LaunchdApple Watch/ShortcutsSMS 发送

七、设计模式总结

模式应用位置效果
Actor IsolationGatewayConnection (macOS)线程安全的共享连接
MVVMMainViewModel (Android)UI/业务分离
Protocol DINodeAppModel (iOS)15+ 服务可测试替换
Dual Connection三端前台交互与后台能力分离
Command DispatchInvokeDispatcher (Android)12 Handler 路由
State HoistingAppState (macOS)50+ 设置集中管理
Auto RecoveryGatewayConnection.request()三档重试 + 路径回退
Trust-on-First-UseTLS 指纹首次信任后验证

八、推荐阅读顺序

macOS:

  1. apps/macos/Sources/OpenClaw/MenuBar.swift — 应用入口
  2. apps/macos/Sources/OpenClaw/AppState.swift — 状态中枢
  3. apps/macos/Sources/OpenClaw/GatewayProcessManager.swift — 进程管理
  4. apps/macos/Sources/OpenClaw/GatewayConnection.swift — Actor 连接
  5. apps/macos/Sources/OpenClaw/ControlChannel.swift — 控制频道
  6. apps/macos/Sources/OpenClaw/CanvasWindowController.swift — Canvas 窗口

iOS:

  1. apps/ios/Sources/OpenClawApp.swift — 应用入口
  2. apps/ios/Sources/Model/NodeAppModel.swift — 双连接核心
  3. apps/ios/Sources/Gateway/GatewayNodeSession.swift — WebSocket 会话

Android:

  1. apps/android/.../MainActivity.kt — Compose 入口
  2. apps/android/.../MainViewModel.kt — 状态透传
  3. apps/android/.../NodeRuntime.kt — 业务核心
  4. apps/android/.../gateway/GatewaySession.kt — OkHttp WebSocket

九、思考题

  1. macOS 版同时是 Gateway 宿主和客户端——这种"自连接"模式有什么风险? 如果 Gateway 崩溃,App 如何检测和恢复?

  2. 双连接(Operator + Node)是必要的吗? 一个连接用不同的 scope 能否达到同样效果?分开的真正原因是什么?

  3. Android 的 MainViewModel 是纯透传层——这算过度设计吗? 直接在 Compose 中引用 NodeRuntime 的 StateFlow 有什么问题?

  4. iOS 后台策略使用"显著位置变化"唤醒——这对不需要位置功能的用户是否合理? 有没有更好的后台保活方案?

  5. 三端使用不同的密钥存储方案(Keychain vs 加密 SharedPreferences)——安全等级一致吗? 哪个更容易被攻击?

读文档、看源码、写代码,理解 AI Agent 本质 🤖