万字解析 OpenClaw 源码架构-跨平台应用之Android 应用

9 阅读37分钟

Android 应用层的关键文件组织如下:

  • 应用入口与运行时:NodeApp、NodeRuntime
  • 前台服务:NodeForegroundService
  • 网关会话:GatewaySession(WebSocket 客户端)
  • 平台服务:DeviceNotificationListenerService(通知监听)
  • 设备信息与健康:DeviceHandler(电池、内存等)
  • 安全与偏好:SecurePrefs
  • UI 绑定:MainViewModel(对 NodeRuntime 的调用封装)
graph TB
subgraph "应用层"
A["NodeApp<br/>应用入口"]
B["NodeRuntime<br/>节点运行时"]
C["NodeForegroundService<br/>前台服务"]
D["DeviceNotificationListenerService<br/>通知监听服务"]
end
subgraph "网关层"
E["GatewaySession<br/>WebSocket 会话"]
end
subgraph "设备与系统"
F["DeviceHandler<br/>设备信息/健康"]
G["SecurePrefs<br/>加密偏好"]
end
A --> B
B --> C
B --> E
B --> F
B --> G
D --> B

核心组件

  • NodeApp:应用生命周期持有者,延迟初始化 NodeRuntime
  • NodeRuntime:核心运行时,负责:
    • 网关连接(操作员会话与节点会话)
    • 自动重连与断线恢复
    • 状态流(连接状态、服务器名、麦克风状态等)
    • 事件分发与命令派发(InvokeDispatcher)
    • 通知监听事件转发到节点会话
  • NodeForegroundService:前台服务,负责:
    • 创建并更新通知
    • 订阅 NodeRuntime 状态流,动态刷新通知内容
    • 提供“断开”动作以触发 NodeRuntime 断开连接
  • GatewaySession:WebSocket 客户端,封装连接、请求、事件与自动重连循环
  • DeviceNotificationListenerService:系统通知监听服务,将通知事件转发给 NodeRuntime
  • DeviceHandler:设备健康信息采集(电池、内存、温度等)
  • SecurePrefs:加密存储(SharedPreferences 加密包装),用于实例 ID、显示名、网关凭据等

架构总览

下图展示从前台服务到运行时、再到网关会话的整体交互流程。

sequenceDiagram
participant Sys as "系统"
participant Svc as "NodeForegroundService"
participant RT as "NodeRuntime"
participant GS as "GatewaySession"
participant GW as "网关"
Sys->>Svc : "启动前台服务"
Svc->>RT : "订阅状态流状态/服务器/连接/Mic"
RT-->>Svc : "状态变化连接/断开/麦克风状态"
Svc->>Svc : "构建/更新通知"
Note over Svc : "点击“断开”触发停止动作"
Svc->>RT : "disconnect()"
RT->>GS : "关闭会话/取消重连"
GS-->>RT : "onDisconnected 回调"
RT-->>Svc : "状态流更新"
Svc->>Svc : "更新通知为断开状态"

详细组件分析

NodeForegroundService 前台服务

  • 职责
    • 在应用启动后立即以前台服务方式运行,避免被系统回收
    • 订阅 NodeRuntime 的多源状态流,动态更新通知标题与文本
    • 提供“断开”动作,通过广播触发 NodeRuntime 断开连接并停止自身
  • 关键点
    • 使用通知通道(IMPORTANCE_LOW)与 FOREGROUND_SERVICE_TYPE_DATA_SYNC
    • 通知设置为 ongoing 且仅提示一次,确保用户可随时查看
    • 首次启动时调用 startForegroundWithTypes,后续使用 notify 更新
  • 生命周期
    • onCreate:创建通知通道、初始通知、订阅状态流
    • onStartCommand:处理 ACTION_STOP 动作,触发断开并 stopSelf
    • onDestroy:取消协程作业,释放资源
flowchart TD
Start(["服务启动"]) --> Ensure["创建通知通道"]
Ensure --> InitNotify["构建初始通知并启动前台"]
InitNotify --> Subscribe["订阅 NodeRuntime 状态流"]
Subscribe --> OnChange{"状态变化?"}
OnChange --> |是| Build["构建新通知"]
Build --> Update["notify 更新"]
OnChange --> |否| Wait["等待下次变化"]
Update --> Wait
Wait --> OnChange
StopAction["收到 ACTION_STOP"] --> Disconnect["调用 NodeRuntime.disconnect()"]
Disconnect --> StopSelf["stopSelf()"]

NodeRuntime 节点运行时与生命周期控制

  • 职责
    • 管理两个 GatewaySession:操作员会话与节点会话
    • 维护连接状态、服务器名、远端地址、主会话键等状态流
    • 自动发现与自动连接(受信任网关 TLS 指纹约束)
    • 将通知监听事件转发至节点会话
    • 管理麦克风、TTS、Canvas 等子系统
  • 连接与重连
    • runLoop 循环尝试连接,失败指数退避,保持持续重连
    • onConnected/onDisconnected 回调驱动状态流更新
  • 状态流
    • isConnected、statusText、serverName、remoteAddress、micEnabled、micIsListening 等
    • 通过 combine 组合多个流,驱动前台通知实时更新
  • 生命周期控制
    • setForeground:切换前后台,必要时停止语音会话
    • disconnect:清空目标端点并断开会话
classDiagram
class NodeRuntime {
+connect(endpoint)
+disconnect()
+refreshGatewayConnection()
+setForeground(Boolean)
+statusText : StateFlow
+isConnected : StateFlow
+serverName : StateFlow
+remoteAddress : StateFlow
+micEnabled : StateFlow
+micIsListening : StateFlow
}
class GatewaySession {
+connect(endpoint, token, password, options, tls)
+disconnect()
+reconnect()
+request(method, paramsJson)
+sendNodeEvent(event, payloadJson)
}
NodeRuntime --> GatewaySession : "管理两个会话"

网关会话(GatewaySession)与自动重连

  • 运行循环
    • runLoop:根据 desired 目标连接;失败则延迟重试,指数退避上限 8 秒
    • connectOnce:建立单次连接,等待关闭或异常
  • 请求与事件
    • request:发送 RPC 请求并等待响应,超时抛出异常
    • handleEvent:处理事件帧,识别 node.invoke.request 并派发到 onInvoke
  • TLS 与指纹
    • 支持 TLS 参数与指纹回调,首次连接时探测并保存指纹,后续自动信任
flowchart TD
Loop["runLoop 开始"] --> Target{"存在目标连接?"}
Target --> |否| Close["关闭当前连接/等待"] --> Delay["delay(250ms)"] --> Loop
Target --> |是| Connect["connectOnce 连接"]
Connect --> Ok{"连接成功?"}
Ok --> |是| Run["保持连接/等待关闭"]
Ok --> |否| Retry["增加尝试次数"] --> Backoff["指数退避(<=8s)"] --> Loop

通知监听服务与状态同步

  • DeviceNotificationListenerService
    • 实现 onNotificationPosted,解析通知为内部条目并写入存储
    • 过滤自身包名的通知,向 NodeEventSink 发送变更事件
  • 事件同步
    • NodeRuntime 初始化时设置事件 Sink,将事件转发到节点会话(node.event)
    • 前台服务订阅 NodeRuntime 状态流,实现 UI/通知与运行时状态的双向同步
sequenceDiagram
participant OS as "系统通知栏"
participant NLS as "DeviceNotificationListenerService"
participant Sink as "NodeEventSink"
participant NR as "NodeRuntime"
participant NS as "Node Session"
OS->>NLS : "通知发布"
NLS->>NLS : "解析为条目/写入存储"
NLS->>Sink : "emitNotificationsChanged(...)"
Sink->>NR : "转发事件"
NR->>NS : "sendNodeEvent(event, payload)"

设备健康与内存管理

  • DeviceHandler
    • 读取电池快照(状态、充电、电量分数、温度)
    • 读取内存快照(总内存、可用内存、是否低内存、压力等级)
    • 输出 JSON 负载供上层使用
  • 内存与电池指标可用于:
    • UI 健康指示
    • 自适应降级(如降低麦克风采样率、暂停非关键任务)

安全与配置

  • 权限与前台服务类型
    • AndroidManifest 声明 FOREGROUND_SERVICE、FOREGROUND_SERVICE_DATA_SYNC、INTERNET 等
    • NodeForegroundService 使用 FOREGROUND_SERVICE_TYPE_DATA_SYNC
  • 加密存储
    • SecurePrefs 使用 EncryptedSharedPreferences 存储网关令牌、密码、TLS 指纹等
    • 实例 ID 与显示名自动生成/迁移,保证唯一性与隐私
  • TLS 指纹校验
    • 首次连接探测指纹,用户确认后持久化,后续自动信任

依赖关系分析

  • 组件耦合
    • NodeForegroundService 强依赖 NodeRuntime 的状态流,弱依赖 NodeApp(获取 runtime)
    • NodeRuntime 依赖 GatewaySession(两个会话)、DeviceNotificationListenerService(事件源)、DeviceHandler(健康数据)、SecurePrefs(配置与凭据)
    • GatewaySession 依赖 OkHttp WebSocket、DeviceIdentityStore、DeviceAuthStore
  • 外部依赖
    • Android 系统服务:通知、前台服务、通知监听服务
    • OkHttp:WebSocket 客户端
  • 可能的循环依赖
    • 当前结构无直接循环依赖;事件通过回调与状态流解耦
graph LR
Svc["NodeForegroundService"] --> RT["NodeRuntime"]
RT --> GS["GatewaySession"]
RT --> DH["DeviceHandler"]
RT --> NLS["DeviceNotificationListenerService"]
RT --> SP["SecurePrefs"]
GS --> OkHttp["OkHttp WebSocket"]

性能与电池优化

  • 通知即时行为
    • 使用 FOREGROUND_SERVICE_IMMEDIATE,确保通知立即可见,减少用户感知延迟
  • 协程与背压
    • 使用 SupervisorJob + IO 主线程,避免主线程阻塞
    • 状态流合并使用 distinctUntilChanged,减少无效 UI 刷新
  • 自动重连与退避
    • 指数退避上限 8 秒,降低网络波动对系统的影响
  • 电池与内存
    • DeviceHandler 提供电池/内存快照,便于在低电量/低内存时降级处理
  • 建议
    • 在低电量/低内存场景下暂停非关键任务(如 Canvas 重载)
    • 控制通知频率,避免频繁更新导致 CPU/电量消耗
    • 对长耗时任务拆分为小步,配合 WorkManager 或前台服务

界面组件

Android UI 采用 Jetpack Compose 架构,以“主题层 → 布局层 → 屏幕层 → 功能组件层”的分层组织方式:

  • 主题层:统一颜色、字体与动态色方案
  • 布局层:顶部状态栏、底部导航、Scaffold 容器
  • 屏幕层:引导流程、标签页容器、各功能页(聊天、语音、屏幕、设置)
  • 功能组件层:聊天输入区、消息气泡、工具调用提示、错误提示等
graph TB
A["MainActivity<br/>设置主题与根内容"] --> B["OpenClawTheme<br/>Material3 动态色"]
B --> C["RootScreen<br/>引导/标签页入口"]
C --> D["PostOnboardingTabs<br/>Scaffold + TabBar"]
D --> E["ConnectTabScreen"]
D --> F["ChatSheetContent<br/>消息列表 + 输入区"]
D --> G["VoiceTabScreen"]
D --> H["ScreenTabScreen"]
D --> I["SettingsSheet<br/>权限与设备配置"]

核心组件

  • 主题系统:基于 Material3 动态色,提供覆盖容器色与图标色的辅助函数,确保深浅模式一致体验
  • 移动 UI 令牌:集中定义颜色、字体族与排版样式,统一视觉语言
  • 根屏幕:根据引导完成状态切换到标签页容器
  • 标签页容器:顶部状态栏 + 底部导航 + 内容区域,支持 IME 折叠与 Tab 切换
  • 设置页面:集中管理权限、位置模式、睡眠策略、通知与数据访问等
  • 聊天界面:会话选择、消息列表、输入区(文本+图片附件)、工具调用提示、实时流式助手

架构总览

Compose 驱动的单 Activity 多屏幕架构,通过 ViewModel 暴露状态流,屏幕层订阅并渲染 UI;功能组件通过参数回调与 ViewModel 交互,形成清晰的数据流向。

sequenceDiagram
participant A as "MainActivity"
participant B as "OpenClawTheme"
participant C as "RootScreen"
participant D as "PostOnboardingTabs"
participant E as "MainViewModel"
participant F as "ChatSheetContent"
participant G as "ChatComposer"
A->>B : "设置主题"
B->>C : "包裹根内容"
C->>D : "根据引导状态切换"
D->>E : "订阅状态流"
D->>F : "加载聊天/语音/屏幕/设置"
F->>E : "读取消息/会话/健康状态"
F->>G : "传递发送/刷新/中止回调"
G->>E : "触发发送/中止/切换思考级别"

详细组件分析

主题系统与颜色/字体令牌

  • 动态色方案:根据系统深浅模式选择动态亮/暗配色,作为 MaterialTheme 的 colorScheme
  • 覆盖容器色与图标色:提供 overlayContainerColor 与 overlayIconColor,用于浮层与覆盖 UI 的一致性
  • 颜色令牌:集中定义背景、表面、边框、文本、强调色、成功/警告/危险等语义色
  • 字体令牌:定义字体族与多级排版样式(标题、正文、说明、脚注等),统一在组件中引用
classDiagram
class OpenClawTheme {
+OpenClawTheme(content)
+overlayContainerColor()
+overlayIconColor()
}
class MobileUiTokens {
+mobileBackgroundGradient
+mobileSurface
+mobileText*
+mobileAccent*
+mobileCode*
+mobileFontFamily
+mobileTitle1/mobileTitle2
+mobileHeadline/mobileBody
+mobileCallout/mobileCaption1/mobileCaption2
}
OpenClawTheme --> MobileUiTokens : "使用颜色/字体令牌"

标签页容器与状态指示器

  • 顶部状态栏:根据连接状态映射为不同视觉状态(已连接、连接中、警告、错误、离线),并以色块与点标示
  • 底部导航:Tab 切换时高亮当前项,结合 IME 显示隐藏规则优化输入体验
  • 内容区域:按需渲染各功能页,如屏幕页支持“恢复仪表盘”提示
flowchart TD
A["状态文本"] --> B{"解析状态"}
B --> |已连接| C["Connected<br/>绿色系"]
B --> |连接中/重连| D["Connecting<br/>蓝色系"]
B --> |配对/授权| E["Warning<br/>橙色系"]
B --> |错误/失败| F["Error<br/>红色系"]
B --> |默认| G["Offline<br/>灰阶"]

聊天界面

  • 会话选择:横向滚动展示历史会话,当前会话高亮
  • 错误提示:错误文本出现时以警示色块显示
  • 消息列表:支持文本、Markdown、图片、工具调用、流式助手等多类型渲染
  • 输入区:支持文本输入、图片附件、思考级别选择、刷新/中止、发送按钮与禁用态
sequenceDiagram
participant U as "用户"
participant CS as "ChatSheetContent"
participant VM as "MainViewModel"
participant CC as "ChatComposer"
participant MV as "ChatMessageViews"
U->>CS : "打开聊天页"
CS->>VM : "订阅消息/会话/健康状态"
CS->>CC : "传入发送/刷新/中止回调"
U->>CC : "输入文本/选择图片/选择思考级别"
CC->>VM : "sendChat(message, thinking, attachments)"
VM-->>CS : "更新消息/健康状态"
CS->>MV : "渲染消息列表"

设置页面

  • 设备信息:实例 ID、设备型号、版本号
  • 权限管理:麦克风、相机、短信、通知、照片、联系人、日历、运动、位置(含精确位置)
  • 位置模式:Off/WhileUsing/Precise Location 三态控制
  • 屏幕常亮:防止休眠开关
  • 交互流程:权限请求通过 ActivityResultLauncher 触发,状态变更通过 ViewModel 同步
flowchart TD
A["进入设置页"] --> B["读取权限状态"]
B --> C{"是否已授予?"}
C --> |是| D["显示“管理”按钮"]
C --> |否| E["显示“授权”按钮"]
D --> F["打开系统设置或执行操作"]
E --> G["启动权限请求"]
G --> H["回调后更新状态"]

数据流与状态管理

  • ViewModel 暴露 StateFlow,屏幕与组件通过 collectAsState 订阅
  • 聊天:消息列表、错误、健康状态、思考级别、流式助手文本、待执行工具调用、会话列表
  • 连接:网关发现状态、连接状态、远端地址、服务器名
  • 语音/屏幕/节点:相机、Canvas 控制、前台服务等
classDiagram
class MainViewModel {
+canvasCurrentUrl/statusText/isConnected
+chatMessages/chatError/chatHealthOk
+chatThinkingLevel/chatStreamingAssistantText
+chatSessions/pendingRunCount
+locationMode/locationPreciseEnabled
+preventSleep/cameraEnabled
+displayName/instanceId
+set*()/connect()/disconnect()
}
class ChatSheetContent
class ChatComposer
class SettingsSheet
ChatSheetContent --> MainViewModel : "收集状态/触发操作"
ChatComposer --> MainViewModel : "发送/中止/切换思考级别"
SettingsSheet --> MainViewModel : "权限/位置/睡眠策略"

依赖关系分析

  • 组件耦合:屏幕层仅依赖 ViewModel 的 StateFlow,功能组件通过回调与 ViewModel 解耦
  • 主题与样式:所有 UI 组件统一引用 MobileUiTokens 中的颜色与排版,避免硬编码
  • 权限与系统服务:设置页通过 ActivityResultLauncher 与系统权限对话交互,避免直接持有上下文引用
graph LR
MV["MainViewModel"] --> RC["RootScreen"]
RC --> POT["PostOnboardingTabs"]
POT --> CNT["ChatSheetContent"]
POT --> SET["SettingsSheet"]
CNT --> CMP["ChatComposer"]
CNT --> MSG["ChatMessageViews"]
CMP --> THEME["OpenClawTheme"]
MSG --> THEME
SET --> THEME

性能考量

  • 布局折叠:底部导航在 IME 弹出时自动隐藏,减少重绘范围
  • 滚动优化:消息列表与会话选择使用水平滚动与懒加载容器,限制一次性渲染量
  • 图片处理:图片附件在 IO 线程解码与 Base64 编码,完成后在主线程更新 UI
  • 状态订阅:仅在必要作用域内订阅 StateFlow,避免过度重组
  • 主题与样式:集中定义样式令牌,减少重复计算与资源分配

权限与安全

Android 应用位于 apps/android/app,核心安全与权限相关文件分布如下:

  • 权限请求:PermissionRequester.kt
  • 安全偏好:SecurePrefs.kt
  • 清单与服务:AndroidManifest.xml
  • 构建与依赖:build.gradle.kts
  • 网络与备份配置:network_security_config.xml、backup_rules.xml
  • TLS 固定:GatewayTls.kt
  • 设备权限状态上报:DeviceHandler.kt
  • 设置界面权限状态展示:SettingsSheet.kt
  • 安全偏好测试:SecurePrefsTest.kt
graph TB
subgraph "Android 应用"
A["PermissionRequester<br/>权限请求器"]
B["SecurePrefs<br/>安全偏好设置"]
C["AndroidManifest<br/>权限与服务声明"]
D["build.gradle.kts<br/>构建与依赖"]
E["network_security_config.xml<br/>网络安全策略"]
F["backup_rules.xml<br/>备份规则"]
G["GatewayTls<br/>TLS 固定"]
H["DeviceHandler<br/>设备权限状态上报"]
I["SettingsSheet<br/>设置界面权限状态"]
J["SecurePrefsTest<br/>安全偏好测试"]
end
A --> C
B --> C
G --> C
H --> C
I --> C
D --> C
E --> C
F --> C
J --> B

核心组件

  • 权限请求器(PermissionRequester):封装多权限一次性请求、理由说明对话框、超时控制与设置引导。
  • 安全偏好(SecurePrefs):基于 EncryptedSharedPreferences 的敏感数据加密存储,同时维护明文偏好用于非敏感配置;支持实例 ID、网关凭据、唤醒词等。
  • 清单与服务(AndroidManifest):声明网络、定位、相机、麦克风、通知、短信、媒体访问、日历联系人等权限;注册前台服务、通知监听服务、FileProvider。
  • 网络安全策略(network_security_config.xml):允许本地与特定域名的清晰文本流量,适配受信尾随网络场景。
  • 备份规则(backup_rules.xml):启用全量文件备份。
  • TLS 固定(GatewayTls):自定义 TrustManager 实现证书指纹校验,支持首次信任(TOFU)与持久化存储。
  • 设备权限状态上报(DeviceHandler):汇总设备权限状态并以 JSON 形式上报。
  • 设置界面权限状态展示(SettingsSheet):在设置页动态检查并显示各类权限状态。

架构总览

下图展示了从“权限请求”到“安全存储”再到“网络通信”的整体链路,以及与清单和服务的关系。

sequenceDiagram
participant UI as "设置界面/调用方"
participant PR as "PermissionRequester"
participant AM as "Android 系统权限框架"
participant SP as "SecurePrefs"
participant TLS as "GatewayTls"
participant NET as "远端网关"
UI->>PR : 请求若干运行时权限
PR->>AM : 发起多权限请求
AM-->>PR : 返回授权结果
PR-->>UI : 合并当前状态并提示设置入口
UI->>SP : 写入/读取敏感配置如网关令牌
SP-->>UI : 返回解密后的值
UI->>TLS : 基于参数构建 TLS 配置
TLS-->>UI : 返回校验证书的 SSL Socket 工厂
UI->>NET : 使用已校验的 TLS 连接进行通信

详细组件分析

权限请求器(PermissionRequester)

  • 功能要点
    • 批量检测缺失权限,必要时弹出理由对话框,尊重用户选择。
    • 使用 ActivityResultLauncher 触发系统授权对话框,支持超时控制。
    • 合并当前授权状态与回调结果,对被拒绝且无理由的权限引导至系统设置。
  • 关键行为
    • 检测逻辑:仅对未授予的权限发起请求。
    • 超时与并发:通过互斥锁与超时机制避免阻塞与竞态。
    • 结果合并:若某权限在回调前已被授予,视为已授权。
  • 用户体验
    • 对相机、麦克风、短信等权限提供明确标签提示。
    • 对不可恢复权限(无理由)引导至应用详情页设置。
flowchart TD
Start(["开始"]) --> Detect["检测缺失权限"]
Detect --> AnyMissing{"存在缺失权限?"}
AnyMissing --> |否| ReturnAllTrue["返回全部已授权"]
AnyMissing --> |是| NeedRationale{"是否需要理由说明?"}
NeedRationale --> |是| ShowRationale["显示理由对话框"]
ShowRationale --> UserChoice{"用户同意?"}
UserChoice --> |否| ReturnCurrent["返回当前授权状态"]
UserChoice --> |是| Launch["启动系统权限请求"]
NeedRationale --> |否| Launch
Launch --> Await["等待授权结果或超时"]
Await --> Merge["合并当前状态与回调结果"]
Merge --> Denied{"是否存在被拒绝且无理由的权限?"}
Denied --> |是| OpenSettings["引导打开应用设置"]
Denied --> |否| Done["完成"]
OpenSettings --> Done
ReturnAllTrue --> End(["结束"])
ReturnCurrent --> End
Done --> End

安全偏好设置(SecurePrefs)

  • 数据隔离
    • 明文偏好:存放非敏感配置(如实例 ID、显示名、位置模式、唤醒词等)。
    • 加密偏好:存放敏感信息(如网关令牌、密码),使用 EncryptedSharedPreferences 与 AES256-GCM。
  • 主密钥与加密方案
    • 使用 MasterKey.AES256_GCM 作为主密钥,Key/Value 分别采用 AES256_SIV 与 AES256_GCM。
  • 生命周期与状态流
    • 大部分字段以 StateFlow 暴露,便于 UI 订阅。
  • 迁移与兼容
    • 对历史键值进行迁移(例如位置模式的“always”迁移到新枚举值)。
  • 测试覆盖
    • 单元测试验证迁移逻辑正确性。
classDiagram
class SecurePrefs {
-appContext : Context
-json : Json
-plainPrefs : SharedPreferences
-masterKey : MasterKey
-securePrefs : SharedPreferences
-_instanceId : StateFlow~String~
-_displayName : StateFlow~String~
-_locationMode : StateFlow~LocationMode~
-_gatewayToken : StateFlow~String~
+setDisplayName(value)
+setLocationMode(mode)
+setGatewayToken(token)
+saveGatewayToken(token)
+loadGatewayToken() String?
+saveGatewayPassword(password)
+loadGatewayPassword() String?
+getString(key) String?
+putString(key,value)
+remove(key)
}

Android 清单与权限配置(AndroidManifest)

  • 权限声明
    • 网络与前台服务:INTERNET、ACCESS_NETWORK_STATE、FOREGROUND_SERVICE、FOREGROUND_SERVICE_DATA_SYNC。
    • 通知与 Wi‑Fi:POST_NOTIFICATIONS、NEARBLY_WIFI_DEVICES(含 flags)、ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION。
    • 媒体与存储:CAMERA、RECORD_AUDIO、SEND_SMS、READ_MEDIA_IMAGES、READ_MEDIA_VISUAL_USER_SELECTED、READ_EXTERNAL_STORAGE(maxSdk=32)。
    • 联系人与日历:READ_CONTACTS、WRITE_CONTACTS、READ_CALENDAR、WRITE_CALENDAR。
    • 行为识别:ACTIVITY_RECOGNITION。
  • 特性声明
    • 摄像头与电话硬件为可选(required=false)。
  • 服务与 Provider
    • 前台服务、通知监听服务、FileProvider(用于分享文件)。
graph LR
M["AndroidManifest"] --> P1["网络/服务权限"]
M --> P2["媒体/存储权限"]
M --> P3["位置/通知权限"]
M --> P4["联系人/日历权限"]
M --> S1["前台服务"]
M --> S2["通知监听服务"]
M --> PDR["FileProvider"]

网络安全策略与备份规则

  • 网络安全配置
    • 允许基础清晰文本流量,针对 openclaw.local 与 ts.net 子域开放 HTTP。
    • 适用于受信尾随网络环境,降低本地开发与内网场景的 TLS 成本。
  • 备份规则
    • 启用全量文件备份,注意敏感数据应仅存于加密偏好中。

TLS 固定与安全通信

  • 目标
    • 在客户端侧对远端网关证书进行指纹校验,防止中间人攻击。
  • 实现
    • 自定义 TrustManager:当提供期望指纹时严格比对;否则回退到默认信任策略。
    • 支持首次信任(TOFU):若允许且首次成功握手,则将指纹持久化。
    • 提供 HostnameVerifier 与 SSLSocketFactory,便于 OkHttp 或原生 HTTPS 使用。
  • 使用建议
    • 对外网与非受信网络强制启用 TLS 与指纹校验;对本地环回地址可放宽策略。
sequenceDiagram
participant APP as "应用"
participant TLS as "GatewayTls"
participant CHAIN as "证书链"
participant STORE as "指纹存储"
APP->>TLS : 传入 GatewayTlsParams
TLS->>CHAIN : 获取首张证书并计算 SHA-256
alt 提供期望指纹
TLS->>TLS : 比对指纹一致?
TLS-->>APP : 一致则允许,否则取消
else 允许 TOFU
TLS->>STORE : 持久化指纹
TLS-->>APP : 允许连接
else 默认策略
TLS->>TLS : 使用系统信任策略校验
TLS-->>APP : 结果由系统决定
end

设备权限状态上报与设置页展示

  • 设备权限状态上报
    • DeviceHandler 汇总相机、麦克风、位置、照片、联系人、日历、运动、通知等权限状态,生成 JSON。
  • 设置页展示
    • SettingsSheet 在 onResume 时检查各权限状态,更新 UI 展示。

依赖关系分析

  • 构建与依赖
    • Compose、OkHttp、安全加密库(androidx.security:security-crypto)、相机库等。
    • 测试框架(Robolectric、Kotest、MockWebServer)。
  • 运行时依赖
    • 权限请求依赖 ActivityResultContracts 与 ActivityCompat。
    • 安全存储依赖 EncryptedSharedPreferences 与 MasterKey。
    • TLS 固定依赖 javax.net.ssl 与系统信任管理器。
graph TB
BR["build.gradle.kts"] --> ACT["AndroidX Activity"]
BR --> SEC["AndroidX Security Crypto"]
BR --> OK["OkHttp"]
BR --> CAM["CameraX"]
PR["PermissionRequester"] --> ACT
SP["SecurePrefs"] --> SEC
TLS["GatewayTls"] --> OK

性能考量

  • 权限请求
    • 使用互斥锁避免并发重复请求;超时控制防止 UI 卡死。
    • 合并当前状态与回调结果,减少后续判断成本。
  • 安全存储
    • EncryptedSharedPreferences 会带来一定开销,建议批量写入与合理缓存。
    • 对频繁读取的键值可引入内存缓存(当前实现以 StateFlow 为主)。
  • TLS 固定
    • 证书指纹计算与持久化需在后台线程执行,避免阻塞主线程。
    • 首次握手失败重试与超时控制,提升稳定性。

设备控制

Android 节点应用位于 apps/android/app,核心控制逻辑集中在 node 包中;网关侧策略与测试位于 src/gateway。

graph TB
subgraph "Android 节点"
ID["InvokeDispatcher<br/>命令分发器"]
DH["DeviceHandler<br/>设备信息/权限/健康"]
CH["CameraHandler<br/>相机列表/拍照/录像"]
CNH["ContactsHandler<br/>联系人搜索/新增"]
NH["NotificationsHandler<br/>通知快照/动作"]
SH["SmsHandler<br/>短信发送"]
LH["LocationHandler<br/>位置获取"]
AH["A2UIHandler<br/>A2UI 就绪/消息应用"]
end
subgraph "网关"
POL["node-command-policy.ts<br/>命令白名单/平台默认值"]
TEST["android-node.capabilities.live.test.ts<br/>能力验证测试"]
DISC["GatewayDiscovery.kt<br/>mDNS/NSD 发现"]
PAIR["message-handler.ts<br/>配对握手/拒绝"]
PNOTI["device-pair/notify.ts<br/>配对提醒轮询"]
end
ID --> DH
ID --> CH
ID --> CNH
ID --> NH
ID --> SH
ID --> LH
ID --> AH
POL --> ID
TEST --> ID
DISC --> ID
PAIR --> ID
PNOTI --> ID

核心组件

  • 命令分发器(InvokeDispatcher)
    • 统一入口,根据命令名路由到对应处理器,并进行前台限制、能力可用性检查与错误包装
    • 支持 Canvas/A2UI、相机、位置、设备、通知、系统、相册、联系人、日历、运动、短信等命令族
  • 各子处理器
    • DeviceHandler:设备状态、信息、权限、健康度
    • CameraHandler:相机设备枚举、拍照、录视频(含大小限制与错误处理)
    • ContactsHandler:联系人搜索、新增(含权限校验与批量插入)
    • NotificationsHandler:通知快照、动作执行(打开/忽略/回复)
    • SmsHandler:短信发送(错误码映射)
    • LocationHandler:位置获取(权限、精度、超时)
    • A2UIHandler:A2UI 主机解析、就绪检测、消息应用
  • 网关侧策略
    • node-command-policy.ts:按平台定义默认命令集、危险命令白名单、节点声明校验
  • 文档与测试
    • 平台文档:Android 节点连接、命令面与注意事项
    • 能力测试:覆盖 Canvas、相机、位置、设备、通知、调试等命令

架构总览

Android 节点通过 mDNS/NSD 发现网关,建立 WebSocket 连接并完成配对。命令由网关侧策略决定是否允许,节点侧通过 InvokeDispatcher 分发至各 Handler 执行,结果回传给网关。

sequenceDiagram
participant Node as "Android 节点"
participant Disc as "GatewayDiscovery<br/>mDNS/NSD"
participant GW as "Gateway"
participant Policy as "node-command-policy.ts"
participant Disp as "InvokeDispatcher"
participant H as "各 Handler"
Node->>Disc : 发现 _openclaw-gw._tcp
Disc-->>Node : 返回网关地址/端口
Node->>GW : 建立 WebSocket 连接
GW-->>Node : 握手/配对请求
alt 未配对且非静默
GW-->>Node : 拒绝连接并提示配对
else 已配对或静默
GW-->>Node : 允许连接
end
Node->>GW : node.invoke(command, params)
GW->>Policy : 校验命令是否允许
Policy-->>GW : 允许/拒绝
GW->>Disp : 调用分发器
Disp->>H : 路由到具体处理器
H-->>Disp : 执行结果/错误
Disp-->>GW : 包装响应
GW-->>Node : 返回 payload 或错误

详细组件分析

命令分发与注册机制(NodeHandler 系统)

  • 命令注册
    • 通过 InvokeCommandRegistry 查找命令规格,包含命令名、是否要求前台、可用性条件
    • 可用性条件支持:Always、CameraEnabled、LocationEnabled、SmsAvailable、MotionActivityAvailable、MotionPedometerAvailable、DebugBuild
  • 分发流程
    • 若命令未知,返回 INVALID_REQUEST
    • 若命令要求前台但节点不在前台,返回 NODE_BACKGROUND_UNAVAILABLE
    • 根据可用性条件检查(如相机/位置/短信/运动),不满足则返回相应错误码
    • 路由到具体处理器执行,A2UI 需要先确保 Canvas 可用与 A2UI 主机就绪
  • 错误处理
    • 统一包装为 GatewaySession.InvokeResult,包含 ok、payload 或 error(code/message)
flowchart TD
Start(["收到 node.invoke"]) --> Lookup["查找命令规格"]
Lookup --> Unknown{"未知命令?"}
Unknown --> |是| ErrUnknown["返回 INVALID_REQUEST"]
Unknown --> |否| Foreground{"需要前台?"}
Foreground --> |是且不在前台| ErrBg["返回 NODE_BACKGROUND_UNAVAILABLE"]
Foreground --> |否| Avail["检查可用性条件"]
Avail --> Denied{"条件不满足?"}
Denied --> |是| ErrCond["返回对应错误码"]
Denied --> |否| Route["路由到具体处理器"]
Route --> Exec["执行并返回结果"]
Exec --> End(["结束"])
ErrUnknown --> End
ErrBg --> End
ErrCond --> End

设备权限与状态(DeviceHandler)

  • 设备状态:电池、热状态、存储、网络连通性、耗电模式、uptime
  • 设备信息:设备名、型号标识、系统版本、应用版本/构建号、语言区域
  • 权限状态:相机、麦克风、位置、短信、通知监听、通知、相册、通讯录、日历、运动
  • 健康度:内存压力、电池状态/充电类型、温度、电流、Doze/LowPower 模式、安全补丁级别
classDiagram
class DeviceHandler {
+handleDeviceStatus(params) InvokeResult
+handleDeviceInfo(params) InvokeResult
+handleDevicePermissions(params) InvokeResult
+handleDeviceHealth(params) InvokeResult
-statusPayloadJson() String
-infoPayloadJson() String
-permissionsPayloadJson() String
-healthPayloadJson() String
}

相机控制(CameraHandler)

  • 列表:列举可用相机设备
  • 拍照:触发闪光、HUD 提示、捕获并返回 base64 图像
  • 录像:可选包含外部音频,限制最大负载,过大则删除临时文件并返回 PAYLOAD_TOO_LARGE
  • 错误处理:将异常映射为错误码与用户可读消息
sequenceDiagram
participant GW as "网关"
participant Disp as "InvokeDispatcher"
participant Cam as "CameraHandler"
GW->>Disp : node.invoke(camera.snap/clip)
Disp->>Cam : handleSnap/handleClip
Cam->>Cam : 触发 HUD/闪光/开始录制
Cam-->>Disp : 成功返回 base64/元数据
Disp-->>GW : InvokeResult.ok

联系人管理(ContactsHandler)

  • 搜索:支持查询关键字与数量上限,返回联系人列表
  • 新增:支持姓名、组织、电话、邮箱,通过批量插入写入系统通讯录
  • 权限:读取需 READ_CONTACTS,写入需 WRITE_CONTACTS;无权限直接返回 CONTACTS_PERMISSION_REQUIRED
flowchart TD
S(["contacts.search"]) --> CheckPerm["检查 READ_CONTACTS 权限"]
CheckPerm --> |无| ErrPerm["返回 CONTACTS_PERMISSION_REQUIRED"]
CheckPerm --> |有| Query["查询联系人"]
Query --> Ok["返回 contacts[]"]
A(["contacts.add"]) --> CheckWrite["检查 WRITE_CONTACTS 权限"]
CheckWrite --> |无| ErrPermAdd["返回 CONTACTS_PERMISSION_REQUIRED"]
CheckWrite --> |有| Validate["校验参数姓名/组织/电话/邮箱至少一项"]
Validate --> |无效| ErrInvalid["返回 CONTACTS_INVALID"]
Validate --> |有效| Insert["批量插入系统通讯录"]
Insert --> OkAdd["返回新增联系人"]

通知处理(NotificationsHandler)

  • 快照:读取通知快照(若启用监听但未连接,尝试重新绑定)
  • 动作:支持 open、dismiss、reply,reply 需要 replyText
  • 失败:返回 UNAVAILABLE 或具体错误码
sequenceDiagram
participant GW as "网关"
participant Disp as "InvokeDispatcher"
participant Noti as "NotificationsHandler"
GW->>Disp : node.invoke(notifications.list/actions)
Disp->>Noti : handleNotificationsList/handleNotificationsActions
Noti->>Noti : 读取快照/必要时重绑
Noti-->>Disp : 返回快照或执行结果
Disp-->>GW : InvokeResult

短信发送(SmsHandler)

  • 参数解析后委托底层 SmsManager 执行
  • 错误映射:将内部错误字符串按冒号前缀提取为错误码,返回 SMS_SEND_FAILED 默认码

位置服务(LocationHandler)

  • 权限:需要粗/精定位之一;前台运行时才允许
  • 精度:precise/coarse/balanced,受精确权限与系统设置影响
  • 超时:默认 10 秒,范围 1–60 秒
  • 异常:超时返回 LOCATION_TIMEOUT,其他异常返回 LOCATION_UNAVAILABLE

A2UI 交互(A2UIHandler)

  • 主机解析:优先节点 Canvas 主机,否则回退到运营者主机
  • 就绪检测:导航到 A2UI 页面并轮询检查 ready 标志
  • 消息应用:支持 messages 数组或 jsonl 字段,严格校验 v0.8 消息格式

网关命令策略与平台默认

  • 平台默认命令集:Android 默认开放 Canvas、Camera、Location、通知、系统通知、设备信息/状态/权限/健康、联系人、日历、提醒、相册、运动等命令
  • 危险命令:相机拍照/录像、屏幕录制、联系人新增、日历新增、提醒新增、短信发送等需显式允许
  • 声明校验:命令必须同时在节点声明的 commands 列表中

设备发现、蓝牙配对与网络通信

  • 设备发现:Android 使用 mDNS/NSD 发现 _openclaw-gw._tcp,支持本地与单播 DNS-SD(跨网络场景)
  • 蓝牙配对:通过网关握手阶段触发配对请求,未配对且非静默时拒绝连接并提示
  • 网络通信:WebSocket 连接,支持 Token/密码认证与 TLS

依赖关系分析

  • 节点侧
    • InvokeDispatcher 依赖各 Handler 与 A2UIHandler,受 InvokeCommandRegistry 的规格约束
    • 各 Handler 依赖系统服务(相机、联系人、通知、位置、短信等)
  • 网关侧
    • node-command-policy.ts 决定命令允许与否,结合节点声明与配置
    • 测试用例覆盖 Android 节点能力矩阵,验证命令返回结构与错误码
graph LR
Reg["InvokeCommandRegistry"] --> Disp["InvokeDispatcher"]
Disp --> DH["DeviceHandler"]
Disp --> CH["CameraHandler"]
Disp --> CNH["ContactsHandler"]
Disp --> NH["NotificationsHandler"]
Disp --> SH["SmsHandler"]
Disp --> LH["LocationHandler"]
Disp --> AH["A2UIHandler"]
Policy["node-command-policy.ts"] --> Disp
Test["android-node.capabilities.live.test.ts"] --> Disp

性能考量

  • 相机录视频负载限制:超过阈值会删除临时文件并返回错误,避免大包导致传输失败
  • 前台限制:Canvas/A2UI/相机/录屏等命令仅在前台可用,减少后台资源占用
  • 通知监听:若监听未连接,自动尝试重绑,降低用户干预成本
  • 网络发现:本地与单播 DNS-SD 双通道,提升跨网络发现成功率

网关通信

Android 网关通信相关代码主要位于 Android 应用模块中,核心类为 GatewaySession 与 DeviceAuthPayload;同时在后端/通用层提供协议定义与校验工具。

graph TB
subgraph "Android 应用"
GS["GatewaySession.kt<br/>会话管理/连接/消息处理"]
DAP["DeviceAuthPayload.kt<br/>认证载荷构建/归一化"]
end
subgraph "通用协议"
PI["protocol/index.ts<br/>帧校验/常量导出"]
SCHEMA["protocol/schema.ts<br/>协议模式入口"]
end
GS --> DAP
GS --> PI
PI --> SCHEMA

核心组件

  • GatewaySession(Android)
    • 负责 WebSocket 连接、消息收发、RPC 请求/响应、事件分发、节点调用请求处理、TLS 配置与指纹回调、Canvas URL 规范化等
    • 内部 Connection 类封装单次连接细节,包括握手、认证、心跳与断线处理
  • DeviceAuthPayload(Android)
    • 构建 v3 设备认证载荷字符串,统一元数据字段大小写规则,便于跨运行时一致性校验
  • 协议与校验(通用)
    • 提供帧类型、参数 Schema、AJV 校验器、协议版本常量与错误码导出

架构总览

Android 端通过 OkHttp WebSocket 客户端发起连接,按“握手挑战—连接参数—认证—会话建立”的顺序完成握手;随后进入消息循环,区分“响应帧”和“事件帧”,并支持节点侧向客户端发起的“invoke 请求”。

sequenceDiagram
participant App as "Android 应用"
participant WS as "OkHttp WebSocket"
participant Srv as "网关服务器"
App->>WS : "建立 WebSocket 连接"
WS-->>App : "onOpen()"
App->>Srv : "等待 connect.challenge 事件"
Srv-->>App : "事件 : connect.challenge { nonce }"
App->>App : "构造 connect 参数 + 设备签名"
App->>Srv : "RPC : connect"
Srv-->>App : "响应 : connect 成功 + 会话信息"
App->>App : "保存设备令牌/Canvas URL/会话键"
App->>Srv : "心跳/事件/请求/响应 循环"

组件详解

GatewaySession 会话管理

  • 连接与生命周期
    • 支持 connect/disconnect/reconnect,内部使用协程与互斥锁保证并发安全
    • 运行循环按指数回退策略重连,最大延迟上限控制
  • 消息处理
    • onMessage 解析 JSON 帧,区分 "res"(响应)与 "event"(事件)
    • pending 映射用于匹配请求 ID 与响应
  • 认证与握手
    • 等待 connect.challenge 事件提取 nonce,随后发送 connect RPC
    • 可选携带 token/password 与设备签名(公钥、签名、时间戳、nonce)
  • 节点调用
    • 接收 "node.invoke.request" 事件,调用应用提供的处理器,再以 "node.invoke.result" 回_ack_
  • Canvas URL 规范化
    • 根据连接是否 TLS 以及返回的 canvasHostUrl,修正 scheme/port/path/query/fragment
classDiagram
class GatewaySession {
+connect(endpoint, token, password, options, tls)
+disconnect()
+reconnect()
+request(method, paramsJson, timeoutMs)
+sendNodeEvent(event, payloadJson)
+refreshNodeCanvasCapability(timeoutMs)
}
class Connection {
+connect()
+request(method, params, timeoutMs)
+awaitClose()
-sendConnect(nonce)
-handleMessage(text)
-handleEvent(frame)
-handleResponse(frame)
-handleInvokeEvent(payloadJson)
-sendInvokeResult(id, nodeId, result, timeoutMs)
}
GatewaySession --> Connection : "持有并管理"

DeviceAuthPayload 认证机制

  • 载荷格式
    • v3 版本字符串由固定字段拼接,字段包括版本、设备 ID、客户端 ID、模式、角色、作用域列表、签名时间、令牌、nonce、平台、设备系列
    • 平台与设备系列字段进行大小写归一化(仅 ASCII A-Z 转小写),确保跨运行时一致性
  • 使用场景
    • 在 connect 参数中生成设备签名,随同公钥一起提交给网关,由网关验证签名与时间戳
flowchart TD
Start(["开始"]) --> BuildFields["拼接字段: v3|deviceId|clientId|mode|role|scopes|signedAtMs|token|nonce|platform|deviceFamily"]
BuildFields --> Normalize["平台/设备系列字段大小写归一化"]
Normalize --> Join["以'|'连接为最终载荷字符串"]
Join --> Sign["使用设备私钥对载荷签名"]
Sign --> Done(["结束"])

协议常量与校验(通用)

  • 协议版本
    • 通过 schema 导入统一导出协议版本常量,客户端在 connect 参数中声明 min/maxProtocol
  • 帧与参数校验
    • 使用 AJV 编译各 Schema,提供 validateXxx 函数用于请求/响应/事件/参数的运行时校验
    • formatValidationErrors 将校验错误格式化为可读字符串
  • 错误码与帧类型
    • 导出 ErrorCodes、GatewayFrame、RequestFrame、ResponseFrame、EventFrame 等类型与校验器
graph LR
IDX["protocol/index.ts"] --> SCH["protocol/schema.ts"]
IDX --> VALID["AJV 校验器"]
IDX --> CONST["协议常量/版本"]

心跳检测与保活

  • 服务端心跳
    • 网关定期下发 "tick" 事件,客户端收到后更新最近心跳时间
  • 客户端保活
    • OkHttp 客户端设置 pingInterval,默认 30 秒;若长时间无消息,可结合业务层 watchdog 强制重连(参考其他平台实现)

消息序列化与反序列化

  • 发送
    • request 方法构造 "req" 帧,序列化为 JSON 后通过 WebSocket 发送
  • 接收
    • onMessage 解析文本为 JSON 对象,根据 "type" 分派到 handleResponse 或 handleEvent
    • 响应帧通过 pending 映射匹配请求 ID,超时抛出异常
  • 节点调用结果
    • invoke 结果以 "node.invoke.result" RPC 返回,支持 payload 与 error 字段

连接配置与 TLS

  • TLS 配置
    • 可选 SSL Socket Factory 与 Hostname Verifier,支持自定义证书指纹校验回调
  • Ping 与超时
    • 写超时 60 秒,读超时无限制,ping 间隔 30 秒
  • Canvas URL 规范化
    • 根据连接是否 TLS 修正 https/scheme 与端口,保留路径/查询/片段

节点调用(GatewaySessionInvoke)

  • 事件到 RPC 的桥接
    • 当收到 "node.invoke.request" 事件时,解析参数(id/nodeId/command/paramsJSON/timeoutMs),调用应用提供的处理器
    • 处理完成后以 "node.invoke.result" 发送结果,超时范围在 15–120 秒之间
  • 测试场景
    • 单测覆盖握手、事件分发、结果回传与关闭流程
sequenceDiagram
participant GS as "GatewaySession.Connection"
participant App as "应用处理器"
GS->>GS : "接收事件 : node.invoke.request"
GS->>App : "调用处理器(InvokeRequest)"
App-->>GS : "返回 InvokeResult"
GS->>GS : "构造 node.invoke.result 参数"
GS-->>GS : "发送 RPC : node.invoke.result"

依赖关系分析

  • Android 端
    • GatewaySession 依赖 OkHttp WebSocket、Kotlinx Serialization、协程与互斥锁
    • DeviceAuthPayload 作为纯函数对象,被 Connection 构造 connect 参数时调用
  • 通用层
    • protocol/index.ts 导出 AJV 校验器与协议常量,供服务端/客户端共享
    • schema.ts 汇总各类 Schema,形成强类型协议模型
graph TB
A["GatewaySession.kt"] --> B["DeviceAuthPayload.kt"]
A --> C["protocol/index.ts"]
C --> D["protocol/schema.ts"]

性能与可靠性

  • 指数回退重连
    • 连接失败时按 1.7 指数增长延迟,上限 8 秒,避免风暴式重试
  • 超时与确认
    • connect RPC 超时 12 秒,invoke 结果确认超时在 15–120 秒区间
  • 心跳与保活
    • OkHttp ping 间隔 30 秒;业务层可结合 watchdog 在长时间无消息时触发重连
  • TLS 与指纹
    • 可配置自定义证书链与主机名校验,并在连接建立时回调指纹,便于运维审计

相机与屏幕录制

Android 相关实现集中在 apps/android/app 模块,核心文件包括:

  • 节点命令处理器:CameraHandler、PhotosHandler
  • 相机采集与录制:CameraCaptureManager
  • 状态与权限:CameraHudState、PermissionRequester
  • 辅助工具:JpegSizeLimiter
  • 文档参考:docs/platforms/android.md
graph TB
subgraph "Android 应用"
CH["CameraHandler<br/>节点命令入口"]
CCM["CameraCaptureManager<br/>相机采集/录制"]
PH["PhotosHandler<br/>相册读取"]
PR["PermissionRequester<br/>权限申请"]
HLS["CameraHudState<br/>HUD 状态模型"]
JSL["JpegSizeLimiter<br/>JPEG 尺寸限制器"]
end
CH --> CCM
CH --> HLS
CH --> PR
PH --> PR
CCM --> PR
CCM --> JSL

核心组件

  • CameraHudState:定义相机 HUD 的状态类型(拍照、录制、成功、错误),用于 UI 提示
  • CameraHandler:节点命令入口,负责 camera.list、camera.snap、camera.clip 的调用与结果封装
  • CameraCaptureManager:实际执行相机操作,含权限检查、设备选择、拍照与录制、文件输出与事件监听
  • PhotosHandler:读取系统相册最新图片,按预算压缩为 JPEG 并返回 base64
  • PermissionRequester:统一的权限申请与引导,支持理由说明与设置页跳转
  • JpegSizeLimiter:在尺寸与质量之间迭代压缩,确保输出不超过上限

架构总览

下图展示了从节点命令到相机采集与录制的整体流程,以及与权限系统的交互。

sequenceDiagram
participant Node as "节点命令"
participant Handler as "CameraHandler"
participant Manager as "CameraCaptureManager"
participant Perm as "PermissionRequester"
participant Cam as "相机系统(CamerX)"
participant UI as "HUD 状态"
Node->>Handler : 调用 camera.snap 或 camera.clip
Handler->>UI : 显示提示(拍照/录制)
alt 需要相机权限
Handler->>Perm : 请求相机权限
Perm-->>Handler : 返回授权结果
end
Handler->>Manager : 执行 snap/clip
Manager->>Cam : 绑定生命周期/选择相机/开始录制
Cam-->>Manager : 回调事件(Finalize/状态)
Manager-->>Handler : 返回结果(照片/文件)
Handler->>UI : 显示成功/错误提示
Handler-->>Node : 返回 JSON 结果

组件详解

相机状态管理:CameraHudState

  • 定义状态类型:Photo(拍照)、Recording(录制中)、Success(成功)、Error(错误)
  • 数据结构包含 token、kind、message,用于 UI 层渲染与自动隐藏

相机处理器:CameraHandler

职责与流程:

  • 设备列表:调用 CameraCaptureManager.listDevices,返回设备数组
  • 拍照:显示 HUD、触发闪光、调用 snap,返回 base64 JPEG;失败时显示错误 HUD
  • 录制:显示 HUD、可选启用外部音频、调用 clip,将 mp4 文件读入内存并 base64 编码返回;超过阈值则删除临时文件并报错

关键行为:

  • 参数解析:includeAudio、durationMs、deviceId、facing、quality、maxWidth
  • 负载限制:对 clip 的二进制大小进行上限检查,避免 WebSocket 超限
  • HUD 生命周期:成功/错误提示与自动隐藏时间

相机采集与录制:CameraCaptureManager

职责与流程:

  • 设备枚举:通过 ProcessCameraProvider 获取可用摄像头并映射为设备信息
  • 权限保障:ensureCameraPermission / ensureMicPermission
  • 拍照:
    • 解析参数:facing、quality、maxWidth、deviceId
    • 绑定生命周期并拍摄 JPEG,读取 EXIF 方向并旋转,按 maxWidth 缩放
    • 使用 JpegSizeLimiter 控制 JPEG 大小,返回 JSON 字符串
  • 录制:
    • 解析参数:facing、durationMs、includeAudio、deviceId
    • 设置最低质量以减小文件体积
    • 绑定 Preview 与 VideoCapture,预热后开始录制,延时后停止
    • 监听 Finalize 事件,超时或失败时清理临时文件并抛出异常
    • 返回 File 与元数据(时长、是否含音频)

辅助工具:

  • takeJpegWithExif:异步拍摄 JPEG 并返回字节与 EXIF 方向
  • cameraDeviceInfoOrNull / cameraIdOrNull:从 CameraInfo 解析设备信息
  • JpegSizeLimiter:在尺寸与质量间迭代压缩,确保不超过上限

相册处理:PhotosHandler

职责与流程:

  • 权限检查:根据系统版本选择 READ_MEDIA_IMAGES 或 READ_EXTERNAL_STORAGE
  • 查询策略:按拍摄时间与添加时间降序查询最近图片
  • 解码与缩放:按最大宽度计算 inSampleSize 并解码,必要时缩放
  • 压缩与预算:使用预算约束编码 JPEG,逐张评估 base64 长度,累计不超过总预算
  • 返回结构:每张图片包含 format、base64、width、height、createdAt

权限管理:PermissionRequester

  • 支持多权限一次性申请
  • 对需要理由的权限弹窗说明用途
  • 对被拒绝且不再提示的权限,引导用户前往应用设置开启
  • 内部互斥与超时控制,保证并发安全

JPEG 尺寸限制器:JpegSizeLimiter

  • 输入:初始宽高、起始质量、最大字节数
  • 策略:优先降低质量,再逐步缩小尺寸,直到满足上限
  • 输出:最终字节、宽高、质量

屏幕录制(跨平台对比)

  • iOS 实现要点:
    • 计算配置:时长、帧率、音频开关、输出路径
    • 启动/停止/写入:分阶段回调,准备写入器、处理视频样本、完成写入
    • FPS 采样间隔:按目标帧率去抖,避免过量写入
  • macOS 实现要点:
    • 可选音频输入配置(AAC)
    • 流停止错误记录与日志

依赖关系分析

  • CameraHandler 依赖 CameraCaptureManager、PermissionRequester、CameraHudState
  • CameraCaptureManager 依赖 CameraX(ProcessCameraProvider、ImageCapture、VideoCapture)、ExifInterface、JpegSizeLimiter
  • PhotosHandler 依赖系统媒体存储 ContentResolver、Bitmap 解码与压缩
  • PermissionRequester 依赖 Android ActivityResultLauncher 与系统设置页面
classDiagram
class CameraHandler {
+handleList(params)
+handleSnap(params)
+handleClip(params)
}
class CameraCaptureManager {
+listDevices()
+snap(params)
+clip(params)
}
class PermissionRequester {
+requestIfMissing(perms)
}
class PhotosHandler {
+handlePhotosLatest(params)
}
class JpegSizeLimiter {
+compressToLimit(...)
}
class CameraHudState {
+token
+kind
+message
}
CameraHandler --> CameraCaptureManager : "调用"
CameraHandler --> PermissionRequester : "请求权限"
CameraHandler --> CameraHudState : "更新HUD"
CameraCaptureManager --> JpegSizeLimiter : "压缩JPEG"
PhotosHandler --> PermissionRequester : "读取相册需权限"

性能考量

  • 拍照路径
    • 预热与方向:先旋转再缩放,减少重复变换开销
    • 压缩预算:JPEG 压缩与尺寸缩放双管齐下,确保 payload 不超限
  • 录制路径
    • 最低质量:优先降低质量而非分辨率,兼顾体积与清晰度
    • 预览绑定:强制绑定 Preview 以激活编码管线,避免无有效数据
    • 超时与清理:录制完成后等待 Finalize,超时或失败及时删除临时文件
  • 相册读取
    • inSampleSize 估算与按预算编码,避免一次性加载过大位图
    • 累计预算控制,保证多图返回不越界

应用架构

Android 应用位于 apps/android/app 模块,采用 Kotlin + Jetpack Compose UI + OkHttp WebSocket 通信的现代 Android 技术栈。核心目录与职责概览:

  • app/src/main/java/ai/openclaw/app
    • 入口与生命周期:NodeApp、MainActivity、NodeForegroundService
    • 状态与视图模型:MainViewModel
    • 核心运行时:NodeRuntime 及其子系统(网关会话、Canvas、相机、语音等)
    • UI 层:RootScreen 与各功能页(Compose)
    • 配置与清单:AndroidManifest.xml、build.gradle.kts
  • app/src/main/res:资源与主题
  • 测试:app/src/test/java 下按功能域分层测试
graph TB
subgraph "应用进程"
NA["NodeApp<br/>Application"]
MA["MainActivity<br/>ComponentActivity"]
VM["MainViewModel<br/>AndroidViewModel"]
NS["NodeForegroundService<br/>Service"]
end
subgraph "运行时核心"
NR["NodeRuntime<br/>核心编排"]
GS["GatewaySession<br/>操作端/节点端"]
CC["CanvasController<br/>WebView 承载"]
CM["CameraCaptureManager<br/>CameraX 封装"]
end
NA --> NR
MA --> VM
VM --> NR
MA --> NS
NR --> GS
NR --> CC
NR --> CM

核心组件

  • NodeApp:应用入口,负责严格模式调试与延迟初始化 NodeRuntime 单例
  • MainActivity:承载 Compose UI,绑定 MainViewModel,处理权限与系统窗口标志,延后启动前台服务
  • MainViewModel:将 NodeRuntime 的大量 StateFlow 暴露给 UI,并转发用户设置与连接控制命令
  • NodeRuntime:应用核心运行时,聚合网关会话、Canvas、相机、位置、短信、语音等子系统,统一状态与事件分发
  • NodeForegroundService:前台服务,根据 NodeRuntime 状态流动态更新通知
  • GatewaySession:封装 WebSocket 连接、鉴权、RPC 请求/响应、事件分发与自动重连
  • CanvasController:WebView 容器与调试状态注入,提供快照与脚本执行能力
  • CameraCaptureManager:基于 CameraX 的拍照/视频录制封装,含权限与 EXIF 方向处理

架构总览

应用采用“单例运行时 + 响应式状态流”的架构模式:

  • NodeApp.lazy 持有 NodeRuntime,避免冷启动路径冗余
  • NodeRuntime 使用协程与 StateFlow 组织多源状态(连接、Canvas、相机、语音、聊天等),并通过 GatewaySession 与远端网关保持双向通信
  • MainActivity 仅承担 UI 与生命周期职责,通过 MainViewModel 访问运行时能力
  • NodeForegroundService 以前台服务形式常驻,实时反映连接状态与麦克风监听状态
sequenceDiagram
participant App as "NodeApp"
participant Runtime as "NodeRuntime"
participant Operator as "GatewaySession(操作端)"
participant Node as "GatewaySession(节点端)"
participant Service as "NodeForegroundService"
App->>Runtime : lazy 初始化
Note over Runtime : 启动协程作用域并注册各子系统
Runtime->>Operator : 构建连接参数并发起连接
Runtime->>Node : 构建连接参数并发起连接
Operator-->>Runtime : 连接成功/失败回调
Node-->>Runtime : 连接成功/失败回调
Runtime-->>Service : 状态流变化连接/服务器名/麦克风
Service-->>Service : 更新通知

详细组件分析

启动流程与生命周期

  • 应用启动
    • AndroidManifest 指定 Application 为 NodeApp,Activity 为 MainActivity
    • NodeApp 在 onCreate 中启用严格模式(调试构建)并延迟初始化 NodeRuntime
  • MainActivity 生命周期
    • onCreate:禁用系统窗口装饰适配、初始化 PermissionRequester、绑定相机/短信权限请求器、设置 UI 主题与根屏幕、在首帧后延时启动 NodeForegroundService
    • onStart/onStop:切换前台状态,影响 NodeRuntime 内部的语音会话与外部音频捕获标记
  • 前台服务
    • NodeForegroundService 在 onCreate 中创建通知通道并首次显示“正在启动”通知
    • 通过组合 NodeRuntime 的多个状态流,动态更新标题与内容;支持从通知触发断开连接
flowchart TD
Start(["应用启动"]) --> AppInit["NodeApp.onCreate()<br/>启用严格模式"]
AppInit --> RuntimeInit["NodeApp.runtime.lazy 初始化"]
RuntimeInit --> ActivityInit["MainActivity.onCreate()"]
ActivityInit --> BindPerms["绑定权限请求器"]
ActivityInit --> SetUI["设置主题与根屏幕"]
ActivityInit --> DelayStart["首帧后延时启动前台服务"]
ActivityInit --> Foreground["NodeForegroundService.start()"]
Foreground --> CombineState["合并状态流<br/>连接/服务器/麦克风"]
CombineState --> UpdateNotify["更新通知"]
ActivityInit --> OnStart["onStart() 设置前台=true"]
ActivityInit --> OnStop["onStop() 设置前台=false"]

状态管理与 MainViewModel

  • MainViewModel 作为 AndroidViewModel,持有 NodeApp.runtime 并直接暴露 NodeRuntime 的大量 StateFlow(连接状态、Canvas 状态、相机/位置/麦克风/扬声器、聊天状态等)
  • 提供 setter 方法将用户设置与连接控制命令转发至 NodeRuntime,实现 UI 对运行时的可控访问
  • 通过 viewModels() 在 MainActivity 中获取实例,确保进程内共享与生命周期感知
classDiagram
class MainViewModel {
+canvas : CanvasController
+camera : CameraCaptureManager
+sms : SmsManager
+isConnected : StateFlow<Boolean>
+statusText : StateFlow<String>
+chat* 系列状态
+set*() 用户设置方法
+connect()/disconnect()
+sendChat()/abortChat()
}
class NodeRuntime {
+canvas : CanvasController
+camera : CameraCaptureManager
+sms : SmsManager
+gateways/statusText/pendingGatewayTrust
+chat* 系列状态
+set*() 用户设置
+connect()/disconnect()
+sendChat()/abortChat()
}
MainViewModel --> NodeRuntime : "委托调用"

NodeRuntime:运行时核心

  • 协程与作用域:使用 SupervisorJob + Dispatchers.IO 管理子系统任务,保证异常不扩散
  • 子系统聚合:Canvas、相机、位置、短信、通知、系统、照片、联系人、日历、运动、A2UI、Invoke 分发器等
  • 网关会话:维护两个 GatewaySession(操作端与节点端),分别处理连接、断开、事件与 RPC 请求
  • 状态流:对外暴露大量只读 StateFlow,内部通过 MutableStateFlow 维护可变状态并进行去抖与合并
  • 自动连接:依据偏好设置与发现列表,自动连接可信网关(基于存储的 TLS 指纹)
  • Canvas A2UI:支持从 WebView 触发 agent.request 并回传状态反馈
classDiagram
class NodeRuntime {
-scope : CoroutineScope
-canvas : CanvasController
-camera : CameraCaptureManager
-location : LocationCaptureManager
-sms : SmsManager
-discovery : GatewayDiscovery
-operatorSession : GatewaySession
-nodeSession : GatewaySession
-invokeDispatcher : InvokeDispatcher
+connect()/disconnect()
+requestCanvasRehydrate()
+handleCanvasA2UIActionFromWebView()
}
class GatewaySession {
+connect()
+disconnect()
+reconnect()
+request()
+sendNodeEvent()
}
class CanvasController {
+attach()/detach()
+navigate()
+eval()/snapshot*
}
NodeRuntime --> CanvasController : "持有"
NodeRuntime --> GatewaySession : "两个会话"
NodeRuntime --> CanvasController : "A2UI 交互"

UI 与导航

  • RootScreen:根据 onboardingCompleted 决定展示引导流程或主标签页
  • MainActivity:设置系统窗口装饰、权限请求器、绑定相机/短信权限、根据生命周期控制“防休眠”标志位、渲染主题与根屏幕
  • NodeForegroundService:根据运行时状态流动态更新通知,支持从通知断开连接
sequenceDiagram
participant UI as "RootScreen"
participant VM as "MainViewModel"
participant NR as "NodeRuntime"
UI->>VM : collect onboardingCompleted
alt 未完成
UI->>UI : 显示引导流程
else 已完成
UI->>UI : 显示主标签页
end
VM->>NR : set*()/connect()/sendChat()

网络与安全

  • GatewaySession 使用 OkHttp WebSocket 实现连接、鉴权、RPC 请求与事件分发
  • 支持 TLS 参数解析与指纹校验,首次连接时捕获指纹并提示用户验证,随后持久化到偏好中
  • 自动重连策略随失败次数指数退避,上限保护
flowchart TD
Connect["发起连接"] --> Challenge["接收挑战 nonce"]
Challenge --> Auth["构造 connect 参数<br/>签名/公钥/角色/权限"]
Auth --> Send["发送 connect 请求"]
Send --> Resp{"响应 ok?"}
Resp -- 是 --> Ready["建立会话<br/>保存 canvasHostUrl/mainSessionKey"]
Resp -- 否 --> Fail["抛出错误并断开"]
Ready --> Event["事件/请求分发"]
Event --> Retry["异常/断开后按指数退避重连"]

设备能力与媒体

  • CanvasController:WebView 容器,支持导航、调试状态注入、JS 评估与图片快照(PNG/JPEG)
  • CameraCaptureManager:基于 CameraX 的拍照与视频录制,含 EXIF 方向旋转、质量压缩、最大尺寸限制与权限检查
classDiagram
class CanvasController {
+attach()/detach()
+navigate()
+eval()
+snapshotPngBase64()
+snapshotBase64()
}
class CameraCaptureManager {
+snap()
+clip()
+listDevices()
-ensureCameraPermission()
-ensureMicPermission()
}
NodeRuntime --> CanvasController : "持有"
NodeRuntime --> CameraCaptureManager : "持有"

依赖关系分析

  • 模块耦合
    • NodeApp 与 NodeRuntime:单例持有,低耦合高内聚
    • MainActivity 与 MainViewModel:通过 ViewModelProvider 解耦,UI 不直接依赖运行时
    • NodeRuntime 与子系统:通过组合模式聚合,职责清晰
  • 外部依赖
    • Jetpack Compose、Material3、Navigation
    • OkHttp WebSocket、CameraX、Kotlinx Serialization、Kotlinx Coroutines
    • BouncyCastle、CommonMark 等
graph LR
NA["NodeApp"] --> NR["NodeRuntime"]
MA["MainActivity"] --> VM["MainViewModel"]
VM --> NR
NR --> GS["GatewaySession"]
NR --> CC["CanvasController"]
NR --> CM["CameraCaptureManager"]
MA --> NS["NodeForegroundService"]

性能考量

  • 启动路径优化:MainActivity 在首帧后才启动前台服务,减少冷启动阻塞
  • 状态流去抖与合并:NodeRuntime 使用 combine + distinctUntilChanged 控制 UI 更新频率
  • 图片快照与压缩:CanvasController 与 CameraCaptureManager 对图片进行缩放与质量压缩,避免超大负载
  • 协程调度:IO 调度器用于网络与磁盘 IO,Main 调度器用于 UI 相关操作
  • 通知更新:NodeForegroundService 仅在状态变化时更新通知,降低系统开销

语音功能

Android 语音相关代码主要位于应用模块的 voice 包与根级配置类中,配合 UI 层的引导与权限请求,形成完整的语音工作流。

graph TB
subgraph "Android 应用"
A["VoiceWakeMode<br/>唤醒模式枚举"]
B["VoiceWakeManager<br/>唤醒监听器"]
C["WakeWords<br/>唤醒词工具"]
D["SecurePrefs<br/>安全偏好存储"]
E["VoiceWakeCommandExtractor<br/>命令提取器"]
F["TalkDirectiveParser<br/>指令解析器"]
G["TalkModeManager<br/>Talk 模式管理器"]
H["ElevenLabsStreamingTts<br/>流式 TTS"]
I["OnboardingFlow<br/>引导与权限"]
J["VoiceTabScreen<br/>语音标签页 UI"]
end
A --> D
C --> D
D --> B
D --> G
B --> E
G --> F
G --> H
I --> J
J --> G

核心组件

  • 语音唤醒模式:VoiceWakeMode 定义 off/foreground/always 三种模式,并提供从原始字符串解析的方法。
  • 唤醒词管理:WakeWords 提供解析、变更检测与清洗逻辑;SecurePrefs 负责持久化存储与默认值。
  • 唤醒监听:VoiceWakeManager 使用 Android SpeechRecognizer 进行持续监听,结合 VoiceWakeCommandExtractor 提取触发后的命令。
  • Talk 指令解析:TalkDirectiveParser 解析首行 JSON 指令,支持多键名别名映射,剥离后返回纯文本与未知键列表。
  • Talk 模式:TalkModeManager 实现录音、转写、对话、TTS 播放与中断控制,支持 ElevenLabs 流式 TTS 与系统 TTS 双通道。
  • 流式 TTS:ElevenLabsStreamingTts 通过 WebSocket 接收实时音频,AudioTrack/PCM 或 MediaPlayer 播放。

架构总览

Android 语音功能采用“手动触发 + 云端转写 + 本地/云端 TTS”的混合架构。用户在语音标签页点击开始录音,TalkModeManager 启动 SpeechRecognizer,转写为文本后发送到网关,等待最终回复并进行 TTS 播放。若具备 ElevenLabs 凭证则优先使用其流式 TTS,否则回退系统 TTS。

sequenceDiagram
participant UI as "语音标签页 UI"
participant TM as "TalkModeManager"
participant SR as "SpeechRecognizer"
participant GW as "网关会话"
participant EL as "ElevenLabs"
participant SYS as "系统TTS"
UI->>TM : 开始录音
TM->>SR : 启动识别(云转写)
SR-->>TM : 部分/最终结果
TM->>GW : chat.send(带会话键)
GW-->>TM : agent 流事件/最终事件
alt 有 ElevenLabs 凭证
TM->>EL : 流式合成(WebSocket)
EL-->>TM : 音频流
TM-->>UI : 播放音频
else 回退
TM->>SYS : 文本转语音
SYS-->>TM : 语音输出
TM-->>UI : 播放语音
end

详细组件分析

语音唤醒模式与唤醒词管理

  • VoiceWakeMode:定义三种模式,提供字符串到枚举的解析,默认值为前台模式。
  • WakeWords:限制最大数量与长度,支持逗号分隔解析、变更检测与清洗。
  • SecurePrefs:持久化存储唤醒词列表与模式,提供默认唤醒词集合;加载时进行 JSON 解码与清洗。
classDiagram
class VoiceWakeMode {
+Off
+Foreground
+Always
+fromRawValue(raw)
}
class WakeWords {
+maxWords : Int
+maxWordLength : Int
+parseCommaSeparated(input)
+parseIfChanged(input, current)
+sanitize(words, defaults)
}
class SecurePrefs {
+setWakeWords(words)
+setVoiceWakeMode(mode)
+loadWakeWords()
+loadVoiceWakeMode()
}
VoiceWakeMode <.. SecurePrefs : "使用"
WakeWords <.. SecurePrefs : "清洗/校验"

语音唤醒监听与命令提取

  • VoiceWakeManager:封装 SpeechRecognizer 生命周期,处理错误与重启;监听部分/最终结果,调用 VoiceWakeCommandExtractor 提取命令。
  • VoiceWakeCommandExtractor:基于触发词正则匹配,提取触发词之后的自然语言命令,过滤空值与标点。
flowchart TD
Start(["开始监听"]) --> Listen["SpeechRecognizer 启动"]
Listen --> OnResult{"收到结果?"}
OnResult --> |否| Restart["延迟重启"]
OnResult --> |是| Extract["提取命令"]
Extract --> Valid{"命令有效?"}
Valid --> |否| Restart
Valid --> |是| Dispatch["派发命令回调"]
Dispatch --> Restart

Talk 指令解析机制

  • TalkDirectiveParser:解析首行 JSON 指令,支持多键名别名(如 voice_id、speakerBoost 等),记录未知键;剥离指令后返回纯文本与未知键列表。
flowchart TD
In(["输入文本"]) --> Split["按行分割"]
Split --> FindHead["定位首个非空行"]
FindHead --> IsObj{"是否为 JSON 对象?"}
IsObj --> |否| ReturnPlain["返回纯文本与空未知键"]
IsObj --> |是| Parse["解析对象字段"]
Parse --> MapKeys["键名归一化映射"]
MapKeys --> BuildDirective["构建指令对象"]
BuildDirective --> HasDirective{"存在有效字段?"}
HasDirective --> |否| ReturnPlain
HasDirective --> |是| Strip["移除首行与空行"]
Strip --> CollectUnknown["收集未知键"]
CollectUnknown --> Out(["返回指令+文本+未知键"])

Talk 模式:录音、转写、TTS 播放

  • 录音与转写:TalkModeManager 使用 SpeechRecognizer,启用云转写与静默窗口策略,避免过早结束。
  • 对话与订阅:支持 chat.subscribe 订阅事件流,缓存最终文本,减少轮询。
  • TTS 播放:优先 ElevenLabs 流式 TTS(WebSocket),失败或不支持时回退到文件下载播放或系统 TTS;支持音频焦点与中断控制。
sequenceDiagram
participant TM as "TalkModeManager"
participant SR as "SpeechRecognizer"
participant GW as "网关"
participant ST as "ElevenLabsStreamingTts"
participant AT as "AudioTrack/MediaPlayer"
participant SYS as "系统TTS"
TM->>SR : startListening(云转写)
SR-->>TM : 部分/最终结果
TM->>GW : chat.send + subscribe
GW-->>TM : 流式/最终事件
alt ElevenLabs 可用
TM->>ST : start + sendText
ST-->>TM : 音频流
TM->>AT : 播放
else 回退
TM->>SYS : speak
SYS-->>TM : 语音
TM->>AT : 播放
end

依赖关系分析

  • 组件耦合
    • VoiceWakeManager 依赖 WakeWords 与 VoiceWakeCommandExtractor,受 SecurePrefs 中的模式与触发词影响。
    • TalkModeManager 依赖 GatewaySession、ElevenLabsStreamingTts 与系统 TTS,内部维护状态流与播放令牌。
  • 外部依赖
    • Android SpeechRecognizer(云转写)
    • ElevenLabs API(流式 TTS)
    • Android AudioManager/AudioTrack/MediaPlayer(音频播放)
graph LR
SW["SecurePrefs"] --> VWM["VoiceWakeManager"]
WW["WakeWords"] --> VWM
VWE["VoiceWakeCommandExtractor"] --> VWM
VWM --> CMD["命令回调"]
TM["TalkModeManager"] --> SR["SpeechRecognizer"]
TM --> GW["GatewaySession"]
TM --> ELS["ElevenLabsStreamingTts"]
TM --> SYS["系统TTS"]

性能考虑

  • 转写与静默策略
    • 使用云转写模型,配合静默窗口参数,提升自然停顿后的识别稳定性。
    • 在播放期间可选择“说话时打断”以避免录音拾取播放音频导致的设备特定问题。
  • TTS 播放路径
    • 优先 WebSocket 流式 PCM,降低延迟;失败时降级为 MP3 文件下载播放,提高兼容性。
    • AudioTrack 缓冲区大小与最小缓冲区计算,避免 OEM 设备(如 OxygenOS/OnePlus)的 AudioTrack 写入问题。
  • 状态与资源管理
    • 使用播放令牌与生成器确保并发播放不会互相干扰;及时释放 MediaPlayer/AudioTrack 与 SpeechRecognizer。
    • 缓存最终聊天文本,减少历史查询开销。

Android 应用

Android 应用位于 apps/android 目录,采用 Gradle 多模块结构,核心模块为 app;测试与基准位于 benchmark、test 等目录。应用通过 Jetpack Compose 构建 UI,使用 OkHttp WebSocket 连接网关,结合 CameraX 实现相机与视频录制能力,并通过自研 NodeRuntime 统一调度各类节点命令与状态。

graph TB
subgraph "应用层"
MA["MainActivity<br/>生命周期与权限绑定"]
VM["MainViewModel<br/>状态与行为入口"]
RT["NodeRuntime<br/>运行时与会话调度"]
end
subgraph "节点处理层"
CAM["CameraCaptureManager<br/>拍照/录屏"]
DEVH["DeviceHandler<br/>设备信息/健康/权限"]
INV["InvokeDispatcher<br/>命令分发"]
A2UI["A2UIHandler<br/>Canvas/A2UI"]
end
subgraph "网关通信层"
GS["GatewaySession<br/>WebSocket/RPC"]
DISC["GatewayDiscovery<br/>发现/信任提示"]
end
subgraph "系统与权限"
PERM["PermissionRequester<br/>动态权限请求"]
SVC["NodeForegroundService<br/>前台服务"]
NLS["DeviceNotificationListenerService<br/>通知监听"]
end
MA --> VM
VM --> RT
RT --> CAM
RT --> DEVH
RT --> INV
RT --> A2UI
RT --> GS
RT --> DISC
MA --> PERM
MA --> SVC
MA --> NLS

核心组件

  • MainActivity:负责应用启动、权限请求器绑定、前台服务启动时机、保持屏幕常亮等。
  • MainViewModel:桥接 UI 与 NodeRuntime,暴露状态流与操作方法。
  • NodeRuntime:统一运行时,管理网关会话、节点命令分发、Canvas/A2UI、语音、聊天、设备能力等。
  • PermissionRequester:封装动态权限请求流程,支持理由对话与设置页引导。
  • CameraCaptureManager:基于 CameraX 的拍照与录屏能力,支持参数化控制与权限校验。
  • DeviceHandler:提供设备状态、信息、权限与健康度查询。
  • GatewaySession:基于 OkHttp 的 WebSocket 会话,负责连接、RPC 请求、事件分发与重连。

架构总览

应用采用“运行时统一调度 + 节点处理器 + 网关会话”的分层架构。NodeRuntime 将 UI 层与底层系统能力解耦,通过 InvokeDispatcher 将命令路由到具体处理器(如 CameraHandler、LocationHandler、DeviceHandler 等),并通过 GatewaySession 与网关建立长连接,实现命令调用、事件推送与 Canvas/A2UI 交互。

sequenceDiagram
participant UI as "UI/MainActivity"
participant VM as "MainViewModel"
participant RT as "NodeRuntime"
participant GS as "GatewaySession"
participant DISP as "InvokeDispatcher"
participant H as "各处理器(如Camera/Device)"
UI->>VM : 用户操作/状态订阅
VM->>RT : 调用连接/断开/命令
RT->>GS : 建立/维护会话
RT->>DISP : 分发命令(command,params)
DISP->>H : 路由到对应处理器
H-->>DISP : 返回结果或错误
DISP-->>RT : 汇总结果
RT-->>GS : 发送 node.invoke.result 或 node.event
GS-->>UI : 推送事件/状态更新

详细组件分析

设备控制与命令执行

  • 运行时调度:NodeRuntime 初始化多个处理器(相机、位置、设备、通知、系统、照片、联系人、日历、运动、短信、A2UI、调试等),并通过 InvokeDispatcher 将命令路由至对应处理器。
  • 命令分发:InvokeDispatcher 在处理前检查前置条件(如前台状态、相机启用、位置模式、短信可用性、录音权限等),并根据处理器返回结果构造响应。
  • 网关会话:GatewaySession 负责 WebSocket 连接、RPC 请求、事件分发与重连策略,支持 TLS 参数与指纹校验,确保首次连接的信任提示与后续自动连接的安全性。
classDiagram
class NodeRuntime {
+gateways
+discoveryStatusText
+isConnected
+nodeConnected
+statusText
+camera
+location
+sms
+canvas
+connect(endpoint)
+disconnect()
+refreshGatewayConnection()
}
class InvokeDispatcher {
+handleInvoke(command,params)
}
class GatewaySession {
+connect(endpoint,token,password,options,tls)
+request(method,params,timeout)
+sendNodeEvent(event,payload)
+reconnect()
+disconnect()
}
NodeRuntime --> InvokeDispatcher : "分发命令"
NodeRuntime --> GatewaySession : "会话管理"

相机与屏幕录制

  • 权限与参数:支持 CAMERA、RECORD_AUDIO(可选)权限;参数包括 facing、quality、maxWidth、durationMs、deviceId、includeAudio 等。
  • 拍照:使用 CameraX ImageCapture,读取 EXIF 方向并旋转/缩放,压缩至 5MB 以内,返回 base64 JPEG。
  • 录屏:使用 VideoCapture + Recorder,最低质量以减小文件体积;需预热摄像头并提供空预览以激活编码器;支持带/不带音频录制。
  • 错误处理:超时与失败场景删除临时文件、释放资源并抛出明确异常。
flowchart TD
Start(["开始 clip/snap"]) --> CheckPerm["检查相机/麦克风权限"]
CheckPerm --> |缺失| RequestPerm["弹窗请求权限"]
RequestPerm --> |拒绝| ThrowErr["抛出权限不足错误"]
RequestPerm --> |允许| BindCam["绑定生命周期与相机选择器"]
BindCam --> Mode{"操作类型"}
Mode --> |snap| TakePhoto["拍照并读取EXIF方向"]
TakePhoto --> Rotate["按EXIF旋转位图"]
Rotate --> Scale["按maxWidth缩放"]
Scale --> Limit["压缩至5MB内(base64上限)"]
Limit --> ReturnSnap["返回JPEG payload"]
Mode --> |clip| Record["准备录制(可含音频)"]
Record --> Warm["预热摄像头1.5秒"]
Warm --> StartRec["开始录制并等待定时结束"]
StartRec --> StopRec["停止录制并等待完成事件"]
StopRec --> Finalize{"是否成功完成"}
Finalize --> |否| Clean["删除临时文件并抛错"]
Finalize --> |是| ReturnClip["返回文件路径/时长/音频标记"]

设备命令与权限管理

  • 设备状态/信息/健康:DeviceHandler 提供电池、存储、网络、内存、温度、压力等级等信息;权限状态汇总(相机、麦克风、位置、短信、通知监听、通知、相册、联系人、日历、运动等)。
  • 权限请求:PermissionRequester 支持多权限批量请求、理由对话、被拒后引导至系统设置页。
  • 后台限制:应用通过前台服务维持关键能力(数据同步、通知等),并在生命周期变化时调整行为(如前台切换时停止语音会话)。
classDiagram
class DeviceHandler {
+handleDeviceStatus(params)
+handleDeviceInfo(params)
+handleDevicePermissions(params)
+handleDeviceHealth(params)
}
class PermissionRequester {
+requestIfMissing(permissions,timeout)
-showRationaleDialog(permissions)
-showSettingsDialog(permissions)
}
class NodeRuntime {
+setForeground(value)
+setMicEnabled(value)
+setSpeakerEnabled(value)
}
DeviceHandler ..> GatewaySession : "返回JSON负载"
PermissionRequester --> MainActivity : "触发系统权限对话"
NodeRuntime --> PermissionRequester : "绑定相机/短信等权限"

网络通信与设备发现

  • WebSocket 会话:GatewaySession 建立 wss/ws 连接,发送 connect 挑战、签名设备信息、接收 canvasHostUrl 与会话默认值;支持 TLS 参数与指纹校验。
  • 自动连接与信任:NodeRuntime 在发现可信网关后自动连接,首次连接要求用户确认 TLS 指纹并持久化;手动连接模式要求已保存指纹。
  • 事件与 RPC:支持 node.event 推送与 node.invoke.request 调用,InvokeDispatcher 将请求路由到对应处理器并回传结果。
sequenceDiagram
participant RT as "NodeRuntime"
participant GS as "GatewaySession"
participant GW as "网关"
RT->>GS : connect(endpoint, token/password, options, tls)
GS->>GW : 建立WebSocket
GW-->>GS : challenge(nonce)
GS->>GS : 构造connect参数(签名/权限/能力)
GS->>GW : 发送connect
GW-->>GS : 返回server/canvasHostUrl/sessionDefaults
GS-->>RT : onConnected回调
RT->>GS : sendNodeEvent / request
GS-->>RT : 事件/响应

Android 权限体系与后台服务限制

  • 权限清单:应用声明 INTERNET、NETWORK_STATE、FOREGROUND_SERVICE、POST_NOTIFICATIONS、NEARBY_WIFI_DEVICES、LOCATION、CAMERA、RECORD_AUDIO、SMS、READ_MEDIA_*、READ_CONTACTS、READ_CALENDAR、ACTIVITY_RECOGNITION 等。
  • 动态权限:相机、录音、短信、通知监听等在运行时请求;PermissionRequester 提供理由对话与设置页引导。
  • 后台限制:应用通过前台服务维持数据同步;前台切换时停止语音会话以节省资源;最小化对电池与性能的影响。

依赖关系分析

  • 构建与工具链:根级 build.gradle.kts 引入 Android 插件、ktlint、Compose、Serialization 插件;app/build.gradle.kts 配置编译目标、签名、依赖与打包规则。
  • 第三方库:OkHttp、Material3、CameraX、dnsjava、BCProv、Commonmark、Kotlinx Serialization、Kotlinx Coroutines、Kotest/Robolectric 测试框架等。
  • 资源与图标:mipmap、values、xml 等资源目录用于主题、备份/数据提取规则、网络配置与文件提供者。
graph LR
A["根构建脚本"] --> B["应用模块构建脚本"]
B --> C["OkHttp/网络"]
B --> D["CameraX/相机"]
B --> E["Material3/Compose"]
B --> F["Kotlinx/序列化"]
B --> G["测试框架"]

性能考虑

  • 启动路径精简:MainActivity 在首帧后才启动前台服务,降低冷启动时间。
  • 拍摄与录制优化:拍照前旋转/缩放在主线程完成但耗时短;录屏使用最低质量与空预览激活编码器,缩短初始化时间。
  • 资源压缩:JPEG 压缩限制在 5MB 内,避免传输与解析开销过大。
  • 任务调度:使用 SupervisorJob 与 IO 线程池隔离网络与 I/O;状态流组合与去重减少 UI 重组。
  • 打包与混淆:开启资源压缩与 ProGuard 规则,排除无关文件与元数据。