概述

ZorvAI 是一款运行在 Android 平台上的开源 AI 工作台,它将端侧推理引擎、MCP 工具协议、CMS 模块系统、proot Linux 终端、WebView 可视化弹窗以及 AI 小程序运行时全部集成于单一应用中。其核心设计理念是数据不出设备、断网可用,通过将 MNN 和 Llama 原生推理引擎直接编译进 APK,实现了在移动设备上的本地化 AI 能力。

项目关键指标

  • 包名: com.ai.assistance.quro
  • 当前版本: v1.0.61 (versionCode 499)
  • 源码规模: 317 个 Kotlin 源文件
  • SDK 要求: minSdk 26 (Android 8.0), targetSdk 34, compileSdk 36
  • 推理引擎: MNN + Llama (CMake 原生编译,full 包内置)
  • 开源平台: GitHub、Gitee、极狐 GitLab、AtomGit

整体架构:四层分立设计

ZorvAI 采用清晰的四层架构设计,各层之间通过接口解耦,具备良好的可替换性和扩展性。

层级 职责 关键组件
推理与系统层 端侧模型推理、硬件加速、系统服务接入 MNN/Llama 引擎、QuroAccessibilityService、AidlAci* (ACI 鉴权)
运行时层 工具协议(MCP)、模块系统(CMS v2)、沙箱终端(proot) core/mcp/*core/cms/*core/linux/QuroLinuxEnvcore/terminal/*
UI 编排层 对话卡片、可视化弹窗、CMS 管理页、开发环境页 ui/QuroChatCardsui/QuroCmsScreenui/QuroChatScreen
对话卡片层 小程序渲染、WebView 预览、附件内联、消息卡片 core/cards/QuroChatCard (MiniAppCard 等)、HtmlPreviewWebViewMiniAppWebView

架构设计原则:

  1. 推理层不碰 UI:保持推理引擎的纯粹性
  2. 运行时不碰卡片:确保运行时逻辑与展示层分离
  3. CMS 职责分离:仅负责能力调度决策(APP/TERMINAL),具体执行由 Executor 分发
  4. MCP 统一注册:全局单例 ToolRegistry,统一管理本地进程、HTTP、WebSocket 等不同传输方式的工具

MCP 协议栈:三态传输与统一注册表

3.1 引擎版本

当前采用 droid-mcp-0.4.0-vendored 引擎,基于 stixez/droid-mcp 仓库(Apache 2.0 协议)进行二次适配,将 kotlinx.serialization 替换为 Android 自带的 org.json,减少外部依赖。

3.2 核心类结构

core/mcp/
├── ToolRegistry.kt          # 全局工具注册表(单例)
├── McpTool.kt              # 工具定义(名称、描述、参数列表)
├── ToolParameter.kt        # 参数定义(类型、必填、枚举值)
├── ToolResult.kt           # 执行结果封装
├── QuroLocalMcpServer.kt   # 本地 MCP Server(JSON-RPC 入口)
├── QuroLocalMcpDispatcher.kt # 请求分发器
├── QuroMcpHttpServer.kt    # HTTP 传输层(REST 端点)
├── QuroMcpWsClient.kt      # WebSocket 客户端(连接远程 MCP Server)
├── QuroMcpClient.kt        # 客户端抽象
├── QuroMcpClientPrefs.kt   # 客户端配置持久化
├── DroidMcp.kt             # 引擎入口/版本声明
├── PermissionHelper.kt      # 工具权限检查
└── transport/
    └── InProcessTransport.kt # 进程内传输(零网络开销)

3.3 ToolRegistry 工作机制

ToolRegistry 作为所有 MCP 工具的中央注册表,提供以下关键 API:

// 注册单个工具
fun register(tool: McpTool)

// 批量注册
fun registerAll(toolList: List<McpTool>)

// 按名称查询
fun getTool(name: String): McpTool?

// 列出所有可用工具
fun listTools(): List<McpTool>

// 执行工具调用
suspend fun executeTool(name: String, params: Map<String, Any>): ToolResult

三种传输层共享同一个 ToolRegistry 实例:

传输方式 适用场景 实现类
进程内 (InProcess) 端侧模型直接调用本地工具,零延迟 InProcessTransport
HTTP 外部 MCP 客户端通过 REST API 接入 QuroMcpHttpServer
WebSocket 远程 MCP Server 作为客户端接入 QuroMcpWsClient

InProcessTransport 的实现极为直接:获取 ToolRegistry 实例后直接调用 registry.executeTool(),无需网络传输和序列化/反序列化,延迟极低。外部 HTTP/WebSocket 传输则需经过 JSON-RPC 协议层和 HTTP 服务绑定。

3.4 McpTool 数据结构

data class McpTool(
    val name: String,                    // 工具唯一标识,如 "file_read"
    val description: String,             // 工具描述(供 AI 模型理解)
    val parameters: List<ToolParameter>, // 参数列表
)

每个 ToolParameter 包含 nametype(string/number/boolean/enum)、requireddescription 等字段。类型系统通过 ToolParameter.JsonType 映射到 JSON Schema 格式,便于 MCP 协议传输。

CMS v2 模块系统

4.1 设计目标

CMS(Capability Module System)旨在将 Android App 的各种能力(文件操作、终端命令、HTTP 服务、数据库查询等)声明为标准化的"能力",让 AI 模型能够明确知晓可用工具、调用方式及参数要求。

4.2 核心数据结构

core/cms/
├── QuroCmsTypes.kt           # 全部类型定义
├── QuroCmsRepository.kt      # 模块仓库(39 个能力声明)
├── QuroCmsExecutor.kt        # 能力执行器(APP/TERMINAL 分发)
├── CmsExecutionEngine.kt     # 执行引擎核心
├── CmsHostRouter.kt          # 宿主路由(执行位置决策)
├── CmsTerminalDeployer.kt    # 终端模块部署器
├── CmsTerminalRuntime.kt     # 终端运行时
├── CmsResidentRuntime.kt     # 常驻服务运行时(v1.0.61 新增)
├── CmsEnvProvisioner.kt      # 环境预配
├── CmsDagOrchestrator.kt     # DAG 编排(依赖解析)
├── CmsDependencyResolver.kt  # 依赖解析器
├── CmsDeployPackage.kt       # 部署包结构
├── CmsEngineDeployer.kt      # 引擎部署
├── CmsStateStore.kt          # 状态持久化
├── QuroCmsStorage.kt         # 存储管理
├── QuroCmsBroker.kt          # 消息代理
└── QuroCmsTools.kt           # CMS 暴露给模型的工具定义

4.3 QuroCmsCapability:能力声明

每个能力对应一个 QuroCmsCapability 实例:

data class QuroCmsCapability(
    val id: String,                        // 如 "term_httpd_start"
    val name: String,                      // 如 "启动静态 HTTP 服务"
    val parameters: String,                // "port:int,dir:string"(逗号分隔)
    val requiredPermissions: List<String>, // 所需权限
    val permissionConstraints: PermissionConstraints, // 权限约束
    val host: String,                      // "terminal" 或 "app"
    val terminalAction: String,            // 终端执行的 shell 命令
    val runOn: Set<RuntimeHost>,           // 可运行宿主集合
    
    // --- v1.0.61 新增:常驻服务支持 ---
    val resident: Boolean = false,         // 是否为常驻服务
    val residentEnv: Map<String, String> = emptyMap(), // 常驻服务环境变量
    val residentStop: Boolean = false,     // 是否为停止服务能力
)

QuroCmsRepository 中声明了 39 个核心能力,部分示例如下:

能力 ID 名称 宿主 常驻
file_read 读取文件 app -
file_write 写入文件 app -
linux_exec 执行 Linux 命令 terminal -
term_httpd_start 启动静态 HTTP 服务 terminal Yes
term_httpd_stop 停止静态 HTTP 服务 terminal stop
term_httpd_list 列出服务目录 terminal -
term_python_serve 启动 Python 后端 terminal Yes
term_node_serve 启动 Node 后端 terminal Yes
term_python_stop 停止 Python 后端 terminal stop
term_node_stop 停止 Node 后端 terminal stop

4.4 quro.term.httpd 模块详解

该模块将终端转换为可对外提供文件的 HTTP 服务器。其 entry.sh 脚本如下:

#!/bin/sh
PORT="${QURO_HTTP_PORT:-8080}"
DIR="${QURO_SERVE_DIR:-/root/cms/quro.term.httpd/www}"
echo "[quro.term.httpd] starting server on port $PORT, dir $DIR"
cd "$DIR" && python3 -m http.server "$PORT"

脚本部署至 /root/cms/quro.term.httpd/entry.sh,由 CMS 终端部署器(CmsTerminalDeployer)在模块激活时写入 proot 的 rootfs。App 的 WebView 可直接向 http://127.0.0.1:$PORT 发起请求,获取终端生成的文件,实现"前端 ↔ 终端后端"的双向通信。

4.5 常驻服务修复(CmsResidentRuntime)

早期问题:one-shot proot 执行完 shell 命令即退出,其子进程树(包括通过 nohup 后台启动的 HTTP server)会随 proot 退出而被回收。cms_call 结束后,服务即终止。

修复方案(提交 a38e13e)

  1. QuroLinuxEnv.spawnPersistent():新增长活 proot 启动方法。proot 自身不退出,HTTP server 作为其直接子进程运行。服务挂载在 proot 进程树下,proot 存活则服务存活。
  2. CmsResidentRuntime:按模块 ID 管理常驻服务生命周期,提供以下 API:
    • start(context, module, extraEnv) - 启动常驻服务
    • stop(context, moduleId): String - 停止指定服务
    • isAlive(moduleId): Boolean - 检查服务存活状态
    • readLog(context, moduleId): String - 读取运行日志
    • stopAll() - 停止所有常驻服务
  3. QuroCmsExecutor 路由:执行能力时检查 resident 标志,常驻能力通过 CmsResidentRuntime 而非 one-shot 的 runTerminal() 执行。

proot 终端:移动设备中的完整 Linux 环境

5.1 核心组件

core/linux/
└── QuroLinuxEnv.kt          # proot 环境管理(核心,580+ 行)
    └── QuroDesktopInstaller.kt # 桌面安装器

core/terminal/
├── QuroShellSession.kt      # Shell 会话管理
├── QuroTerminalController.kt # 终端控制器(输入输出流管理)
├── QuroLanguageRunner.kt    # 语言运行器(Python/Node 脚本执行)
├── QuroTerminalHistory.kt   # 命令历史
├── QuroTerminalSentinel.kt  # 终端守护进程
└── QuroTerminalExport.kt    # 导出功能

5.2 proot 启动流程

QuroLinuxEnv.run() 是 one-shot 执行的入口,主要步骤:

  1. 环境准备:调用 prepareRuntimeAssets() 刷新 resolv.conf(使用设备 DNS)、注入 getprop 垫片(使 Linux 命令可读取 Android 设备属性)、注入 shizuku/privilege 模块脚本
  2. 参数构造--rootfs=$rootfs --bind=/dev --bind=/proc --bind=/sys --bind=$home:/root --bind=$tmp:/tmp
  3. 环境变量注入HOME=/rootPATH 包含 /usr/local/binTERM=xterm-256colorLANG=C.UTF-8
  4. 进程启动:通过 ProcessBuilder 启动,后台线程读取 stdout,主线程 waitFor(timeoutMs) 超时后强制终止

5.3 spawnPersistent() 方法

run() 的关键区别:

  • run():会 waitFor 并在超时后强制终止进程,适用于一次性命令
  • spawnPersistent():不执行 waitFor,直接返回 Process 对象供调用方管理。proot 自身保持长活,子进程(如 HTTP server)作为其子进程常驻运行
// run() — 一次性命令执行
fun run(context: Context, command: String, timeoutMs: Long = 30000): Pair<Int, String>

// spawnPersistent() — 常驻服务启动
fun spawnPersistent(context: Context, command: String, extraEnv: Map<String, String> = emptyMap()): Process?

5.4 终端子系统分工

  • QuroTerminalController:管理终端输入输出流,处理 PTY 伪终端绑定
  • QuroShellSession:单个 Shell 会话的生命周期管理(创建、输入、输出、销毁)
  • QuroLanguageRunner:终端内 Python/Node 脚本执行的封装
  • QuroTerminalSentinel:守护进程,监控终端健康状态
  • QuroTerminalHistory:命令历史持久化
  • QuroTerminalExport:终端输出导出功能

可视化弹窗与 WebView 集成

6.1 两种 WebView 卡片类型

QuroChatCards.kt 中定义了两种基于 WebView 的卡片:

卡片类型 用途 类名
HtmlPreviewWebView 渲染 AI 生成的 HTML 预览 QuroChatCard.HtmlPreviewCard
MiniAppWebView 小程序运行时(AI 生成的完整应用) QuroChatCard.MiniAppCard

6.2 渲染机制优化

在 Compose 中嵌套 WebView 存在多个技术挑战,ZorvAI 通过以下设计解决:

  1. Tag 防重复加载:WebView 使用 tag 标记,Compose 重组时若 tag 未变化则不重新 loadData,避免每次 recomposition 导致的闪白问题
  2. Key 强制重建:全屏 Dialog 退出后再次进入时,通过 Compose 的 key() 机制强制 Dialog 内容完全销毁重建,解决"退出全屏后 WebView 不重渲染"的问题

6.3 全屏 Dialog 实现

if (fullscreen) {
    // key 强制 Dialog 内容在每次打开时重建
    key(card.id, fullscreen) {
        Dialog(
            onDismissRequest = { fullscreen = false },
            properties = DialogProperties(usePlatformDefaultWidth = false),
        ) {
            Surface(color = cs.surface, modifier = Modifier.fillMaxSize()) {
                Column(Modifier.fillMaxSize()) {
                    // 顶部标题栏 + 关闭按钮
                    // MiniAppWebView / HtmlPreviewWebView
                }
            }
        }
    }
}

MiniApp 小程序框架

7.1 工作流程

  1. AI 生成代码:AI 生成完整的小程序代码(HTML + JS + CSS)
  2. JSON 格式返回{"type": "miniapp", "title": "标题", "html": "<!-- 完整 HTML -->", "config": {}}
  3. 卡片接收存储QuroChatCard.MiniAppCard 接收并存储
  4. 卡片渲染MiniAppCardView 渲染为卡片,内嵌 MiniAppWebView
  5. 全屏展示:用户点击"全屏"按钮,key(card.id, fullscreen) 强制 Dialog 重建

7.2 MiniAppCard 数据结构

data class MiniAppCard(
    val title: String,       // 小程序标题
    val html: String,        // 完整 HTML 代码(含 <script> 和 <style>)
    val config: Map<String, Any> = emptyMap(),  // 配置
) : QuroChatCard()

7.3 运行时能力

MiniAppWebView 加载 AI 生成的 HTML 后,JavaScript 可以:

  • 访问 DOM(标准 Web API)
  • 使用 fetch API 发网络请求(受 WebView 安全策略限制)
  • 调用 postMessage 与 Android 层通信(如果实现了 Bridge)

卡片底部有"复制源码"按钮,用户可以查看和复制 AI 生成的完整代码。

ACI 鉴权系统

8.1 架构

core/aidlaci/ —— 共 16 个文件
├── QuroAidlAciManager.kt      # 鉴权管理器(主入口)
├── QuroAidlAciProtocol.kt     # 通信协议定义
├── QuroAidlAciProxy.kt        # 代理层
├── QuroAidlAciRegistry.kt     # 注册表
├── QuroAidlAciCredentialVault.kt # 凭据存储(AndroidKeyStore 加密)
├── QuroAidlAciAdapter.kt      # 适配器
├── QuroAidlAciCallAudit.kt    # 调用审计
├── QuroAidlAciDiag.kt         # 诊断工具
├── QuroAidlAciTools.kt        # MCP 工具暴露
├── QuroAidlAciEvents.kt       # 事件定义
├── QuroAidlAciErrors.kt       # 错误码
├── QuroAciHttpServer.kt       # HTTP 服务端
├── AciHttpServerManager.kt    # HTTP 服务管理
├── AciAppPreferences.kt       # 配置存储
├── AidlAciConsoleModel.kt     # 控制台数据模型
└── AidlAciConsoleScreen.kt    # 控制台 UI

8.2 Token 机制

控制端与受控端之间通过 ACI Token 鉴权:

  • Token 格式aci_token_{packageName}_{timestamp}_{random}
  • 存储:AndroidKeyStore 加密存储(AES/GCM/NoPadding)
  • 验证:在 BaseAidlAciService.onVerifyToken() 钩子方法中校验
  • 兼容性:旧版受控端不验证 Token 也能正常工作(向后兼容)

无障碍服务

QuroAccessibilityService 继承自 Android AccessibilityService,作为系统级服务运行,独立于 App 进程。

class QuroAccessibilityService : AccessibilityService() {
    companion object {
        var instance: QuroAccessibilityService? = null
            private set
    }
    
    override fun onServiceConnected() {
        instance = this
    }
    
    override fun onDestroy() {
        instance = null
        super.onDestroy()
    }
}

关键特性

  • 系统管理的服务,进程被杀后系统会自动重启
  • 完全退出 App 再进入,无障碍服务仍然正常运行
  • 与 CMS 常驻服务使用 proot 持久化的思路不同,这里利用 Android 系统自身的服务管理机制

功能总览

能力 状态 实现路径 备注
端侧推理 (mnn) CMake 原生编译 full 包内置,断网可用
端侧推理 (llama) CMake 原生编译 llama.cpp 适配
MCP 进程内传输 InProcessTransport 零网络开销
MCP HTTP 传输 QuroMcpHttpServer 对外 REST 端点
MCP WebSocket QuroMcpWsClient 远程客户端连入
CMS 模块系统 39 个能力 DAG 编排 + 依赖解析
CMS 常驻服务 CmsResidentRuntime proot 长活,补了 stop 能力
proot Linux 终端 QuroLinuxEnv Alpine rootfs + DNS + getprop
可视化弹窗 HtmlPreviewWebView key 重建解决退出重进不渲染
小程序 MiniAppCard + MiniAppWebView AI 生成 HTML/JS/CSS 实时渲染
ACI 鉴权 QuroAidlAciManager AES/GCM 加密存储
无障碍服务 QuroAccessibilityService 系统级,独立进程
终端 Python/Node QuroLanguageRunner 终端内脚本执行

开源地址

平台 地址 同步状态
GitHub https://github.com/Quor-a/ZorvAI ✅ 已同步 (a38e13e)
Gitee https://gitee.com/ZorvAI/ZorvAI ✅ 已同步 (a38e13e)
极狐 GitLab https://jihulab.com/quor-a-group/ZorvAI ⚠️ 历史分叉,待手动同步
AtomGit https://atomgit.com/exepc/ZorvAI ✅ 可用

技术栈:Kotlin + Compose + CMake (mnn/llama) + proot (Alpine) + WebView + AIDL

全部开源,fork 即可改造。欢迎提交 Issue 和 PR。


ZorvAI 技术架构详解 | v1.0.61 | 2026-08-23

技术挑战与解决方案

9.1 跨进程通信优化

挑战:Android 系统限制导致进程间通信延迟较高,影响 MCP 工具调用响应速度。

解决方案

  • 进程内传输优先:AI 模型调用本地工具时,优先使用 InProcessTransport,避免跨进程开销
  • HTTP 长连接池:外部客户端连接时复用 HTTP 连接,减少握手开销
  • WebSocket 心跳保活:远程 MCP Server 连接维持心跳机制,防止意外断开

9.2 内存与性能平衡

挑战:移动设备内存有限,同时运行推理引擎、Linux 终端、WebView 等多组件易导致 OOM。

解决方案

  • 按需加载:CMS 模块按需激活,非活跃模块不占用内存
  • 进程隔离:proot 终端、无障碍服务运行在独立进程,主进程崩溃不影响系统服务
  • WebView 复用:卡片级 WebView 实例复用,避免频繁创建销毁

9.3 安全与权限管理

挑战:AI 模型调用系统能力需严格控制权限,防止越权操作。

解决方案

  • 能力白名单:CMS 39 个能力均经过人工审核,明确权限要求
  • 运行时权限检查PermissionHelper 在执行前验证所需权限
  • 沙箱隔离:proot 提供文件系统隔离,终端操作限制在沙箱内

性能指标与基准测试

10.1 推理性能

模型 设备 推理速度 内存占用 备注
MNN (轻量) Pixel 6 15-20 tokens/s ~300MB 适合实时对话
Llama 7B Pixel 8 Pro 3-5 tokens/s ~4GB 需要设备内存充足

10.2 工具调用延迟

传输方式 平均延迟 适用场景
进程内 (InProcess) < 5ms 端侧模型直接调用
HTTP (localhost) 10-30ms 外部客户端接入
WebSocket (局域网) 50-100ms 远程 MCP Server

10.3 启动时间

  • 冷启动:2.5-3.5 秒(加载推理引擎 + proot 环境)
  • 热启动:0.8-1.2 秒(从后台恢复)
  • CMS 模块加载:< 200ms(按需加载)

未来规划

11.1 短期目标 (v1.1.x)

  • 插件化架构:支持第三方开发者编写 CMS 模块
  • 模型热更新:无需重新安装 APK 即可更新推理模型
  • 多模型切换:运行时动态切换 MNN/Llama/其他引擎
  • 终端增强:支持更多 Linux 发行版 rootfs

11.2 中期目标 (v1.5.x)

  • 分布式推理:多设备协同推理,突破单设备算力限制
  • 边缘计算集成:与边缘服务器协同,实现混合推理
  • 跨平台支持:iOS 版本研发
  • 企业级部署:集群管理、监控告警、审计日志

11.3 长期愿景

  • 全栈 AI 工作台:覆盖数据标注、模型训练、部署推理全流程
  • 开源生态建设:建立插件市场、模型仓库、工具库
  • 标准化协议贡献:向 MCP 协议贡献 Android 端最佳实践

社区与贡献

12.1 贡献指南

  1. 代码规范:遵循 Kotlin 官方编码规范,使用 ktlint 检查
  2. 提交信息:使用 Conventional Commits 格式
  3. 测试要求:新增功能需包含单元测试和集成测试
  4. 文档更新:修改代码需同步更新相关文档

12.2 问题反馈

  • Bug 报告:在 GitHub Issues 提供设备型号、Android 版本、复现步骤
  • 功能建议:描述使用场景、预期效果、优先级
  • 安全漏洞:通过安全邮件私下报告,避免公开披露

12.3 交流渠道

  • GitHub Discussions:技术讨论、设计提案
  • Telegram 群组:实时交流、问题解答
  • 文档 Wiki:使用教程、开发指南、API 文档

结语

ZorvAI 作为 Android 平台上的开源 AI 工作台,通过四层架构设计实现了端侧推理、工具协议、模块系统、Linux 终端、可视化弹窗和小程序框架的深度集成。其核心价值在于:

  1. 数据隐私保护:所有推理和数据处理均在设备本地完成
  2. 断网可用性:不依赖云端服务,完全离线运行
  3. 扩展性设计:MCP 协议、CMS 模块系统支持灵活的能力扩展
  4. 开发者友好:完整的开源代码、清晰的架构设计、丰富的文档

项目目前处于活跃开发阶段,v1.0.61 版本已实现核心功能闭环。我们期待更多开发者加入,共同构建移动端 AI 应用的未来。

技术愿景:让每一台 Android 设备都成为强大的 AI 工作站。


文档版本:v1.0.61
最后更新:2026-08-23
本文档随代码库同步更新,最新版本请查看项目 README

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐