本文深入剖析开源项目 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 行代码实现完整 SPAAI 编写五子棋 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原理适合 SweesTermux否自带包管理器但非标准 Linux 目录结构❌ 目录结构不兼容 apt/dpkgDocker是容器化❌ 需要 rootPRoot否通过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_appSELinux 域的安全策略禁止普通应用调用link()/linkat()创建硬链接返回EACCES。这直接导致 PRoot 内的dpkg在执行apt install时卡死——因为 dpkg 依赖硬链接来做 status 文件的原子备份。Swees 的解决方案是一段精巧的 C 代码——linkhook.c/* 拦截 link()先尝试真实硬链接失败则降级 */intlink(constchar*oldpath,constchar*newpath){if(real_link(oldpath,newpath)0)return0;if(errnoEACCES||errnoEPERM){returnlink_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 实现了一套完整的 SSEServer-Sent Events解析与渲染链路。Java 端SseParserSseParser是整个流式输出的核心负责解析 OpenAI 兼容协议的 SSE 流。它的设计有几个值得注意的工程细节publicstaticParseResultparse(InputStreamis,DeltaConsumeronDelta,DeltaConsumeronReasoning,ToolCallDeltaConsumeronToolCallDelta)throwsException{// ...while((liner.readLine())!null){if(!line.startsWith(data:))continue;Stringdataline.substring(5).trim();if(data.equals([DONE]))break;JSONObjectchunknewJSONObject(data);JSONObjectdeltachoices.getJSONObject(0).optJSONObject(delta);if(delta!null){// 1. 思维链增量reasoning_contentStringreasoningdelta.isNull(reasoning_content)?:delta.optString(reasoning_content,);if(!reasoning.isEmpty())onReasoning.onDelta(reasoning);// 2. 正文增量contentStringcontentdelta.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()对 JSONnull值会返回字符串null而非空字符串——这是一个经典的 Android 开发坑。Swees 在每个字段解析处都做了显式判空。③ tool_calls 流式分片累积。OpenAI 协议中tool_calls的arguments字段是分片传输的——首个 chunk 携带id和name后续 chunk 只携带arguments的片段。SseParser使用LinkedHashMapInteger, ToolCall按index累积拼接if(existingnull){// 首个分片建条目toolCallMap.put(idx,newChatRequest.ToolCall(id,name,argsPart));}else{// 后续分片拼 argumentsStringmergedArgsexisting.argumentsargsPart;toolCallMap.put(idx,newChatRequest.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 构建了一套多层安全体系。命令危险分级DangerLevelDangerLevel是一个纯函数式的命令风险评估器将每条命令分为LOW自动执行和HIGH需用户确认两级publicenumLevel{LOW,// 自动执行HIGH// 需用户确认}publicstaticLevelassess(Stringcommand){Stringcmdcommand.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 ))returnLevel.HIGH;// SQL UPDATE 需同时包含 update 和 set避免误伤 apt updateStringlowercmd.toLowerCase();if(lower.contains(update )lower.contains( set ))returnLevel.HIGH;returnLevel.LOW;}设计亮点覆盖面广涵盖文件删除、系统操作、权限变更、网络下载、Git 危险操作、数据库写操作等 7 大类高危命令。精准误伤控制比如 SQLUPDATE的判定要求同时包含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管理publicProotSessiongetOrCreate(StringagentId,StringworkDir)throwsException{ProotSessionsessionsessions.get(agentId);if(session!nullsession.isAlive()){lastActivity.put(agentId,System.currentTimeMillis());returnsession;// 复用已有会话}sessionnewProotSession(workDir);session.start(prootPath,rootfsPath,nativeLibDir,tmpDir);sessions.put(agentId,session);returnsession;}不同 Agent 的命令在各自的 Ubuntu 会话中执行工作目录、环境变量、进程空间完全隔离。Agent A 的cd /project-a不会影响 Agent B。4.4 数据层设计13 张表 消息版本控制 FTS4 记忆搜索Swees 的数据层基于 SQLite共 13 张表含 FTS 虚拟表覆盖对话、消息、智能体、设置、记忆、IPC 任务等全部业务域。表结构总览表名职责关键字段conversations对话主题agent_id,starred,cache_hit/missmessages消息记录role,content,tool_call,reasoning,prev_id,is_current,bubble_idagents智能体配置system_prompt,model,provider,session_id,trust_levelsettings全局设置key-value 结构i18n国际化文本(key, lang)联合主键request_logsAI 请求日志request_bodyAPI Key 脱敏ipc_tasksIPC 任务队列action,status,resultmemories跨对话记忆content,keywords,category,importancememories_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_idB1) ├── AI回复v1 (prev_iduser_msg, is_current0, bubble_idB2) └── AI回复v2 (prev_iduser_msg, is_current1, bubble_idB2) ← 当前显示这种设计让「重新生成」变得简单新建一条is_current1的消息把旧版本标记为is_current0UI 只渲染当前版本但历史版本完整保留。FTS4 全文搜索让 AI 拥有记忆memories表配合memories_fts虚拟表实现了跨对话的持久化记忆系统。Swees 使用 SQLite 的 FTS4 扩展standalone 模式自带数据副本通过触发器自动同步-- 插入记忆时自动同步到 FTS 索引CREATETRIGGERmemories_aiAFTERINSERTONmemoriesBEGININSERTINTOmemories_fts(rowid,content,keywords)VALUES(new.id,new.content,new.keywords);END;-- 全文搜索SELECT*FROMmemories_ftsWHEREmemories_ftsMATCH用户偏好;记忆表还设计了importance1-10 重要性评分、access_count命中次数、last_accessed最近命中时间、expires_at过期时间等字段支持记忆的优先级排序和生命周期管理。请求日志自动裁剪request_logs表通过一个 SQLite 触发器实现自动裁剪只保留最近 100 条记录CREATETRIGGERtrg_request_logs_trimAFTERINSERTONrequest_logsBEGINDELETEFROMrequest_logsWHEREidIN(SELECTidFROMrequest_logsORDERBYtimestampDESCLIMIT-1OFFSET100);END这是一个优雅的设计——不需要 Java 层的定时清理任务数据库自己搞定。五、工程决策与思考为什么不用前端框架Swees 的前端是 ~12K 行纯 HTML/CSS/JS没有 React、Vue、也没有任何构建工具。这个决定并非偷懒而是深思熟虑的结果WebView 性能移动端 WebView 加载 React/Vue 的 runtime 本身就有开销而 Swees 的 UI 逻辑用原生 JS 完全可以胜任。包体积零依赖意味着 APK 更小用户下载更快。调试友好没有构建步骤改完代码刷新即生效开发体验极佳。可控性每一行代码都是自己写的不存在框架黑盒。为什么选 PRoot 而非 TermuxTermux 是 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复用privatestaticfinallongIDLE_TIMEOUT_MS5*60*1000;// 5 分钟空闲超时publicvoidreapIdle(){longnowSystem.currentTimeMillis();for(Map.EntryString,Longe: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 真正动手。