乐于分享
好东西不私藏

OpenClaw Android APP 启动全流程大揭秘

OpenClaw Android APP 启动全流程大揭秘

当你手指戳向那个图标的瞬间,你可能以为只是打开了一个 APP。

错!大错特错!

你其实是按下了一个复杂机器的启动按钮,就像推开了迪士尼乐园的大门——接下来,一场精心编排的表演开始了。


第一幕:Application 的”晨间准备”

NodeApp:我是大管家

classNodeApp : Application() {
val prefs: SecurePrefs by lazy { SecurePrefs(this) }
}

NodeApp 这位老哥,相当于整个 APP 的”大内总管”。它干的第一件事就是掏出个小本本(SecurePrefs),开始记录各种配置信息。

这个小本本可不一般,它是加密版的!比你的日记本还安全,专门存放各种敏感信息:网关 Token、密码、设备 ID……

💡 冷知识:这货用的是 AES256 加密,比你微信聊天记录保护得还好。

NodeApp 还会在 Debug 模式下开启”严格模式”,像个班主任一样盯着:”谁敢在主线程搞 IO?站出来!”


第二幕:MainActivity 的”化妆时间”

MainActivity:我要见人了

overridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, false)
    permissionRequester = PermissionRequester(this)

    setContent {
        OpenClawTheme {
            Surface(modifier = Modifier) {
                RootScreen(viewModel = viewModel)
            }
        }
    }
}

MainActivity 这位女主角登场了,她开始精心打扮:

  1. 去掉系统窗口装饰
     – “我要全屏美艳!”
  2. 准备权限请求器
     – “等会儿要权限的,先准备好”
  3. 设置 Compose UI
     – “穿上 Jetpack Compose 这件名牌衣服”

然后她创建了一个重要的角色——MainViewModel


第三幕:MainViewModel 的”灵魂拷问”

MainViewModel:你是谁?你去哪?

init {
if (prefs.onboardingCompleted.value) {
        ensureRuntime()  // 老用户,直接干活
    }
}

MainViewModel 一上任就开始了灵魂拷问:

“兄弟,第一次来吗?”

这就引出了整个 APP 最重要的分叉路口——


第四幕:人生的岔路口

RootScreen:新人老人,区别对待

val onboardingCompleted by viewModel.onboardingCompleted.collectAsState()

if (!onboardingCompleted) {
    OnboardingFlow(...)        // 新手村
else {
    PostOnboardingTabs(...)    // 老江湖
}

这一刻,RootScreen 像个火车站检票员:

  • 第一次来的
     → 左边,引导流程走起!
  • 老司机
     → 右边,主界面随便玩!

第五幕:新手村的”四道关卡”

OnboardingFlow:闯关开始

如果你是个萌新,恭喜你,要经历四个步骤的”洗礼”:

Step 1:Welcome(画大饼)

WelcomeStep() {
    FeatureCard("连接到你的网关")
    FeatureCard("选择你的权限")
    FeatureCard("聊天、语音、屏幕控制")
    FeatureCard("验证连接")
}

这一步就是在给你画大饼

  • “我们能连网关!”
  • “我们能管权限!”
  • “我们什么都能干!”

像不像健身房销售给你展示完美身材的照片?

Step 2:Gateway(填表格)

when (gatewayInputMode) {
    GatewayInputMode.SetupCode -> "扫码吧亲"
    GatewayInputMode.Manual -> "手动输入地址端口"
}

这里让你配置网关地址,有两种方式:

  1. 扫描二维码
     – “滴~ 学生卡”(啊不对,是网关码)
  2. 手动输入
     – 适合极客,一行代码搞定那种

还要填 Token、密码、TLS 开关……

😂 吐槽:这架势,比你办银行卡填的表还多。

Step 3:Permissions(要权限)

PermissionToggle.entries.forEach { 
    CheckBox("相机""位置""麦克风""短信""通讯录"...)
}

到了最刺激的环节——要权限

APP 像个小心翼翼的销售:

  • “亲,能给个相机权限吗?”
  • “那……位置呢?”
  • “麦克风也行啊……”
  • “通讯录?就看看,不干嘛……”

用户内心 OS:你要不要干脆把我银行卡密码也要了?

Step 4:FinalCheck(验货)

if (isConnected) {
    Button("Finish")  // 成功了!
else {
    Button("Connect"// 再试试
}

最后一步,现场表演”胸口碎大石”——当场连接网关给你看!

成功了?恭喜毕业! 失败了?再来一次!

期间还可能弹出 TLS 证书确认框:

“这个网关的指纹是 XXX,你信不信?”

你:“我信我信,赶紧让我进去!”


第六幕:正式上岗——五大护法

PostOnboardingTabs:主界面的五虎上将

通过新手村后,你终于来到了主界面。这里有五个 Tab,各司其职:

Connect(连接护法)

HomeTab.Connect -> "找网关、连网关、显示状态"

负责找网关、连网关,显示当前连接状态。

状态包括:

  • Connected – 一切正常
  • Connecting – 正在努力
  • Warning – 有点小问题
  • Error – 出大事了
  • Offline – 躺平了

像极了你的 WiFi 信号指示器。

Chat(聊天护法)

HomeTab.Chat -> "发消息、收消息、调用工具"

这就是个AI 聊天界面,但不止能聊天:

  • 发文字
  • 发图片
  • AI 思考时还能看到”thinking level”
  • 需要调用工具时会弹窗问你

示例对话

你:帮我查下天气
AI:[思考中...]
AI:需要调用天气 API,同意吗?
你:准了
AI:今天晴天,25 度

Voice(语音护法)

HomeTab.Voice -> "听你说、对你喊"

这是语音交互模式

  • 实时听写你说的话
  • 显示声音波形
  • 自动识别唤醒词(”openclaw”、”claude”)
  • 用 TTS 对你说话

开启后,你的手机就变成了《钢铁侠》里的贾维斯。

Screen(屏幕护法)

HomeTab.Screen -> "WebView 渲染远程 UI"

这是最黑科技的功能——A2UI 画布

简单说,就是能在 APP 里显示一个远程控制的网页,你在手机上点点点,远程服务器就知道你在干嘛。

类似于 TeamViewer,但更轻量级。

Settings(设置护法)

HomeTab.Settings -> "改名字、开关功能、调参数"

这里是控制面板

  • 改设备名字
  • 开关相机/位置
  • 防止屏幕休眠
  • 配置唤醒词
  • 开关麦克风和扬声器

第七幕:后台的”秘密行动”

NodeRuntime:真正的打工人

当你沉浸在 UI 中时,NodeRuntime 这位幕后大佬已经在后台忙活了:

classNodeRuntime {
val canvas = CanvasController()      // 管屏幕的
val camera = CameraCaptureManager()  // 管相机的
val location = LocationCaptureManager() // 管位置的
val sms = SmsManager()               // 管短信的

// 还有十几个 Handler...
}

它初始化了一堆Handler

  • CameraHandler
     – 拍照录像
  • LocationHandler
     – 定位追踪
  • SmsHandler
     – 收发短信
  • ContactsHandler
     – 读联系人
  • CalendarHandler
     – 访问日历
  • MotionHandler
     – 检测运动
  • ……

感觉像是在组建复仇者联盟。

GatewayDiscovery:星探

privateval discovery = GatewayDiscovery(...)
val gateways: StateFlow<List<GatewayEndpoint>> = discovery.gateways

这家伙像个星探,一直在后台扫描:

“发现网关!IP: 192.168.1.100” “又发现一个!IP: 10.0.2.15”

然后把这些信息告诉 UI,让你选择连接哪个。

ConnectionManager:外交官

privateval connectionManager = ConnectionManager(...)

这位是外交官,负责跟网关建立外交关系:

  1. 发送认证信息
  2. 协商 TLS 加密
  3. 建立 WebSocket 连接
  4. 保持心跳

比你谈恋爱还用心。


第八幕:前台服务——”我不睡了”

NodeForegroundService:永动机

NodeForegroundService.start(this)

一旦你完成引导,APP 就会启动一个前台服务,并在通知栏显示:

“OpenClaw 正在运行”

这意味着啥?它不睡了!

即使你切换到其他 APP,它依然在后台:

  • 监听网关消息
  • 准备接收语音指令
  • 保持连接不断线

像个 24 小时待命的私人助理。


第九幕:数据流的”八卦图”

状态同步:牵一发而动全身

整个 APP 的数据流是这样的:

用户操作 → ViewModel → NodeRuntime → 网关
   ↓                           ↓
UI 更新 ← StateFlow ← 状态变化

举个例子:

  1. 你点了”连接”按钮
  2. ViewModel 调用 connect()
  3. NodeRuntime 开始连接
  4. 连接成功后更新 _isConnected.value = true
  5. StateFlow 通知所有订阅者
  6. UI 自动刷新,显示”已连接”

这就是 Kotlin Flow 的威力——全自动流水线。


总结:你以为的 vs 实际上的

你以为的启动:

点击图标 → 出现界面 → 完事

实际上的启动:

Application 创建
  ↓
读取加密配置
  ↓
创建 ViewModel
  ↓
判断是否首次
  ↓
[首次] → 引导流程 4 步
  ↓
[非首次] → 主界面 5 个 Tab
  ↓
初始化运行时
  ↓
创建 10+ 个 Handler
  ↓
启动网关发现
  ↓
建立 WebSocket 连接
  ↓
启动前台服务
  ↓
Canvas A2UI 水合
  ↓
终于可以跟你打招呼了

整个过程,比你早上起床刷牙洗脸吃早餐还复杂。


彩蛋:那些代码里的小心思

1. 懒加载的智慧

val prefs: SecurePrefs by lazy { SecurePrefs(this) }

lazy 关键字:能拖则拖,不到用时不创建。

像极了 deadline 前的你。

2. 状态管理的艺术

val isConnected: StateFlow<Boolean>
val statusText: StateFlow<String>

全是 StateFlow,UI 想不更新都难。

这就是响应式编程的魅力。

3. 协程的力量

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
// 自动跟随生命周期
    }
}

协程 + 生命周期感知,内存泄漏?不存在的。

比你对象还贴心。