# 安卓 APP 技术方案(Capacitor 打包) > 状态:需求已评审,方案已与需求方达成共识 > 关联代码:`desktop/`(Vue 3 前端)、`backend/`(Java 后端) > 适用范围:本文件描述的是**第一版**安卓 APP 的完整实现范围,做与不做均已明确界定,不包含"可选"项。 ## 1. 需求背景 Mao 客户端目前支持两种访问形态: - **桌面客户端**:Electron 壳(`desktop/electron/`),支持 LOCAL 模式(在用户本机执行终端、git、文件读写等工具)与 CLOUD 模式(工具在服务端执行); - **Web 访问**:同一套 Vue 3 前端通过 Nginx 部署(`/opt/mao/desktop/dist`),浏览器访问时自动降级为 CLOUD 模式。 现状痛点:移动场景(通勤、外出)下用户只能用手机浏览器访问 Web 端,体验差(无桌面图标、无独立应用、入口不便)。需求方希望产出一个**安卓 APP**,让用户像使用原生应用一样打开 Mao 客户端。 经评估:Electron 官方不支持移动端,无法直接复用 Electron 打包链路;但前端整体为纯 Web 技术栈,具备用 WebView 壳方案打包成安卓 APP 的条件。 ## 2. 需求描述 ### 2.1 目标 交付一个安卓 APK,内部人员安装后: 1. 以独立应用形态提供与 Web 端一致的**核心聊天闭环**体验; 2. 手机端**仅使用 CLOUD 模式**(工具在服务端执行),不依赖本机能力; 3. 复用现有后端(`https://mao.etarch.cn`)与现有前端代码,后端零改动。 ### 2.2 范围界定 #### 要做(第一版交付内容) | 模块 | 说明 | |---|---| | 登录/登出 | 沿用现有认证流程,token 存 localStorage(与 Web 端一致) | | 会话管理 | 会话列表、创建会话、切换会话、历史消息加载 | | Agent 选择 | 选择 Agent、切换模型 | | 聊天与流式输出 | 消息发送、WebSocket 流式接收(content_delta)、消息渲染(Markdown/代码高亮/图片) | | CLOUD 工具执行 | 工具在服务端执行,结果流式回流展示(running tools、文件变更面板等现有 UI) | | ask_user_questions | 服务端提问事件在 Web 端已有处理链路,安卓端沿用 | | Side Task 查看 | 并行子会话的进度与结果展示沿用现有 UI | | 本地技能列表展示 | 服务端技能目录展示(CLOUD 下可用部分) | | 版本号展示 | 设置页展示 APP 版本(versionName) | | APK 构建与签名 | 本机 Android SDK 构建,release keystore 签名,输出可分发的 APK | | **应用内更新(OTA)** | 启动时 + 设置页手动检查版本;支持强制更新与普通更新;APP 内下载并拉起系统安装器;支持忽略普通版本 | #### 不做(明确排除) | 模块 | 排除原因 | |---|---| | LOCAL 模式全部能力(终端、git 状态/提交、本地文件读写、本地技能同步、MCP 本地执行、`select-directory` 等) | 安卓沙盒无 node-pty / child_process / 文件系统直读;前端已有降级逻辑会自动禁用 LOCAL 入口 | | 工具审批弹窗(Electron 原生对话框链路) | CLOUD 模式工具在服务端执行,不产生 `tool_execute` 事件,无审批需求;`WAITING_APPROVAL` 仅 LOCAL 模式存在 | | 系统推送通知(Agent 任务完成等) | 不在第一版范围(OTA 的下载进度提示除外) | | 移动端布局重构/适配 | 第一版直接加载现有 UI,靠 viewport 与系统 WebView 适配 | | PWA / Add to Home Screen 优化 | 已有原生壳,不需要 PWA | | 上架应用商店(Google Play / 国内商店) | 仅内部分发 | | 后端代码改动 | CORS 已 `allowedOriginPatterns("*")`,无 UA/设备限制,无需改动 | | admin 管理后台改造成安卓 APP | 只做客户端(desktop 前端),不做管理后台 | ## 3. 技术选型 ### 3.1 方案对比 | 方案 | 结论 | |---|---| | Electron 打包安卓 | **不可行**。Electron 官方无移动端支持,无法产出 APK | | React Native / Flutter 重写 | **不选**。需重写全部 UI,成本极高,且项目前端为 Vue 3 技术栈,无法复用 | | **Capacitor(WebView 壳)** | **采用**。复用现有 Vue 前端零重构,官方支持 Vite 集成与安卓打包,是"Web 技术栈 → 安卓 APK"的标准路径 | | 直接分发 PWA | 不选。无独立应用形态、无签名分发、体验弱于 APK | ### 3.2 关键选型明细 | 项 | 选型 | 理由 | |---|---|---| | 壳框架 | Capacitor(@capacitor/core + @capacitor/cli + @capacitor/android) | 官方支持安卓,WebView 壳,最小侵入 | | 安卓工程 | Capacitor 生成的 `android/` 原生工程(Gradle 构建) | 自带 gradle wrapper,无需全局 Gradle | | 构建环境 | 本机(当前服务器)安装 Android SDK 命令行工具 | 与现有 `deploy_local.sh` 部署风格一致,产物可控 | | 签名 | 新建 release keystore(keytool 生成),存服务器安全目录,**不入 git** | 保证后续版本可覆盖升级 | | token 存储 | localStorage(前端 `auth-storage.ts` 非 Electron 分支自动生效) | 与 Web 端行为一致,零代码改动 | | 包名 | `cn.etarch.mao.app` | 独立于桌面端 `cn.etarch.mao.desktop` | | minSdk / targetSdk | minSdk 24(Android 7.0)/ targetSdk 34 | 覆盖主流设备,Capacitor 7 默认要求 | | 版本号 | versionName 沿用桌面端版本节奏(0.0.x),versionCode 每次递增 | 支持同签名覆盖安装 | | WebSocket | 沿用 `useStreamWS` 自动转换逻辑,`wss://mao.etarch.cn/api/ws/stream` | 生产环境自动生效,零改动 | | OTA 数据源 | 静态 `android-latest.json` 清单,与 APK 一同发布到 `https://mao.etarch.cn/uploads/releases/`(复用现有 Nginx 静态服务与 electron 发布目录) | 后端/admin 零改动 | | OTA 下载安装 | 原生 DownloadManager 下载 APK + FileProvider 拉起系统安装器(自研小插件,不依赖社区库) | APP 内完成,不跳浏览器 | | OTA 策略 | `minVersionCode` 强制更新 / 普通更新;普通更新支持"忽略此版本"(localStorage 记忆) | 覆盖协议变更强升与日常迭代 | | OTA 更新说明 | 根 `CHANGELOG.md` 的 `### 安卓原生`(`apk-ota-text`) | 原生壳发版时更新 | ### 3.3 现状兼容性(代码探索结论) 以下事实决定了"前端零重构"的可行性,均已从当前代码验证: 1. **electronAPI 全量特性检测**:所有 IPC 调用(`auth-storage.ts`、`useStreamWS.ts`、`useChat.ts`、`useTerminal.ts`、`localSkills.ts` 等)均以 `!!window.electronAPI` 守卫。Capacitor WebView 不注入 electronAPI,前端自动走 browser 路径。 2. **LOCAL 模式自动禁用**:`ChatInput.vue` 的 LOCAL radio `:disabled="!isElectronClient"`;`useChat.ts` 对 `executionMode === 'LOCAL' && !isElectron` 直接阻断。安卓端用户无法创建 LOCAL 会话。 3. **认证存储自动回退**:`auth-storage.ts` 非 Electron 环境使用 localStorage,与 Web 端完全一致。 4. **WS 客户端标识**:非 Electron 自动传 `client=browser`,服务端按 browser 客户端处理(CLOUD 工具服务端执行,无 `tool_execute` 下发)。 5. **后端无阻碍**:CORS 全放行;生产地址为 HTTPS,无明文流量问题。 ### 3.4 版本与兼容矩阵 | 依赖 | 版本要求 | 当前环境 | |---|---|---| | Node.js | ≥ 20 | v24.3.0 ✓ | | JDK | 17(AGP 8.x 要求) | 17.0.19 ✓ | | Android SDK | platform 34 + build-tools 34 | 未安装,需安装 | | Gradle | 由 Capacitor 生成的 wrapper 提供 | 无需全局安装 | | Capacitor | 7.x | 待安装 | ## 4. 实现步骤 ### 4.1 安装 Android SDK(一次性) 1. 下载 Android commandline-tools 到 `/opt/android-sdk/cmdline-tools`; 2. 用 `sdkmanager` 安装:`platform-tools`、`platforms;android-34`、`build-tools;34.0.0`; 3. 配置环境变量 `ANDROID_HOME=/opt/android-sdk`,写入 shell 配置; 4. 接受 SDK licenses(`yes | sdkmanager --licenses`)。 ### 4.2 创建 android/ 壳工程 1. 新建 `android/` 目录(与 backend/admin/desktop 平级),初始化 npm 项目; 2. 安装 `@capacitor/core`、`@capacitor/cli`、`@capacitor/android`; 3. `npx cap init`:appName=`Mao`,appId=`cn.etarch.mao.app`; 4. 配置 `capacitor.config.json`:`webDir: 'web-stub'`,`server.url: 'https://mao.etarch.cn'`(远程加载,与 Electron 一致); 5. `npx cap add android` 生成安卓原生工程(自带 gradle wrapper)。 ### 4.3 前端加载方式 1. **生产环境**:Capacitor `server.url` 指向 `https://mao.etarch.cn`,WebView 加载与 Web/Electron 相同的远程 SPA; 2. `android/web-stub/` 仅作 Capacitor 占位,**不打包** `desktop/dist`; 3. 前端更新:部署 Nginx 上的 `desktop/dist`,用户顶栏刷新或 `version.json` 轮询提示(`useVersionCheck.startPolling`); 4. `npx cap sync android` 同步 `web-stub` 与配置到安卓工程(发原生壳时执行,非每次前端发版必需); 5. WebView 采用标准 HTTP 缓存(`LOAD_DEFAULT`):入口文档靠服务端 `Cache-Control: no-cache` 保证新鲜(见 `docs/guides/single-domain-nginx-migration.md` 第 7 节),hashed 资源长缓存;首屏完成以原生轮询验证 Vue 挂载(`window.__MAO_APP_MOUNTED` / `#app` 内容)为准,8s 未完成显示重试(重试绕过缓存重拉入口文档);回前台存活探测用应用级标记判定,页面空白也会自动 reload。 ### 4.4 release 签名 1. `keytool -genkeypair` 生成 `mao-release.keystore`,alias 与密码单独记录; 2. keystore 存放于服务器安全目录(如 `/opt/mao/keystore/`),**禁止入 git**(加入 `.gitignore`); 3. 在 `android/` 工程 `gradle.properties` 中通过环境变量注入签名配置(keystore 路径、密码不写入代码库)。 ### 4.5 构建 APK(仅原生壳变更时) 1. 一键脚本 `android/build-apk.sh`:`cap sync` → `gradlew assembleRelease` → 发布 APK + `android-latest.json`; 2. 产物 `android/app/build/outputs/apk/release/app-release.apk`; 3. 复制到 `/opt/mao-data/uploads/releases/mao-android--.apk`。 ### 4.6 验证与分发 1. 安装测试(真机/模拟器):登录、创建会话、发送消息、流式输出、CLOUD 工具执行、ask_user_questions、Side Task; 2. 验证 LOCAL 入口不可用(radio 禁用)、token 登录态持久; 3. 通过内部分发渠道(网盘/微信)下发 APK。 ### 4.7 应用内更新(OTA)实现 1. 自研 Capacitor 插件 `AppUpdate`(原生侧): - `downloadAndInstall(url)`:DownloadManager 下载 APK,完成后通过 FileProvider 拉起系统安装器; - 权限:`REQUEST_INSTALL_PACKAGES`(Android 8+ 未知来源)、写入 Downloads 目录; - 下载进度通过插件事件回传前端展示。 2. 前端更新(与 Web/Electron 共用): - `version.json` 轮询(`useVersionCheck.startPolling`),有更新时顶栏刷新; - 启动时与手动点击刷新按钮均会检查。 3. 原生壳 OTA(仅 `MainActivity` / 插件 / Capacitor 配置变更时发 APK): - `fetch(android-latest.json)` 比对 `versionCode`; - `currentVersionCode < minVersionCode` → 强制更新;否则普通更新(可忽略); - `AppUpdate` 插件下载安装 APK。 4. 发布侧(`android/build-apk.sh`): - changelog 取 `CHANGELOG.md` 的 `### 安卓原生`(`apk-ota-text`); ## 5. 落地清单 | # | 任务 | 产出 | 状态 | |---|---|---|---| | 1 | 安装 Android SDK + 环境变量 + licenses | SDK 就绪 | ✅ 完成 | | 2 | 创建 `android/` Capacitor 壳工程(webDir 指向 desktop/dist) | android/ 工程 | ✅ 完成 | | 3 | 安卓构建脚本(build-apk.sh:vite --base=./ + cap sync + assembleRelease) | 脚本 | ✅ 完成 | | 4 | 生成 release keystore 并配置签名(不入 git) | keystore + 签名配置 | ✅ 完成 | | 5 | 构建 release APK 并输出到分发目录 | APK | ✅ 完成 | | 6 | OTA 发布:build-apk.sh 自动发布 APK + android-latest.json 到 releases 目录 | 发布脚本 | ✅ 完成 | | 7 | OTA 检查:前端启动/手动检查 + 强制/普通弹窗 + 忽略版本 | 前端模块 | ✅ 完成 | | 8 | OTA 安装:原生 AppUpdate 插件(下载/进度/拉起安装器) | 插件 | ✅ 完成 | | 9 | 真机验收(登录/聊天/流式/CLOUD 工具/提问/Side Task/登录态持久/OTA 全流程) | 验收报告 | ⏳ 待用户真机验证 | | 10 | 文档:README 补充安卓构建与 OTA 说明 | README 更新 | ✅ 完成 | ## 6. 风险与注意事项 | 风险 | 影响 | 应对 | |---|---|---| | WebView 的 localStorage 持久性依赖系统行为 | 用户清数据/系统清理后需重新登录 | 可接受(与 Web 端一致);后续如需要可换 Capacitor Preferences | | 老旧安卓机型 WebView 内核过旧 | 渲染异常 | minSdk 24 + 要求系统 WebView 可升级,覆盖主流机型即可 | | Vite `base:'/'` 与远程加载 | 无(已改为 `server.url` 远程 SPA) | 与 Web/Electron 同源 | | 无网络时无法打开 | 首屏依赖线上前端 | 可接受;与 Electron 远程模式一致 | | 桌面布局在手机上的可用性 | 操作体验一般 | 已确认第一版接受,不做布局适配 | | keystore 丢失 | 无法覆盖升级 | keystore 与密码在服务器安全目录备份,避免丢失 | | OTA 下载中断/失败 | 用户停留在旧版 | 下载失败提示重试;强制更新场景不可跳过 | | 安卓 8+ "未知来源安装" 权限被拒 | 无法安装新包 | 拉系统安装器前引导开启 `REQUEST_INSTALL_PACKAGES` 权限 | | 清单 JSON 与 APK 版本不一致 | 用户下载到错误版本 | 构建脚本原子发布(先生成校验,再复制 APK 与 JSON),发布后人工抽查 | | 开发阶段禁止自行重启后端服务 | 影响联调 | 遵循仓库 AGENTS.md 禁令,重启动作由用户执行 | ## 7. 明确不做事项(防止范围蔓延) 1. 不做 LOCAL 模式(终端/git/本地文件/本地技能/MCP 本地); 2. 不做工具审批弹窗链路(CLOUD 模式无此流程); 3. OTA 不做:后台定时检查、系统通知提醒更新(仅启动/手动检查 + 下载进度提示); 4. 不做系统推送通知; 5. 不做移动端布局重构与触控优化; 6. 不做 PWA; 7. 不上架任何应用商店; 8. 不改动后端代码与 admin 管理后台; 9. 不改动现有 desktop 的 Web/Nginx 构建行为(`vite.config.ts` 保持 `base:'/'`)。