Zorv AI 终端:基于 proot 沙箱的 Android 终端模拟器架构全解析

1. 引言

Zorv AI 终端是一个完整的 Android 终端模拟器,集成在 Zorv AI 应用中。它不仅仅是一个简单的命令行界面,而是一个具备以下核心能力的完整终端系统:

  • 真实 Linux 用户空间:基于 proot + Ubuntu 24.04 ARM64,提供完整的 Linux 工具链(Python、apt、bash 等)
  • PTY 伪终端 :标准的 /dev/ptmx 伪终端实现,支持交互式 shell 会话
  • 前台服务保活:通过 Android Foreground Service 让终端会话脱离 UI 生命周期,息屏/切换应用不被杀死
  • ACI 跨进程调用:12 个标准化能力,支持其他应用通过 AIDL/HTTP/MCP 调用终端
  • 多种 IPC 接入:ContentProvider、Deep Link、Intent、BroadcastReceiver 四种标准 Android IPC 方式
  • 多会话管理:支持同时运行多个独立的终端会话
  • 开机自启动:设备重启后自动恢复终端保活

开源地址github.com/Quor-a/Zorv...


2. 架构设计

2.1 整体架构图

perl 复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                          终端 UI 层                                  │
│  ChatScreen 输入框「+」→ 终端  /  AI 调用 ui_open_terminal           │
└───────────────────────────┬─────────────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────────────┐
│                      QuroTerminalController                         │
│  会话管理 · 命令路由 · proot/设备shell 自动选择 · 超时控制            │
└───┬──────────────────────┬──────────────────────┬───────────────────┘
    │                      │                      │
┌───▼───────────┐  ┌───────▼────────┐  ┌─────────▼──────────────────┐
│QuroShellSession│  │QuroLinuxEnv    │  │QuroTerminalSessionManager  │
│PTY 会话载体    │  │proot+Ubuntu    │  │多会话管理·跨进程访问        │
│/dev/ptmx      │  │24.04 ARM64     │  │默认/额外/UI/历史会话        │
│fork/exec      │  │rootfs下载/解压  │  │会话隔离·状态同步            │
└───┬───────────┘  └───────┬────────┘  └─────────┬──────────────────┘
    │                      │                      │
┌───▼──────────────────────▼──────────────────────▼───────────────────┐
│                       前台服务层(保活)                               │
│   QuroTerminalKeepAliveService · QuroTerminalAciService             │
│   specialUse 前台服务 · shell 子进程归属服务进程 · 15 秒巡检          │
└───┬──────────────────────┬──────────────────────┬───────────────────┘
    │                      │                      │
┌───▼───────────┐  ┌───────▼────────┐  ┌─────────▼──────────────────┐
│ACI 跨进程     │  │Intent/Provider │  │BroadcastReceiver/DeepLink  │
│12 个能力      │  │ContentProvider │  │6 个广播 Action              │
│AIDL 绑定     │  │content:// URI  │  │quro://terminal/...         │
└───────────────┘  └────────────────┘  └────────────────────────────┘

2.2 设计原则

原则 说明
进程归属 shell 子进程必须归属前台服务进程,服务存活 = 终端存活
环境自动选择 优先使用 proot Linux 环境,不可用时自动降级到设备 shell
多会话隔离 每个终端会话独立运行,互不干扰
跨进程标准化 通过 ACI 协议和标准 Android IPC 暴露能力
Android 版本兼容 支持 Android 8.0(API 26)到 Android 15+

2.3 技术选型

技术 选择 原因
Linux 用户空间 proot + Ubuntu 24.04 ARM64 无需 ROOT,完整 Ubuntu 工具链
伪终端 /dev/ptmx PTY 标准 Linux 伪终端,支持交互式 shell
前台服务 specialUse 类型 Android 14+ 兼容,无需真实数据同步活动
跨进程通信 ACI AIDL + HTTP + MCP 多种调用方式,适配不同场景
IPC 接入 Provider + DeepLink + Intent + Broadcast 标准 Android IPC,无需特殊权限
会话管理 SessionManager 单例 统一管理所有会话,支持跨进程访问

3. 核心组件详解

3.1 QuroTerminalController --- 终端控制器

文件位置app/src/main/java/com/ai/assistance/quro/core/terminal/QuroTerminalController.kt

职责:终端的核心控制器,负责命令路由、环境选择、超时控制。

核心能力

kotlin 复制代码
// 命令执行入口
fun runCommand(command: String, timeout: Long = 14000): String {
    val env = QuroLinuxEnv.getInstance(context)
    return if (env.isReady()) {
        // Linux 环境可用 → 使用 proot
        runCommandInLinux(command, timeout)
    } else {
        // 回退到设备 shell
        runCommandInDeviceShell(command, timeout)
    }
}

关键特性

  • 环境自动检测 :检查 QuroLinuxEnv.isReady(),自动选择 proot 或设备 shell
  • 超时控制:默认 14 秒超时,防止命令挂起
  • 错误处理:捕获所有异常,返回可读错误信息
  • 进程管理:管理子进程生命周期,支持中断执行

3.2 QuroShellSession --- PTY Shell 会话载体

文件位置app/src/main/java/com/ai/assistance/quro/core/terminal/QuroShellSession.kt

职责:PTY 伪终端的完整实现,管理 shell 进程的创建、通信和销毁。

核心能力

kotlin 复制代码
// 会话创建
companion object {
    suspend fun create(
        context: Context,
        env: Map<String, String>,
        name: String = "default",
        onOutput: (String) -> Unit
    ): QuroShellSession {
        // 1. 打开伪终端主设备
        val masterFd = Os.open("/dev/ptmx", O_RDWR or O_NOCTTY)
        // 2. 授权并解锁从设备
        Os.grantpt(masterFd)
        Os.unlockpt(masterFd)
        // 3. 获取从设备名
        val slaveName = Os.slavename(masterFd)
        // 4. 打开从设备
        val slaveFd = Os.open(slaveName, O_RDWR or O_NOCTTY)
        // 5. 设置窗口大小
        val winsize = Winsize(24, 80, 0, 0)
        Os.ioctl(masterFd, TIOCSWINSZ, winsize)
        // 6. fork 子进程
        val pid = fork()
        if (pid == 0) {
            // 子进程:重定向标准 I/O 到从设备
            Os.dup2(slaveFd, 0)
            Os.dup2(slaveFd, 1)
            Os.dup2(slaveFd, 2)
            Os.execve("/bin/sh", arrayOf("/bin/sh"), envp)
        }
        // 7. 父进程:创建会话对象
        return QuroShellSession(masterFd, pid, name, onOutput)
    }
}

关键特性

  • PTY 伪终端 :标准 /dev/ptmx 实现,支持交互式 shell
  • 进程控制fork/exec 创建子进程,TIOCSWINSZ 设置窗口大小
  • 输出流读取:异步读取子进程输出,通过回调传递给 UI
  • 会话状态:跟踪进程 PID、运行状态、启动时间
  • 会话销毁:发送 SIGTERM/SIGKILL 信号,清理资源

3.3 QuroTerminalSessionManager --- 多会话管理器

文件位置app/src/main/java/com/ai/assistance/quro/core/terminal/QuroTerminalSessionManager.kt

职责:统一管理所有终端会话,支持多会话并发和跨进程访问。

核心能力

kotlin 复制代码
// 会话管理器(单例)
object QuroTerminalSessionManager {
    // 会话存储
    private val sessions = mutableMapOf<String, QuroShellSession>()

    // 创建会话
    suspend fun createSession(
        context: Context,
        name: String = "session_${System.currentTimeMillis()}",
        installIfMissing: Boolean = false
    ): QuroShellSession {
        val env = QuroLinuxEnv.getInstance(context)
        if (installIfMissing && !env.isReady()) {
            env.ensureInstalled(context)
        }
        val session = QuroShellSession.create(context, env.getEnv(), name) { output ->
            // 输出回调
        }
        sessions[session.id] = session
        return session
    }

    // 获取会话
    fun getSession(sessionId: String): QuroShellSession? = sessions[sessionId]

    // 列出所有会话
    fun listSessions(): List<Map<String, Any>> {
        return sessions.map { (id, session) ->
            mapOf(
                "id" to id,
                "name" to session.name,
                "is_alive" to session.isAlive(),
                "pid" to session.pid,
                "uptime" to session.getUptime()
            )
        }
    }

    // 销毁会话
    fun destroySession(sessionId: String): Boolean {
        val session = sessions.remove(sessionId) ?: return false
        session.destroy()
        return true
    }
}

会话类型

类型 说明 生命周期
默认会话 主终端界面使用的会话 前台服务保活,最长生命周期
额外会话 用户手动创建的会话 跟随应用进程
UI 会话 终端 UI 界面的会话 跟随 UI 生命周期
历史会话 已结束的会话记录 仅保留状态信息

3.4 QuroLinuxEnv --- Linux 环境后端

文件位置app/src/main/java/com/ai/assistance/quro/core/linux/QuroLinuxEnv.kt

职责:管理 proot + Ubuntu 24.04 ARM64 用户空间,提供 Linux 环境的安装、检测和配置。

核心能力

kotlin 复制代码
class QuroLinuxEnv(private val context: Context) {
    // 路径配置(全动态,无 hardcode)
    private val rootfsPath = File(context.filesDir, "linux-sandbox/rootfs")
    private val prootPath = "${context.applicationInfo.nativeLibraryDir}/libproot.so"
    private val homePath = context.getExternalFilesDir(null)

    // 检测 Linux 环境是否就绪
    fun isReady(): Boolean {
        return rootfsPath.exists() &&
               File(rootfsPath, "usr/bin/sh").exists() &&
               File(prootPath).exists()
    }

    // 安装 Linux 环境(下载 rootfs)
    suspend fun ensureInstalled(context: Context) {
        if (isReady()) return
        // 从 Ubuntu 官方镜像下载 rootfs
        downloadRootfs(context)
        // 解压 rootfs
        extractRootfs(context)
        // 配置 apt 源
        configureAptSources()
    }

    // 获取 proot 启动参数
    fun getProotArgs(): List<String> {
        return listOf(
            "-0", "root",
            "--link2symlink",
            "-w", "/root",
            "--bind=/proc",
            "--bind=/sys",
            "--bind=/dev",
            "--bind=/sdcard:/mnt/sdcard"
        )
    }

    // 获取环境变量
    fun getEnv(): Map<String, String> {
        return mapOf(
            "HOME" to "/root",
            "PATH" to "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "TERM" to "xterm-256color",
            "LANG" to "en_US.UTF-8",
            "TMPDIR" to "/tmp"
        )
    }
}

技术细节

项目 说明
rootfs 格式 ubuntu-noble-aarch64-pd-v4.18.0.tar.xz(Ubuntu 24.04 Noble ARM64)
rootfs 大小 约 80MB(压缩后),解压后约 300MB
下载源 Ubuntu 官方镜像(aliyun / tuna / cdimage)
内置工具 proot.so 形式)、libbashlibbusybox
路径 rootfsPath=File(context.filesDir,"linux-sandbox"),全动态无 hardcode

3.5 QuroTerminalKeepAliveService --- 前台保活服务

文件位置app/src/main/java/com/ai/assistance/quro/service/QuroTerminalKeepAliveService.kt

职责:以前台服务身份存活,让终端会话脱离 UI 生命周期,息屏/切 App 不被杀。

核心原理

scss 复制代码
前台服务调 startForeground()
    → 系统不杀这个进程
    → 进程内 fork 的 shell 子进程也不会被杀
    → 息屏/切 App 不死

核心代码

kotlin 复制代码
class QuroTerminalKeepAliveService : Service() {
    private var heldSession: QuroShellSession? = null  // 服务直接持有终端会话

    override fun onCreate() {
        // 启动前台服务(specialUse 类型)
        startForeground(NOTIF_ID, buildNotification("终端运行中..."),
            ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)

        // 启动巡检循环
        startLoop()
    }

    private fun ensureSessionSafe() {
        // 在服务进程内创建 shell 子进程
        heldSession = QuroShellSession.create(
            context = this,
            env = QuroLinuxEnv.getInstance(this).getEnv(),
            name = "keepalive"
        ) { output ->
            // 输出回调
        }
        // shell 子进程是服务进程的 fork → 服务存活 = shell 子进程存活
    }

    private fun startLoop() {
        // 每 15 秒巡检
        coroutineScope.launch {
            while (isActive) {
                ensureSessionSafe()  // 确保会话存活
                ensureAciService()  // 确保 ACI 服务运行
                updateNotification() // 更新通知
                delay(15_000)       // 15 秒间隔
            }
        }
    }
}

关键特性

特性 实现
前台服务类型 specialUse(Android 14+ 兼容)
巡检间隔 每 15 秒检查会话状态,死亡自动重建
通知栏 常驻「Zorv AI 终端运行中」,点击跳转主界面
会话所有权 shell 子进程归属服务进程,服务存活 = 终端存活
ACI 服务管理 自动启动/重启 QuroTerminalAciService

3.6 QuroTerminalAciService --- ACI 受控端服务

文件位置app/src/main/java/com/ai/assistance/quro/service/QuroTerminalAciService.kt

职责 :继承 BaseAidlAciService,暴露终端全部能力给外部应用调用。

12 个 ACI 能力

能力 入参 返回 说明
exec command(必填) / timeout(可选) / session_id(可选) output / exit_code / error 执行命令
create_session name(可选) session_id / name 创建会话
destroy_session session_id(必填) destroyed 销毁会话
send_input session_id(必填) / input(必填) sent 发送输入
get_session_status session_id(必填) session_id / is_alive / pid / uptime 会话状态
list_sessions --- sessions (array) 列出所有会话
set_session_env session_id / key / value set 设置环境变量
get_session_env session_id / key value 获取环境变量
list_capabilities --- capabilities (array) 列出能力
get_service_status --- running / session_count / uptime 服务状态
get_audit_log limit(可选) logs (array) 审计日志
help --- help_text 帮助信息

4. Linux 沙箱(proot + Ubuntu 24.04 ARM64)

4.1 架构原理

bash 复制代码
┌─────────────────────────────────────────┐
│           Android 应用进程                │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │         proot 进程               │    │
│  │  ┌─────────────────────────┐    │    │
│  │  │    Ubuntu 24.04 ARM64   │    │    │
│  │  │    rootfs 用户空间       │    │    │
│  │  │                         │    │    │
│  │  │  /bin/sh  ← 终端 shell  │    │    │
│  │  │  /usr/bin/python3       │    │    │
│  │  │  /usr/bin/apt           │    │    │
│  │  │  /usr/bin/bash          │    │    │
│  │  └─────────────────────────┘    │    │
│  │                                 │    │
│  │  --bind=/proc  ← 挂载 proc     │    │
│  │  --bind=/sys   ← 挂载 sys      │    │
│  │  --bind=/dev   ← 挂载 dev      │    │
│  │  --link2symlink ← 符号链接兼容  │    │
│  └─────────────────────────────────┘    │
│                                         │
│  proot 路径: nativeLibraryDir/libproot.so │
│  rootfs: filesDir/linux-sandbox/rootfs  │
└─────────────────────────────────────────┘

4.2 rootfs 管理

  • 格式ubuntu-noble-aarch64-pd-v4.18.0.tar.xz
  • 大小:约 80MB(压缩后),解压后约 300MB
  • 下载源:Ubuntu 官方镜像(aliyun / tuna / cdimage)
  • 路径filesDir/linux-sandbox/rootfs,全动态无 hardcode

4.3 内置工具链

工具 说明
proot .so 形式内置,通过 nativeLibraryDir 加载
libbash Bash shell 支持
libbusybox 精简 Unix 工具集
python3 Python 3 运行时
apt 包管理器,可安装更多工具

4.4 CMS 运行时集成

bash 复制代码
# 通过 bootstrap 脚本安装运行时
bootstrap.sh --install NODE    # Node.js
bootstrap.sh --install PYTHON  # Python
bootstrap.sh --install RUST    # Rust
bootstrap.sh --install GO      # Go
bootstrap.sh --install JAVA    # Java
bootstrap.sh --install SSH     # OpenSSH

运行时共享:所有终端会话共享同一套 CMS 运行时,无需重复安装。


5. 前台服务保活机制

5.1 为什么需要前台服务

Android 系统会在以下情况杀死后台进程:

  • 内存不足:系统会回收低优先级进程
  • 电量优化:Doze 模式限制后台活动
  • 用户手动清理:从最近任务列表滑动清除
  • 应用切换:切到其他应用后,原应用可能被杀死

前台服务是 Android 提供的最高优先级服务类型,系统几乎不会杀死前台服务进程。因此,让终端会话归属于前台服务进程,就能保证息屏/切 App 不被杀。

5.2 specialUse 类型选择

Android 14+ 对前台服务类型有严格限制:

类型 限制 适用场景
dataSync 必须有真实数据同步活动 文件同步、云备份
mediaPlayback 必须正在播放媒体 音乐播放器
location 必须正在获取位置 导航应用
specialUse 需要 <property> 标签说明用途 终端保活(我们的选择)

选择 specialUse 的原因

  1. 终端保活不属于 dataSync(没有真实数据同步)
  2. specialUse 需要 <property> 标签说明用途,Google Play 审核时会检查
  3. 添加 <property> 后,startForeground() 不会被系统静默拒绝

5.3 会话所有权模型

perl 复制代码
传统模型(不保活):
App 进程 → fork shell 子进程 → App 被杀 → 子进程也被杀

前台服务模型(保活):
App 进程 → 前台服务 → fork shell 子进程
    ↑
    系统不杀这个进程
    → shell 子进程也不会被杀
    → 息屏/切 App 不死

关键代码

kotlin 复制代码
// QuroTerminalKeepAliveService.kt
private var heldSession: QuroShellSession? = null

private fun ensureSessionSafe() {
    // 在服务进程内创建 shell 子进程
    heldSession = QuroShellSession.create(
        context = this,
        env = QuroLinuxEnv.getInstance(this).getEnv(),
        name = "keepalive"
    ) { output ->
        // 输出回调
    }
    // shell 子进程是服务进程的 fork
    // 服务存活 = shell 子进程存活
}

5.4 巡检与自愈机制

kotlin 复制代码
private fun startLoop() {
    coroutineScope.launch {
        while (isActive) {
            // 1. 检查会话是否存活
            val session = heldSession
            if (session == null || !session.isAlive()) {
                Log.w(TAG, "会话已死亡,重建中...")
                ensureSessionSafe()
            }

            // 2. 检查 ACI 服务是否运行
            ensureAciService()

            // 3. 更新通知
            updateNotification()

            // 4. 等待 15 秒
            delay(15_000)
        }
    }
}

5.5 Android 14+ 兼容性

Manifest 配置

xml 复制代码
<!-- 权限声明 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

<!-- 服务声明 -->
<service
    android:name=".service.QuroTerminalKeepAliveService"
    android:exported="false"
    android:foregroundServiceType="specialUse">
    <property
        android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
        android:value="终端会话保活:保持终端会话在息屏/切换应用时不被杀死" />
</service>

启动代码

kotlin 复制代码
override fun onCreate() {
    super.onCreate()
    try {
        startForeground(
            NOTIF_ID,
            buildNotification("终端运行中..."),
            ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
        )
    } catch (e: Throwable) {
        Log.e(TAG, "前台通知创建失败", e)
        stopSelf()
        return
    }
}

5.6 开机自启动

kotlin 复制代码
// QuroTerminalBootReceiver.kt
class QuroTerminalBootReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
            // 开机自启动前台服务
            val serviceIntent = Intent(context, QuroTerminalKeepAliveService::class.java)
            context.startForegroundService(serviceIntent)
        }
    }
}

Manifest 注册

xml 复制代码
<receiver
    android:name=".service.QuroTerminalBootReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

6. PTY 伪终端实现

6.1 伪终端工作原理

bash 复制代码
┌─────────────────────────────────────────────────┐
│                  终端 UI 进程                     │
│                                                 │
│  ┌─────────────────────────────────────────┐    │
│  │         master fd(主设备)               │    │
│  │  /dev/ptmx                              │    │
│  └──────────────────┬──────────────────────┘    │
│                     │                           │
│                     │ 内核伪终端驱动              │
│                     │                           │
│  ┌──────────────────▼──────────────────────┐    │
│  │         slave fd(从设备)                │    │
│  │  /dev/pts/N                             │    │
│  └──────────────────┬──────────────────────┘    │
│                     │                           │
│                     │ dup2 重定向                │
│                     │                           │
│  ┌──────────────────▼──────────────────────┐    │
│  │         shell 子进程                     │    │
│  │  /bin/sh                                │    │
│  │  stdin  ← slave fd                      │    │
│  │  stdout → slave fd                      │    │
│  │  stderr → slave fd                      │    │
│  └─────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘

6.2 核心代码实现

kotlin 复制代码
// QuroShellSession.kt 核心创建逻辑
val masterFd = Os.open("/dev/ptmx", O_RDWR or O_NOCTTY)
val slaveName = Os.slavename(masterFd)
Os.grantpt(masterFd)
Os.unlockpt(masterFd)
val slaveFd = Os.open(slaveName, O_RDWR or O_NOCTTY)

// 设置窗口大小
val winsize = Winsize(24, 80, 0, 0)
Os.ioctl(masterFd, TIOCSWINSZ, winsize)

// 创建新会话(必须)
Os.setsid()

// fork 子进程
val pid = Os.fork()
if (pid == 0) {
    // 子进程
    Os.dup2(slaveFd, 0)  // stdin
    Os.dup2(slaveFd, 1)  // stdout
    Os.dup2(slaveFd, 2)  // stderr
    Os.execve("/bin/sh", arrayOf("/bin/sh"), envp)
}

6.3 输出流处理

kotlin 复制代码
// 异步读取输出
private fun readOutputLoop() {
    val buffer = ByteArray(4096)
    while (isRunning) {
        val bytesRead = Os.read(masterFd, buffer)
        if (bytesRead > 0) {
            val output = String(buffer, 0, bytesRead)
            onOutput(output)  // 回调给 UI
        } else if (bytesRead == 0) {
            break  // EOF
        }
    }
}

7. 跨进程接入方式

7.1 ContentProvider(TerminalProvider)

Authoritycontent://com.ai.assistance.quro.terminal

支持的路径

路径 方法 说明
/sessions query 列出所有会话
/exec?cmd=... query 执行命令并返回结果
/status query 获取服务状态
/session/{id} query 获取指定会话信息

使用示例

kotlin 复制代码
// 列出会话
val cursor = contentResolver.query(
    Uri.parse("content://com.ai.assistance.quro.terminal/sessions"),
    null, null, null, null
)

// 执行命令
val cursor = contentResolver.query(
    Uri.parse("content://com.ai.assistance.quro.terminal/exec?cmd=uname -a"),
    null, null, null, null
)

7.2 Deep Link(TerminalDeepLinkHandler)

Schemequro://terminal/...

支持的路径

路径 说明
exec?cmd=... 执行命令
sessions 会话列表
create?name=... 创建会话
status 服务状态

使用示例

kotlin 复制代码
// 执行命令
val intent = Intent(Intent.ACTION_VIEW,
    Uri.parse("quro://terminal/exec?cmd=python3 --version"))
startActivity(intent)

// 创建会话
val intent = Intent(Intent.ACTION_VIEW,
    Uri.parse("quro://terminal/create?name=my-session"))
startActivity(intent)

7.3 Intent Handler(TerminalIntentHandler)

支持的 Action

Action Extra 说明
com.ai.assistance.quro.action.TERMINAL_EXEC command 执行命令
com.ai.assistance.quro.action.TERMINAL_STATUS --- 获取状态
com.ai.assistance.quro.action.TERMINAL_SESSIONS --- 列出会话
com.ai.assistance.quro.action.TERMINAL_CREATE_SESSION name 创建会话

使用示例

kotlin 复制代码
// 执行命令
val intent = Intent("com.ai.assistance.quro.action.TERMINAL_EXEC")
intent.putExtra("command", "ls -la /home")
sendBroadcast(intent)

7.4 BroadcastReceiver(TerminalBroadcastReceiver)

支持的 Action(6 个)

Action Extra 返回
TERMINAL_EXEC command output / exit_code / error
TERMINAL_STATUS --- running / session_count
TERMINAL_SESSIONS --- sessions (array)
TERMINAL_CREATE_SESSION name session_id
TERMINAL_DESTROY_SESSION session_id destroyed
TERMINAL_SEND_INPUT session_id / input sent

使用示例

kotlin 复制代码
// 发送广播并接收结果
val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val output = intent.getStringExtra("output")
        val exitCode = intent.getIntExtra("exit_code", -1)
        Log.d("Terminal", "Output: $output, Exit: $exitCode")
    }
}

registerReceiver(receiver, IntentFilter("com.ai.assistance.quro.action.TERMINAL_RESULT"))

val intent = Intent("com.ai.assistance.quro.action.TERMINAL_EXEC")
intent.putExtra("command", "echo hello")
sendBroadcast(intent)

7.5 对比与选型

方式 适用场景 优点 缺点
ContentProvider 数据查询、跨应用数据共享 标准 Android API,支持 CRUD 不适合长时间运行的命令
Deep Link 用户点击链接触发操作 直观,支持 URL 分享 不适合后台调用
Intent 应用间简单通信 简单,支持 extras 结果返回需要额外机制
BroadcastReceiver 异步通知、事件驱动 解耦,支持一对多 结果返回需要额外注册
ACI 复杂跨进程调用 12 个标准化能力,支持 AIDL/HTTP/MCP 需要绑定服务

8. 命令执行路由

8.1 环境自动检测

kotlin 复制代码
// QuroTerminalController.kt
fun runCommand(command: String, timeout: Long = 14000): String {
    val env = QuroLinuxEnv.getInstance(context)
    return if (env.isReady()) {
        // Linux 环境可用 → 使用 proot
        runCommandInLinux(command, timeout)
    } else {
        // 回退到设备 shell
        runCommandInDeviceShell(command, timeout)
    }
}

8.2 proot 命令构建

kotlin 复制代码
private fun runCommandInLinux(command: String, timeout: Long): String {
    val prootPath = "${applicationInfo.nativeLibraryDir}/libproot.so"
    val prootArgs = QuroLinuxEnv.getInstance(context).getProotArgs()
    // 直接使用 prootArgs + 命令,不重复添加参数
    val fullCommand = listOf(prootPath) + prootArgs + listOf("/bin/sh", "-c", command)

    val process = ProcessBuilder(fullCommand)
        .redirectErrorStream(true)
        .start()

    // 超时控制
    val completed = process.waitFor(timeout, TimeUnit.MILLISECONDS)
    if (!completed) {
        process.destroyForcibly()
        throw TimeoutException("命令执行超时: $command")
    }

    return process.inputStream.bufferedReader().readText()
}

8.3 超时与错误处理

kotlin 复制代码
// 超时控制
val completed = process.waitFor(timeout, TimeUnit.MILLISECONDS)
if (!completed) {
    process.destroyForcibly()
    throw TimeoutException("命令执行超时: $command")
}

// 错误处理
try {
    val result = runCommand(command, timeout)
    return mapOf(
        "output" to result,
        "exit_code" to 0,
        "error" to ""
    )
} catch (e: TimeoutException) {
    return mapOf(
        "output" to "",
        "exit_code" to -1,
        "error" to "命令执行超时"
    )
} catch (e: Exception) {
    return mapOf(
        "output" to "",
        "exit_code" to -1,
        "error" to e.message ?: "未知错误"
    )
}

9. 终端 UI 集成

9.1 入口方式

入口 说明
对话框输入框「+」 点击输入框左侧「+」按钮,选择「终端」
AI 调用 ui_open_terminal AI 在对话中主动打开终端界面
Deep Link quro://terminal/exec?cmd=... 直接启动
ACI 跨进程调用 其他应用通过 ACI 协议调用终端能力

9.2 会话切换

kotlin 复制代码
// QuroTerminalSessionManager.kt
// 创建新会话
val newSession = QuroTerminalSessionManager.createSession(context, "my-session")

// 切换到指定会话
val session = QuroTerminalSessionManager.getSession("session-id")

// 销毁会话
QuroTerminalSessionManager.destroySession("session-id")

9.3 输入输出渲染

终端 UI 使用 Jetpack Compose 实现:

kotlin 复制代码
@Composable
fun TerminalScreen() {
    val output = remember { mutableStateListOf<String>() }

    LaunchedEffect(Unit) {
        // 启动终端会话
        val session = QuroTerminalSessionManager.createSession(context) { line ->
            output.add(line)
        }
    }

    LazyColumn {
        items(output) { line ->
            Text(
                text = line,
                fontFamily = FontFamily.Monospace,
                fontSize = 12.sp
            )
        }
    }
}

10. 关键特性总结

特性 状态 说明
真实用户空间 Ubuntu 24.04 ARM64,完整的 Linux 工具链
PTY 伪终端 /dev/ptmx + fork/exec + TIOCSWINSZ
前台服务保活 specialUse 类型,息屏/切 App 不被杀
ACI 跨进程 12 个能力,AIDL/HTTP/MCP 三种调用方式
Intent/Provider ContentProvider + Deep Link + Intent + BroadcastReceiver
多会话支持 默认/额外/UI/历史会话,会话隔离
开机自启动 BOOT_COMPLETED 广播接收器
Android 14+ 兼容 specialUse + <property> 标签
proot 沙箱 无需 ROOT,link2symlink 符号链接
CMS 运行时 NODE/PYTHON/RUST/GO/JAVA 共享环境
超时控制 默认 14 秒超时,防止命令挂起
错误处理 完善的异常捕获和错误返回

11. 常见问题与故障排除

现象 说明 / 处理
终端息屏/切 App 后被杀 确认前台服务已启动:通知栏应显示「Zorv AI 终端运行中」。检查 AndroidManifest 中 foregroundServiceType="specialUse"<property> 标签
终端 ACI 跨进程调用失败 检查 QuroTerminalAciService 是否在 Manifest 中注册,权限 ai.aci.permission.CALL 是否声明
终端 Intent/Provider 不响应 检查 TerminalProviderTerminalBroadcastReceiverTerminalDeepLinkHandler 是否在 Manifest 中注册
终端会话状态不一致 QuroTerminalSessionManager 管理多会话,调用 listSessions() 获取真实状态
终端命令执行报 Illegal option -0 proot 参数重复问题,更新到 v1.0.67+ 已修复
应用内 Linux(L5)无法运行 首次进入终端会提示「安装 Linux 环境」,rootfs 需联网从 Ubuntu 官方镜像下载
命令执行超时 默认 14 秒超时,可通过 timeout 参数调整
会话输出乱码 检查 TERM 环境变量是否设置为 xterm-256color

12. 开发指南

12.1 环境准备

  • Android Studio:2024.1+ (Koala)
  • JDK:17+
  • Android SDK:compileSdk 36, minSdk 26, targetSdk 34
  • Gradle:8.13+
  • Kotlin:2.3+

12.2 从源码构建

bash 复制代码
# 克隆仓库
git clone https://github.com/Quor-a/ZorvAI.git
cd ZorvAI

# 构建 full release APK
./gradlew :app:assembleFullRelease

# 输出路径
# app/build/outputs/apk/full/release/app-full-release.apk

12.3 添加新的终端能力

步骤 1 :在 QuroTerminalAciService.ktonCreateCapabilities 中添加能力定义

kotlin 复制代码
override fun onCreateCapabilities(): List<AidlAciCapability> {
    val caps = super.onCreateCapabilities().toMutableList()
    caps.add(AidlAciCapability("my_new_capability", "我的新能力"))
    return caps
}

步骤 2 :在 onCall 中添加能力处理逻辑

kotlin 复制代码
override fun onCall(request: AidlAciRequest): AidlAciResponse {
    return when (request.capability) {
        "my_new_capability" -> handleMyNewCapability(request.params)
        // ... 其他能力
    }
}

12.4 测试建议

功能测试

  • 测试 exec 能力:执行简单命令、复杂命令、带超时的命令
  • 测试 list_sessions 能力:查看会话状态
  • 测试 help 能力:获取帮助信息

跨进程测试

  • 测试其他应用通过 ACI 调用终端
  • 测试 Intent/Provider/BroadcastReceiver 接入

前台服务测试

  • 测试息屏后服务是否持续运行
  • 测试切 App 后服务是否持续运行
  • 测试通知栏是否显示「终端运行中」

Linux 环境测试

  • 测试 proot 环境是否正常工作
  • 测试设备 shell 回退机制
  • 测试 rootfs 下载和解压

13. 开源信息

项目 信息
开源地址 github.com/Quor-a/Zorv...
ACI 开发者手册 docs/ACI_DEVELOPER_GUIDE.md
当前版本 v1.0.67
许可证 Apache License 2.0
技术栈 Kotlin 2.3 + Jetpack Compose
最低支持 Android 8.0 (API 26)
目标版本 Android 14 (API 34)

本文档由 Zorv AI 开发团队维护。如有问题或建议,请在 GitHub Issues 反馈。

相关推荐
其实防守也摸鱼31 分钟前
ZLibrary 类项目合规避坑指南:从技术实现到法律风险的全景梳理
运维·服务器·数据库·安全·自动化·github·copilot
今天AI了吗2 小时前
AI工作流的自动化趋势:从手动实验到自主Agent的研究范式转变
运维·数据库·人工智能·sql·机器学习·自动化·github
m4Rk_4 小时前
【论文阅读】Agent 记忆机制(53):Experience-Following——为什么错误经验会在记忆中不断传播
论文阅读·人工智能·学习·开源·github
峰向AI18 小时前
Superpower 不是最「聪明」的,这个开源项目让模型学会资深工程师的肌肉记忆
github
YuePeng18 小时前
卖了几年的 BI 模块突然免费:24 种图表,一条 SQL 一张图
github
dong_junshuai18 小时前
每天一个开源项目#82 9.2K星God's Eye View:公开信号3D地球
开源·github
简创AIGC陶先生19 小时前
第1章:项目全景与架构总览
github
怕浪猫19 小时前
ZCode 周末送额度活动开启:3 亿 Token 免费领取
github
P1Browser19 小时前
指纹浏览器安全吗?从浏览器环境隔离、数据存储到安全风险解析
网络·tcp/ip·安全·网络安全·github·php