让 AI 真正动手:Swees — 手机上的 AI Agent 架构全解析
本文深入剖析开源项目 Swees 的技术架构,探讨如何在无 root 的 Android 手机上构建一个真正能「动手」的 AI Agent。
一、引言:AI 只能说,不能做?
你大概有过这样的体验:在手机上问 AI「帮我写个 Python 脚本处理 CSV」,AI 给你一段漂亮的代码,然后呢?你得手动复制、打开电脑、装环境、跑代码、遇到报错再回去问 AI……
手机AI 停留在「说」的阶段,无法「做」。
桌面端的 Claude Code、Cursor、Devin 已经给出了答案——让 AI 直接操作终端。但在移动端,这个领域几乎是空白的。原因很简单:手机上没有可用的 Linux 环境,没有 root 权限,没有成熟的终端模拟方案。
Swees 就是为了填这个空白。
它是一个运行在 Android 手机上的开源 AI Agent 应用。通过 PRoot 技术在手机内嵌完整的 Ubuntu 24.04 环境,AI 可以直接执行终端命令、编辑代码、管理文件——无需 root,无需电脑,一切在手机上完成。
二、项目概览
一句话定位:Swees 是一个 Android 原生 AI Agent 应用,内置 Ubuntu 终端,让 AI 从「对话」走向「行动」。
核心卖点:
| 特性 | 说明 |
|---|---|
| 真正的命令执行 | AI 自主调用 Function Calling,在真实 Ubuntu 环境中执行 shell 命令 |
| 无需 root | 基于 PRoot 技术,普通 Android 手机即可运行 |
| 多智能体协作 | 多个 Agent 各自独立配置,通过 IPC 跨话题通信 |
| 跨对话记忆 | SQLite + FTS4 全文搜索,AI 记住你说过的一切 |
| 零框架前端 | 纯 HTML/CSS/JS,~12K 行代码实现完整 SPA |

AI 编写五子棋 APP

手机上编译

安装,全程在手机上完成
(甚至gif都是AI做的)
三、技术架构总览
Swees 采用经典的三层架构,每层职责清晰、边界明确:
┌──────────────────────────────────────────────────┐
│ WebView (HTML / CSS / Vanilla JS) │ 前端 UI · ~12K 行
│ ↓ SweesBridge │
├──────────────────────────────────────────────────┤
│ Java Bridge Layer │ JS ↔ Java 桥接
│ ┌───────────┬───────────┬────────────────────┐ │
│ │ AI 引擎 │ PRoot │ Storage │ │
│ │ SSE 流式 │ Ubuntu │ SQLite │ │
│ │ OpenAI │ Session │ 13 张表 │ │
│ │ 兼容协议 │ Pool │ + FTS4 全文索引 │ │
│ └───────────┴───────────┴────────────────────┘ │
│ Native Layer (C / .so) │ linkhook 等
├──────────────────────────────────────────────────┤
│ Android OS (arm64) │
└──────────────────────────────────────────────────┘
各层职责:
- 前端 UI 层:WebView 承载的单页应用,纯 HTML + CSS + 原生 JavaScript,无任何前端框架。负责聊天界面渲染、Markdown 解析、手势交互、Git 面板等全部 UI 逻辑。
- Java 桥接层:核心是
SweesBridge,暴露 71 个@JavascriptInterface方法,是 JS 与 Java 之间唯一的通信通道。AI 引擎、PRoot 终端、SQLite 存储三大子系统在此交汇。 - Native 层:C 语言编写的
linkhook.c,编译为.so动态库,解决 Android 14 SELinux 对硬链接的限制问题。
数据流的关键路径是:用户输入 → JS 调用 Bridge → Java 发起 HTTP 请求 → SSE 流式回调 → JS 实时渲染。当 AI 决定执行命令时,Java 层通过 PRoot 会话池执行命令,结果回传给 AI 进行下一轮推理。
四、关键技术深度解析
4.1 PRoot:无 Root 运行 Ubuntu 的原理与 Android 14 兼容
为什么是 PRoot?
在 Android 上运行 Linux 环境通常有三条路径:
| 方案 | 需要 Root | 原理 | 适合 Swees? |
|---|---|---|---|
| Termux | 否 | 自带包管理器,但非标准 Linux 目录结构 | ❌ 目录结构不兼容 apt/dpkg |
| Docker | 是 | 容器化 | ❌ 需要 root |
| PRoot | 否 | 通过 ptrace 拦截系统调用,模拟 chroot + bind mount |
✅ |
PRoot 的核心原理是利用 Linux 的 ptrace() 系统调用拦截目标程序的所有系统调用。当程序尝试访问文件路径时,PRoot 拦截并重写路径——比如把 /usr/bin/python3 重定向到 /data/data/com.swees/files/rootfs/usr/bin/python3。这样,对程序而言,它就运行在一个「真实」的 Ubuntu 根文件系统中。
Swees 在首次使用时自动下载约 29MB 的 Ubuntu 24.04 arm64 rootfs,解压后在 PRoot 中启动完整的 bash 环境,预装 git、python3、sqlite3 等开发工具。
Android 14 兼容:linkhook 的故事
Android 14 收紧了 untrusted_app SELinux 域的安全策略,禁止普通应用调用 link() / linkat() 创建硬链接(返回 EACCES)。这直接导致 PRoot 内的 dpkg 在执行 apt install 时卡死——因为 dpkg 依赖硬链接来做 status 文件的原子备份。
Swees 的解决方案是一段精巧的 C 代码——linkhook.c:
/* 拦截 link():先尝试真实硬链接,失败则降级 */
int link(const char *oldpath, const char *newpath) {
if (real_link(oldpath, newpath) == 0) return 0;
if (errno == EACCES || errno == EPERM) {
return link_fallback(oldpath, newpath); // 降级为 copy → symlink
}
return -1;
}
设计要点:
- LD_PRELOAD 拦截:编译为
.so后通过LD_PRELOAD注入,对 PRoot 内所有程序透明——dpkg、apt 完全无感知。 - 三级降级策略:先尝试真实
link()→ 失败则copy_file()(产生独立副本)→ copy 也失败则symlink()兜底。 - 零依赖:不使用
dlsym(RTLD_NEXT)(避免依赖libdl),而是直接通过syscall()调用原始系统调用,库本身只依赖 libc。
这段代码不到 100 行,却解决了 Android 14 上 PRoot 生态最头疼的兼容性问题。
4.2 SSE 流式输出:从 SseParser 到前端实时渲染
AI 对话的体验好坏,很大程度上取决于流式输出的流畅度。Swees 实现了一套完整的 SSE(Server-Sent Events)解析与渲染链路。
Java 端:SseParser
SseParser 是整个流式输出的核心,负责解析 OpenAI 兼容协议的 SSE 流。它的设计有几个值得注意的工程细节:
public static ParseResult parse(InputStream is,
DeltaConsumer onDelta,
DeltaConsumer onReasoning,
ToolCallDeltaConsumer onToolCallDelta) throws Exception {
// ...
while ((line = r.readLine()) != null) {
if (!line.startsWith("data:")) continue;
String data = line.substring(5).trim();
if (data.equals("[DONE]")) break;
JSONObject chunk = new JSONObject(data);
JSONObject delta = choices.getJSONObject(0).optJSONObject("delta");
if (delta != null) {
// 1. 思维链增量(reasoning_content)
String reasoning = delta.isNull("reasoning_content") ? ""
: delta.optString("reasoning_content", "");
if (!reasoning.isEmpty()) onReasoning.onDelta(reasoning);
// 2. 正文增量(content)
String content = delta.isNull("content") ? ""
: delta.optString("content", "");
if (!content.isEmpty()) { full.append(content); onDelta.onDelta(content); }
// 3. tool_calls 流式分片(按 index 累积拼接)
// ...
}
}
}
关键设计决策:
① 三路并行回调。SseParser 同时支持三种增量回调:正文 onDelta、思维链 onReasoning、工具调用 onToolCallDelta。这意味着用户在等待 AI 回复时,不仅能看到正文逐字输出,还能看到 AI 的「思考过程」和「正在准备执行的命令卡片」实时渲染。
② org.json 的 null 陷阱。代码中反复出现 delta.isNull("content") ? "" : delta.optString(...) 这种模式。这是因为 org.json 的 optString() 对 JSON null 值会返回字符串 "null" 而非空字符串——这是一个经典的 Android 开发坑。Swees 在每个字段解析处都做了显式判空。
③ tool_calls 流式分片累积。OpenAI 协议中,tool_calls 的 arguments 字段是分片传输的——首个 chunk 携带 id 和 name,后续 chunk 只携带 arguments 的片段。SseParser 使用 LinkedHashMap<Integer, ToolCall> 按 index 累积拼接:
if (existing == null) {
// 首个分片:建条目
toolCallMap.put(idx, new ChatRequest.ToolCall(id, name, argsPart));
} else {
// 后续分片:拼 arguments
String mergedArgs = existing.arguments + argsPart;
toolCallMap.put(idx, new ChatRequest.ToolCall(
mergedId, mergedName, mergedArgs));
}
④ 容错设计。单行 SSE 解析失败不会中断整个流——catch (JSONException) 后跳过继续,但通过 Log.e 让问题可见。这在网络不稳定或服务商返回格式有微小偏差时至关重要。
前端:实时渲染链路
Java 解析出的增量通过 SweesBridge 回调到 JS 层,前端使用增量追加 + marked.js 实时 Markdown 渲染。整个链路是:
HTTP InputStream → SseParser.parse() → onDelta 回调
→ EventPusher.pushEvent("stream_delta", json)
→ WebView.evaluateJavascript("window.onStreamDelta(...)")
→ DOM 增量更新 + marked.js 渲染
4.3 Function Calling 安全设计:命令危险分级 + 三级权限 + 会话隔离
让 AI 执行命令,安全是第一要务。Swees 构建了一套多层安全体系。
命令危险分级(DangerLevel)
DangerLevel 是一个纯函数式的命令风险评估器,将每条命令分为 LOW(自动执行)和 HIGH(需用户确认)两级:
public enum Level {
LOW, // 自动执行
HIGH // 需用户确认
}
public static Level assess(String command) {
String cmd = command.trim();
if (matches(cmd,
"rm ", "rmdir ", "dd if=", "mkfs", "shutdown", "reboot",
"kill ", "sudo ", "chmod ", "chown ",
"curl ", "wget ", "git push", "git reset", "git rebase",
"drop table", "delete from", "mv ", "cp "))
return Level.HIGH;
// SQL UPDATE 需同时包含 update 和 set,避免误伤 apt update
String lower = cmd.toLowerCase();
if (lower.contains("update ") && lower.contains(" set "))
return Level.HIGH;
return Level.LOW;
}
设计亮点:
- 覆盖面广:涵盖文件删除、系统操作、权限变更、网络下载、Git 危险操作、数据库写操作等 7 大类高危命令。
- 精准误伤控制:比如 SQL
UPDATE的判定要求同时包含update和set,避免把apt update、pip update误判为高危。 - Git 操作细分:
git push、git reset、git rebase等列为 HIGH,但git status、git log、git diff等只读操作为 LOW,自动放行。
三级权限模式
每个 Agent 有独立的 trust_level 字段,默认为 RESTRICTED:
| 级别 | 行为 |
|---|---|
RESTRICTED |
所有 HIGH 级命令必须用户确认 |
TRUSTED |
放宽部分限制,AI 有更大自主权 |
FULL |
完全自主,无需确认(危险,谨慎使用) |
会话隔离
每个 Agent 拥有独立的 PRoot 会话,通过 ProotSessionPool 管理:
public ProotSession getOrCreate(String agentId, String workDir) throws Exception {
ProotSession session = sessions.get(agentId);
if (session != null && session.isAlive()) {
lastActivity.put(agentId, System.currentTimeMillis());
return session; // 复用已有会话
}
session = new ProotSession(workDir);
session.start(prootPath, rootfsPath, nativeLibDir, tmpDir);
sessions.put(agentId, session);
return session;
}
不同 Agent 的命令在各自的 Ubuntu 会话中执行,工作目录、环境变量、进程空间完全隔离。Agent A 的 cd /project-a 不会影响 Agent B。
4.4 数据层设计:13 张表 + 消息版本控制 + FTS4 记忆搜索
Swees 的数据层基于 SQLite,共 13 张表(含 FTS 虚拟表),覆盖对话、消息、智能体、设置、记忆、IPC 任务等全部业务域。
表结构总览
| 表名 | 职责 | 关键字段 |
|---|---|---|
conversations |
对话主题 | agent_id, starred, cache_hit/miss |
messages |
消息记录 | role, content, tool_call, reasoning, prev_id, is_current, bubble_id |
agents |
智能体配置 | system_prompt, model, provider, session_id, trust_level |
settings |
全局设置 | key-value 结构 |
i18n |
国际化文本 | (key, lang) 联合主键 |
request_logs |
AI 请求日志 | request_body(API Key 脱敏) |
ipc_tasks |
IPC 任务队列 | action, status, result |
memories |
跨对话记忆 | content, keywords, category, importance |
memories_fts |
记忆全文索引 | FTS4 虚拟表 |
消息版本控制:prev_id / is_current / bubble_id
这是 Swees 数据层最精巧的设计之一。在 AI 对话中,同一条用户消息可能触发多次 AI 回复(比如重新生成、Function Calling 多轮调用)。Swees 用三个字段实现了消息的版本树:
prev_id:指向父消息的 ID,构建消息版本链。is_current:标记当前显示的版本(1=当前,0=历史)。bubble_id:同一「气泡」(UI 上的一个消息块)的所有版本共享同一个bubble_id,删除时级联清理。
用户消息 (bubble_id=B1)
├── AI回复v1 (prev_id=user_msg, is_current=0, bubble_id=B2)
└── AI回复v2 (prev_id=user_msg, is_current=1, bubble_id=B2) ← 当前显示
这种设计让「重新生成」变得简单:新建一条 is_current=1 的消息,把旧版本标记为 is_current=0,UI 只渲染当前版本,但历史版本完整保留。
FTS4 全文搜索:让 AI 拥有记忆
memories 表配合 memories_fts 虚拟表,实现了跨对话的持久化记忆系统。Swees 使用 SQLite 的 FTS4 扩展(standalone 模式,自带数据副本),通过触发器自动同步:
-- 插入记忆时自动同步到 FTS 索引
CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN
INSERT INTO memories_fts(rowid, content, keywords)
VALUES (new.id, new.content, new.keywords);
END;
-- 全文搜索
SELECT * FROM memories_fts WHERE memories_fts MATCH '用户偏好';
记忆表还设计了 importance(1-10 重要性评分)、access_count(命中次数)、last_accessed(最近命中时间)、expires_at(过期时间)等字段,支持记忆的优先级排序和生命周期管理。
请求日志自动裁剪
request_logs 表通过一个 SQLite 触发器实现自动裁剪,只保留最近 100 条记录:
CREATE TRIGGER trg_request_logs_trim AFTER INSERT ON request_logs BEGIN
DELETE FROM request_logs WHERE id IN (
SELECT id FROM request_logs
ORDER BY timestamp DESC LIMIT -1 OFFSET 100
);
END
这是一个优雅的设计——不需要 Java 层的定时清理任务,数据库自己搞定。
五、工程决策与思考
为什么不用前端框架?
Swees 的前端是 ~12K 行纯 HTML/CSS/JS,没有 React、Vue、也没有任何构建工具。这个决定并非偷懒,而是深思熟虑的结果:
- WebView 性能:移动端 WebView 加载 React/Vue 的 runtime 本身就有开销,而 Swees 的 UI 逻辑用原生 JS 完全可以胜任。
- 包体积:零依赖意味着 APK 更小,用户下载更快。
- 调试友好:没有构建步骤,改完代码刷新即生效,开发体验极佳。
- 可控性:每一行代码都是自己写的,不存在框架黑盒。
为什么选 PRoot 而非 Termux?
Termux 是 Android 上最成熟的 Linux 环境方案,但它有自己的包管理器和非标准目录结构(/data/data/com.termux/files/usr/)。这意味着 apt、dpkg 等 Debian 系工具无法直接使用,很多依赖标准 Linux 目录结构的软件包会出问题。
PRoot + Ubuntu rootfs 的方案虽然性能略低(ptrace 拦截有开销),但提供了100% 兼容的 Ubuntu 环境——apt install 开箱即用,所有教程和 StackOverflow 答案都适用。对于 AI Agent 场景,兼容性远比性能重要。
为什么用 vanilla JS 而非 TypeScript?
在 WebView 环境中,TypeScript 需要额外的编译步骤。Swees 的前端代码通过 SweesBridge 直接与 Java 交互,类型安全由 Java 侧的 @JavascriptInterface 注解保证。引入 TypeScript 会增加构建复杂度,但收益有限。
六、性能与优化
SSE 解析效率
SseParser 采用逐行读取 + 即时回调的模式,不缓存整个响应体。每个 SSE chunk 解析后立即通过回调推送到前端,内存占用恒定(仅 StringBuilder full 累积完整文本)。对于长回复,这意味着内存不会随输出长度线性增长。
PRoot 会话池复用
ProotSessionPool 使用 ConcurrentHashMap 管理会话,按 agentId 复用:
private static final long IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 分钟空闲超时
public void reapIdle() {
long now = System.currentTimeMillis();
for (Map.Entry<String, Long> e : lastActivity.entrySet()) {
if (now - e.getValue() > IDLE_TIMEOUT_MS) destroy(e.getKey());
}
}
每次命令执行时更新 lastActivity 时间戳,空闲超过 5 分钟的会话自动回收。这避免了频繁创建/销毁 PRoot 进程的开销(每次启动 PRoot 需要初始化 bash 环境,约 1-2 秒),同时防止内存泄漏。
请求日志自动裁剪
前文提到的触发器自动裁剪机制,确保 request_logs 表不会无限增长。这是一个「设置即忘记」的设计——不需要 Java 层的 Handler.postDelayed 或 WorkManager 定时任务,数据库触发器在每次插入时自动执行清理。
七、未来规划
Swees 仍在积极开发中,路线图包括:
- 🌐 更多 AI 服务商:扩展对 Claude、Gemini、Qwen 等更多模型的支持
- 🔌 插件/技能市场:社区驱动的技能生态,一键安装新能力
- ☁️ 云端同步:对话和记忆的跨设备同步
- 📱 平板适配:针对大屏设备的响应式布局
- 🔐 端侧模型:探索在手机上运行小型 LLM 的可能性
八、结语
Swees 证明了一件事:在 2026 年,一部普通的 Android 手机,足以成为 AI Agent 的完整运行环境。
不需要 root,不需要电脑,不需要云服务器。PRoot 提供了 Linux 环境,SSE 实现了实时交互,Function Calling 赋予了行动能力,SQLite + FTS4 构建了持久记忆——所有这些技术组合在一起,让 AI 从「能说」变成了「能做」。
这是一个开源项目,基于 GPL v3 协议,代码完全公开。
🔗 GitHub: https://github.com/wildwalker2026/Swees
如果你觉得这个项目有意思,欢迎 Star ⭐ 支持。如果你是开发者,欢迎提 Issue、提 PR、写技能——每一份贡献都让 Swees 更好。
Swees — 让 AI 真正动手。
更多推荐

所有评论(0)