原生启动 2.3 秒 · 容器启动 1.33 秒 · 108 条设备路由 · 1354 项权限声明
20 套工具链 · 真 clang / 真 JDK / 真 gradle / 双 rootfs
① 启动快 · ② 手机权限 · ③ 编译环境 · 能力清单 · 安装 · English · Deutsch
一位用户试用后的反馈,成了我们决定往哪个方向做深的依据:
「界面做得挺好,但没有编译环境和 root,能做的事还是有限。」
| 维度 | 我们的做法 |
|---|---|
| 原生启动 | 2.3 秒(低端 3 GB 设备也能跑起来) |
| Linux 容器启动 | 1.33 秒,容器层只占 0.8% |
| 手机能力接入 Agent | 108 条路由 / 42 条目录项 / 21 个独立开关 |
| 手机端编译 | clang + JDK 17 + gradle + cmake + rustc + go |
下面三部分,逐条拆解这三件事背后的实现。
把「快」和「能编」同时做好,是这件事里最难的一档。多数方案会先解决其中一个:
- 原生运行时快,但跑不了 glibc 程序,也没有
apk/apt; - 容器或 rootfs能编,但启动前要先解压、挂载、初始化软件源,慢十几秒。
我们两条路都做到了快,而且容器这一层几乎不带来额外开销。
整套 Node.js 运行时被编译成 Android 原生(bionic)二进制,直接打进 APK。它不需要「搭建」——没有 rootfs 要铺、没有伪文件系统要挂、没有软件源要初始化。冷启动只剩三件事:解压(仅首启一次)、用 execve 启动 Node、等待回环端口就绪。
| 场景 | 设备 | 冷启动到引擎就绪 |
|---|---|---|
| 真机实测 | 一加 15T(Android 16) | 2.3 秒 |
| 主流中端 | 各品牌 Android | 5 ~ 6 秒 |
| 极端低端 | 紫光展锐手表(3 GB 内存) | 13 秒 |
| 模拟器 | x86_64 / 2.5 GB | 2.2 ~ 2.7 秒 |
作为参照:基于 proot 的方案在中低端设备上容易受限于内存与启动开销,主流机型上启动通常在十几秒量级;首次手工搭建 Linux 环境则需要数分钟。
一台 3 GB 内存的手表能把整套引擎跑起来——同内存下,
proot+ glibc 连 rootfs 都铺不开。这不是调参调出来的,是架构差异。
从新版本开始,两条路径都注入了 Node.js 编译缓存(NODE_COMPILE_CACHE),把 JS 模块的编译产物落到磁盘,下次启动直接复用,省掉重复解析与编译。
干净 A/B 对照(同一模拟器、x86_64、各 3 轮、每轮清场):
| 状态 | 三轮结果 | 中位数 |
|---|---|---|
| 缓存命中 | 3652.2 / 3416.8 / 3385.7 ms | 3416.8 ms |
| 缓存清空 | 4540.2 / 4088.8 / 4528.4 ms | 4528.4 ms |
启动耗时减少 1111.6 ms,约 24.5%,缓存落盘体积为 15 MB。
有一个必须处理的细节:NODE_COMPILE_CACHE 是懒写盘的。不注入刷盘器时,跑完一整轮后,缓存目录也只有 16 KB——缓存等于不存在。所以我们内置了 node-compile-cache-flusher.cjs,在原生与容器两条路径中都注入它,让缓存真正落盘,达到 15 MB。
这是当前唯一还有正收益的启动优化项,已进入产线。
这是最反直觉的一条数据。
在自研容器 dsh_alpine 里,跑一条命令只要 2.4 ms;「从零 → 进 Linux → 引擎就绪」的端到端耗时中位数为 1.33 秒。
拆开看这 1.33 秒花在哪:
| 环节 | 占端到端耗时 |
|---|---|
| 容器层本身(路径翻译 + SIGSYS 处理 + ELF 加载) | 0.8% |
| 引擎插件加载(Node 侧) | 96% |
结论:容器这层几乎不花时间。 换任何 OS 级方案,瓶颈都在 Node 加载插件所占的那 96%,不在容器——而那 96% 恰恰是唯一无法绕过、也不该绕过的部分。
同一台设备(OPPO PLZ110 / Android 16 / 内核 6.12.38)横向对照:
| 方案 | 进 Linux 跑一条命令 |
|---|---|
我们的 dsh_alpine |
2.4 ms |
| 真 chroot(需 root) | 1.6 ms |
proot(免 root) |
19 ~ 33 ms |
我们只比需要 root 的真 chroot 慢约 1 ms(就是路径翻译与信号处理的成本),但比 proot 快 8 ~ 14 倍。
内存表现同样干净——跑引擎时与真 chroot 几乎等同(容器 180186 MB vs 真 187 MB),容器 loader 自身 RSS 仅 1.8 MB。文件 IO 比 chroot 181proot 快约 30 倍,syscall 快 2.7 倍。
换句话说:我们不是「在慢的基础上做优化」,而是把容器这一层做到了接近零开销。免 root,却有 root 级的性能。
| 决策 | 换来了什么 |
|---|---|
| bionic 原生运行时,不走 glibc、不套容器 | 省掉用户态翻译,省掉 rootfs 解压与挂载 |
| 容器用 seccomp + SIGSYS,不用 ptrace | 省掉 proot 的全量 syscall 拦截与用户态模拟 |
| 编译缓存落盘(两条路径都注入刷盘器) | 复用 JS 编译产物,再省约 1.1 秒 / 24.5% |
| 运行时预置进 APK(130.8 MB → 解压 418.1 MB / 15292 个文件) | 首启不用下载,装完即用,断网也能启动 |
targetSdk = 28(sideload 策略) |
豁免 Android 10+ 的 W^X,可直接 execve 私有目录二进制 |
CI 收集 + SONAME 去重 + SHA 校验,全部 .so 16 KB 页对齐 |
产物可复现;Android 15+ 的 16 KB 页内核直接可用 |
另有一条:前端注入 polyfill,把 WebView 门槛从 Chrome 126 拉回 85——国产 ROM 常见的 96 实测可用。
有一批同类项目在某些方面做得比我们好——界面更成熟、移植适配更广。我们也承认这一点。
而「Agent 到底能替你操作多少东西」,是这一层里更难被补上、我们投入最多的部分。
我们的上限是:只要 Android 肯给,Agent 就能用。
1354 项权限声明,真机实收 432 项。提供 108 条 HTTP 路由、42 条自描述目录项,配有 21 个独立能力开关——每项能力都要用户单独点头,不是一把梭全开。
| 层面 | 能力 |
|---|---|
| 看 | 无障碍节点树(含坐标 / 角色 / 可点性)、截图(不弹 MediaProjection 授权框)、OCR 兜底、帧差判断、屏幕变化监控 |
| 动 | 点按 / 长按 / 双击 / 滑动 / 拖拽 / 双指缩放 / 输入 / 按键 / 启动 App / 构造 Intent / 滚动查找 / 滚动收集 / 等待 / 条件分支 |
| 闭环 | 21 个动作原语组成的 PICK → ACT → VERIFY → RETRY 执行器;执行失败时会换锚点、换动作、换路径,并把失败证据(候选列表、截图、原因)交回决策层 |
| 看见它的手 | dev hand 在屏幕上画出 AI 即将点击的点:绿 = 点击 / 橙 = 长按 / 蓝 = 滑动,2.5 秒淡出,不吃触摸事件 |
| 感知 | 电量 / 温度 / 音量 / 内存 / 网络 / 前台应用 / 加速度 / 陀螺仪 / 光线 / 距离 / 计步 / 磁场 / 气压 |
| 上下文 | dev context 一次返回时间 + 屏幕 + 锁屏 + Doze + 电池 + 网络 + 前台应用 + 通知概况 + 该不该打扰 |
| 个人数据 | 日历 / 联系人 / 短信 / 通话记录(四项独立授权) |
| 通知 | 列表 / 聚合摘要 / 直接回复(RemoteInput) |
| 控制 | WiFi / 蓝牙 / 音量 / 亮度 / 手电筒 / 振动 / 媒体会话 / 定时提醒 / 地理围栏 / NFC 读标签 |
| 射频 | 蓝牙扫描 + 广播 + GATT + RFCOMM、手机当 BLE 外设、手机冒充蓝牙键盘、HCI 二进制日志解析、WiFi 链路与抓包 |
| 网络 | 按 App UID 查流量、按 UID 插防火墙规则断网(只断目标应用,不断自己) |
| 虚拟硬件 | 通过 /dev/uhid 注册真实的键盘 / 鼠标 / 多媒体键——系统分不出它和物理设备 |
| 系统层 | root 门脚本提供 19 个子命令:进程 / 文件 / 属性 / 服务 / 包管理 / 设置 / 网络 / 挂载 / 日志 / 任意命令 |
三档身份,从普通到 Root:
| 模式 | 引擎身份 | 能做什么 |
|---|---|---|
| 普通 | App 沙箱身份 | 工作区、文件、Shell、Node、PTY、插件、多数常规 Android 能力 |
| Shizuku | App 身份 + shell 桥 | dumpsys / cmd / pm / am、部分系统操作 |
| Root | uid 0 | /dev/uhid 虚拟输入、UID 级防火墙、Doze 白名单、系统级进程与文件、底层射频 |
Root 模式下的四条自律(不是能力限制,是我们自己划的线):
rm/exec/prop set/pm uninstall默认 dry-run,必须显式传confirm=1才执行- 关键进程保护:对
init/zygote/zygote64/system_server/surfaceflinger/servicemanager/logd/vold/netd的操作一律拒绝 rm不真删——移进隔离区/data/local/tmp/dsh-root-trash/<batch>/,root undo <batch>可恢复- 绝不修改 SELinux 策略:可执行代码里不存在
setenforce/supolicy/magiskpolicy/sepolicy-inject
| 现象 | 真因 |
|---|---|
| 虚拟 HID 在 App 进程里打不开 | /dev/uhid 只放行 u:r:ksu:s0;App 却是 u:r:untrusted_app。所以虚拟 HID 走引擎侧门脚本,不经桥 |
| 创建了设备却收不到按键 | 必须留 200 ms 等 InputDispatcher 注册;急着发出的按键会被丢弃 |
| 事件抓得到,系统不响应 | 屏幕必须 Awake。Dozing 下事件能到内核(getevent 看得见),但 InputDispatcher 不派发 |
| 音量键在 Consumer 页无效 | 两页 usage 号不通用:音量加在 Consumer 页是 233,在 Keyboard 页是 128 |
| 防火墙规则明明在链上,却读回 null | iptables -vL 打印的是 owner UID match 10360,不是命令行的 uid-owner——第一版按后者 grep,结果全空 |
断网后 ping 全丢包,判不出成败 |
ping 需要 ICMP raw socket,普通 uid 拿不到,有没有规则都 100% 丢包。必须读规则命中计数 |
dev # 列出 9 组能力目录
dev <能力> # 看某个能力的用法
dev <能力> k=v # 直接执行
dev api GET /dev/help # 原始目录 JSON(含 params / body / limits)
dev raw # 真实路由表(可核对目录是否漂移)dev 是纯渲染器,从服务端拉目录,不在脚本里硬编码能力名。MCP 工具表也从同一份目录派生——42 个工具,新增端点会自动出现在工具表里,不用维护第二份清单。
这是整件事里投入产出最不对称的一块,也是我们最想强调的。
20 套工具链,一键装,装完就能编。
| 分类 | 工具链 | 提供的二进制 |
|---|---|---|
| C / C++ | Clang / LLVM | clang clang++ make |
| JVM | OpenJDK 17 | java javac jar |
| Android 构建 | android-buildtools | aapt aapt2 apksigner gradle d8 |
| 构建系统 | CMake | cmake |
| Rust | Rust | rustc cargo |
| Go | Go | go gofmt |
| Python | Python | python3 pip3 |
| 其他语言 | Ruby / PHP / Lua / Perl | ruby gem php lua perl |
| 版本控制 | Git | git |
| 打包 | Archivers | zip unzip xz |
| 媒体 | FFmpeg / ImageMagick | ffmpeg ffprobe magick convert |
| 数据库 | SQLite | sqlite3 |
| 远程 | OpenSSH / ADB | ssh scp adb fastboot |
| 本地推理 | llama.cpp | llama-server llama-cli llama-embedding |
| 编辑 | Vim | vim |
① 原生 C / C++ —— clang + make + cmake,能编出 .so、能编 NDK 工程。
② Java / Android —— OpenJDK 17 + gradle + aapt2 + apksigner + d8。这是一条真能打出 APK 的完整编译链,我们用它在手机上编过本项目的扩展。
这条路有一个很容易踩的坑:android-buildtools 内嵌的 gradle 启动脚本在 Android 的 toybox 环境里是开箱即坏的——/system/bin/xargs 是 toybox 版本,不剥引号,会把 "-Xmx64m" 原样传给 JVM,报 Could not find or load main class "-Xmx64m";内嵌 JDK 还把 user.home 与 java.io.tmpdir 硬编码为某个外部环境的固定路径。我们在安装期打补丁修掉了这些问题(issue #86),现在开箱可用。
③ 更多语言 —— rustc / cargo、go、python、ruby、php、lua、perl,各自采用独立闭包安装,并进行 SHA-256 校验和原子发布。
第三条重点中,兼容性是最容易被忽略、却最实用的一层:
| 你要跑的 | 走哪条 |
|---|---|
| 常规 Node / Python / Go / Rust / C 工具链 | 原生 bionic(2.3 秒启动,本地工具链直接可用) |
| 只发布 glibc 构建的预编译 CLI | Linux 容器(Ubuntu rootfs,apt 装依赖) |
需要 apk add / apt install 装任意系统软件 |
Linux 容器(Alpine 或 Ubuntu) |
| 需要 jemalloc / libnuma 这类 glibc 专有库 | Linux 容器(glibc 2.39 完整用户态) |
两条路线共用同一套引擎代码、同一个 $DSH_HOME、同一份工作区。 切换路线不需要重装、不需要重新配置、不需要第二个 App。
把两条同时打通、还都保持秒级启动,是这件事最难的地方。我们把两条都做成了开箱可用。
可选加装,默认关闭。 这是一套自研的免 root 用户态容器:
| 特性 | 实现方式 |
|---|---|
不依赖 ptrace |
seccomp BPF 捕获指定 syscall + SIGSYS handler |
| 不依赖 user namespace | 固定基址 0x2000000000、ET_EXEC 非 PIE、自写 ELF loader |
不依赖 chroot |
用户态路径翻译,/proc /sys /dev 宿主透传 |
| 不要求 Root | 全程在 App 沙箱内运行 |
| 双向端口映射 | seccomp 同时改写 guest 与 host 侧端口 |
首次启动时,容器通过 self-reexec 完成 guest 用户态接管,之后运行的就是一个正常的 Linux 进程树。
两套 rootfs 任选:
| 发行版 | libc | 包管理器 | 来源 | 压缩 | 解压 |
|---|---|---|---|---|---|
| Alpine 3.22 | musl | apk |
APK 内置,离线可用 | 3,979,016 B | 8,510,313 B |
| Ubuntu 24.04 base | glibc 2.39 | apt |
首次联网下载 | 29,936,675 B | 100,784,109 B |
实测:容器启动 31 ms;Ubuntu 冷启 2.6 ~ 4.2 秒;Alpine 冷启 0.1 秒级。Ubuntu 比 Alpine 慢一个量级,是由发行版自身开销(glibc + init 脚本链)决定的,不是加载器缺陷。
注意:这不是安全沙箱。它保证兼容性和性能,不隔离不可信代码。
编完直接起本地服务,在手机内的 WebView 中预览:
「在当前工作区建个项目,编译它,起个开发服务器,把 127.0.0.1 预览链接发我。」
Agent 会真的执行:建目录 → 写代码 → make / gradle build → 起服务 → 给你回环链接。
不提供 X11 / Wayland 桌面,图形结果走本地 HTTP + WebView 预览。
先说清楚一件事:已经有好几个同类项目在某些方面做得比我们好——界面打磨更成熟、移植适配更广、生态更热闹。这不是客套,是我们判断自己该往哪补的依据。
下面这张表不是排名,是能力侧重对照。每一列都有它擅长的场景。
| 轻量聊天型移植 | 手工搭 Linux 环境 | proot 容器 |
本方案 | |
|---|---|---|---|---|
| 强项 | 上手快、界面轻 | 环境完整、可控性高 | 免 root 跑 Linux 的成熟方案 | 编译环境 + 手机能力同时具备 |
| 需要 Root | 否 | 否 | 否 | 否(要 Root 时有完整通道) |
| 需要额外 Linux 环境 | 否 | 是 | 常需要 | 否 |
| 原生启动 | 轻量场景更快 | 分钟级 | 十几秒起 | 2.3 秒 |
| 容器启动 | 不适用 | 分钟级 | 需完整 rootfs | 1.33 秒端到端(容器层 0.8%) |
| 免 root 容器性能 | — | 不适用 | 成熟可用 | 2.4 ms/命令,接近真 chroot |
| 编译缓存加速 | 无 | 无 | 无 | 有(约 24.5%) |
| 手机能力接入 Agent | 以界面为主 | 需自己写 | 需自己写 | 108 路由 / 42 目录 / 21 开关 |
| 手机端编译 C / C++ | 通常不做 | 有 | 有 | 有 |
| 手机端编译 Java / APK | 通常不做 | 部分 | 部分 | 有(JDK 17 + gradle + aapt2 + apksigner) |
| glibc 兼容 | 无 | 是 | 是 | 容器可选(Alpine + Ubuntu 双 rootfs) |
| Root 级能力 | 无 | 需外挂 | 需外挂 | 19 个子命令 + 四道自律 |
| 装完即用 | 是 | 否 | 否 | 是 |
我们的取舍是:不跟最好的那几个比谁界面更漂亮,而是把「真能干活」这一层补满。
如果你要的是尽快跑起来聊两句,轻量方案可能更合适;如果你要在手机上真的编译、真的操作设备,那才是我们擅长的场景。
以下是按分组列出的全部能力。前七组对应 GET /dev/help 的自描述目录(42 条目录项 / 36 个唯一路径),另附几个目录之外但真实存在的路由(/dev/help、/dev/perms/status、/dev/perms/revoke)。
| 端点 | 能力 |
|---|---|
GET /dev/status |
设备总览:电量 / 温度 / 音量 / 屏幕 / 内存 / 网络 / 前台应用 |
GET /dev/ping |
桥与无障碍是否在线(最快的一次健康检查) |
GET /dev/sensors |
传感器读数:type=accel|gyro|light|proximity|step|magnet|pressure |
GET /dev/notifications |
当前通知列表(需通知使用权),limit=20 |
GET /dev/media |
当前媒体会话:标题 / 艺术家 / 进度 / 播放状态 |
| 端点 | 能力 |
|---|---|
GET /dev/calendar |
查日程,from / to 为毫秒时间戳 |
GET /dev/contacts |
搜通讯录,q=<关键词> limit=20 |
GET /dev/sms |
读短信,q=<关键词> limit=20 |
GET /dev/calls |
通话记录,limit=20(需 READ_CALL_LOG) |
| 端点 | 能力 |
|---|---|
POST /dev/control |
op=wifi|bt|volume|brightness|torch|vibrate;WiFi / 蓝牙在 Android 10+ 会返回 user_confirmation_required |
POST /dev/clipboard |
剪贴板读写 |
POST /dev/media |
媒体控制:op=play|pause|toggle|next|previous|stop|seek|volume|stream-volume |
POST /dev/sms |
打开短信发送界面(不直接发送,由用户确认) |
POST /dev/notify-reply |
代用户回复可回复的通知(RemoteInput) |
POST /dev/calendar |
新增日程 |
| 端点 | 能力 |
|---|---|
POST /dev/remind |
建定时提醒,in_seconds= 或 at=<ISO8601>,repeat={"kind":"once|daily|weekday"};不依赖网络,重启后仍在 |
GET /dev/remind-list |
列出全部提醒 |
POST /dev/remind-cancel |
取消提醒 |
| 端点 | 能力 | 需 Root |
|---|---|---|
POST /dev/bt |
蓝牙底层:op=status|enable|disable|scan|advertise|connect|services|read|write|rfcomm|log;log 支持 format=binary 解析 HCI 抓包 |
部分 |
POST /dev/wifi-radio |
WiFi 底层:op=status|scan|enable|disable|link|capture |
抓包需 |
POST /dev/net |
按 App(UID)查流量 / 断网;查询不需 root,封锁需 | 封锁需 |
POST /dev/ble |
手机当 BLE 外设(GATT Server),别的设备连上来读写你开放的特征值 | |
POST /dev/bt-hid |
手机冒充蓝牙键盘控制别的设备(HID Device,部分 OEM 会拦) | |
POST /dev/pulse |
通过振动传递摩斯电码:不用亮屏、不出声就知道结果 | |
GET /dev/context |
上下文总线:时间 / 屏幕 / 锁屏 / Doze / 电池 / 网络 / 前台应用 / 通知概况 + should_nudge |
|
POST /dev/nudge |
主动提醒闸门:静音时段 / 息屏 / 锁屏 / Doze / 同一 key 的 30 分钟冷却,命中任一条件即拒,并回报具体原因 | |
POST /dev/hand |
「AI 的手」可视化:op=tap|path|clear|hide|status|debugbg |
|
GET /dev/notify-digest |
通知摘要:按 App 聚合计数 + 最近一条的时间(不返回正文) | |
POST /dev/scene |
传感器场景推断:dark / covered / outdoor / still / moving / walking / running / altitude_change | |
POST /dev/screenwatch |
屏幕变化检测:只回 changed_ratio / mean_delta / 变化网格,不存视频也不存图 |
|
POST /dev/doze |
电池优化白名单自助:op=add|remove|list,并回报是否真的生效 |
|
GET /dev/nfc |
NFC 硬件状态:硬件是否存在 / 系统是否开启 / 前台读标签是否已启动 | |
POST /dev/nfc |
NFC 前台读标签(NDEF 文本与原始字节);无刷写、无卡模拟 | |
POST /dev/geofence |
地理围栏:op=list|add|remove|clear |
| 端点 | 能力 |
|---|---|
POST /dev/dialog |
弹窗检测:用几何判据(底部成对按钮)找出系统弹窗与安全按钮 |
POST /dev/verify |
校验某个动作是否真的生效(前后屏幕对比),expect= / gone=true|false / timeout_ms= |
GET /dev/record |
录屏状态:是否拿到授权 / 是否正在录屏 / 已录多久 |
POST /dev/record |
屏幕录制;op=audio 时用 AudioPlaybackCaptureConfiguration 录制其他 App 的音频 |
GET /dev/hid |
虚拟 HID 设备状态:创建状态 / 设备类型 |
POST /dev/hid |
虚拟 HID 设备:注册真实外设键盘 / 鼠标 / 多媒体键并注入按键;op=status|create|key|tap|text|mouse|destroy |
| 端点 | 能力 |
|---|---|
GET /dev/perms |
权限总览:声明数 / 已获授权数 / 尚缺数(另有 GET /dev/perms/status 看进度详情) |
POST /dev/perms |
授予权限,body {"force":true} |
POST /dev/perms/revoke |
撤销指定权限,body {"perms":[...]} |
| 端点 | 能力 |
|---|---|
GET /diag |
桥自诊断:端口 / token / 无障碍 / 各能力开关 |
每项能力都要用户单独授权,默认全关。开关保存在 capabilities 偏好设置中,键名前缀为 cap.:
| 开关 | 覆盖能力 |
|---|---|
status |
设备总览、上下文总线 |
sensor |
传感器读数、场景推断 |
notify |
主动提醒(nudge) |
notify_read |
通知列表、通知摘要 |
notify_action |
代用户回复通知 |
clipboard |
剪贴板读写 |
control |
硬件开关、振动、Doze 白名单、弹窗检测、动作校验、「AI 的手」 |
wifi |
WiFi 底层 |
bt |
蓝牙底层、BLE 外设、蓝牙 HID |
sched |
定时提醒 |
media |
媒体会话读写 |
personal_calendar |
日历读写 |
personal_contacts |
通讯录 |
personal_sms |
短信读写 |
personal_calls |
通话记录 |
personal |
个人数据总开关 |
rf |
NFC、地理围栏 |
screen_capture |
屏幕变化监控、录屏 |
hid |
虚拟 HID 设备 |
net |
按 UID 查流量与断网 |
root |
系统级门脚本 |
门脚本是给 AI 直接调用的命令行入口,比 dev api 更省 token、更适合脚本化:
| 门脚本 | 用途 |
|---|---|
dev |
统一自描述入口:能力目录、用法、执行、直调 API、查路由表 |
scr |
屏幕脚本:dump / shot / tap / input / swipe / key / find / changed / batch |
loop |
本地闭环执行器:多步计划一次下发,PICK → ACT → VERIFY → RETRY |
fast |
无障碍操作期间的「思考开关」,临时关掉干扰 |
root |
系统级 19 个子命令:info ps kill cat ls rm undo cp mv chmod chown prop svc pm settings net mount journal exec |
hid |
虚拟 HID 一次性执行器:probe / create / tap / text / key / mouse |
mcp |
设备 MCP server(stdio JSON-RPC):serve / list / selftest / probe |
psx |
列进程 |
killx |
按 pid 或名称模式结束进程 |
say |
朗读文本 |
media |
控制当前正在播放的媒体 |
notify |
发通知 |
notify-reply |
代用户回复一条可回复的通知 |
remind |
定时提醒 |
bt-radio |
蓝牙应用层:扫描 / 广播 / GATT / RFCOMM |
wifi-radio |
WiFi 底层(基于 iw):扫描(含隐藏 SSID)/ 链路 / 抓包 |
device-control |
硬件开关 |
device-status |
一次性设备快照 |
device-ping |
最快的健康检查(桥 + 无障碍) |
dev-calendar / dev-clip / dev-contacts / dev-sms / dev-phone |
个人数据各端点 |
dev-dialog / dev-verify |
弹窗检测与动作校验 |
dev-geofence / dev-nfc |
地理围栏与 NFC |
dev-record / dev-sensors |
录屏与传感器 |
getperm / grantperm / revokeperm |
权限查询、授予、撤销(只读 / 需 Root) |
_dsh_http.sh |
公共库:用 bash 内建 /dev/tcp 实现 HTTP 客户端,不是独立入口 |
loop 执行器支持的完整动作集:
tap tap_text double_tap long_press swipe drag pinch input key launch intent scroll scroll_find scroll_collect set_switch collect repeat repeat_until back_to sleep noop
adb shell getprop ro.product.cpu.abiarm64-v8a → 手机包;x86_64 → 模拟器包。
| 包 | 体积 | 代价 | 适合 |
|---|---|---|---|
完整包(-release.apk) |
~161 MB | 运行时预置,装完即用、离线可启动 | 绝大多数人 |
简单包(-noruntime.apk) |
~34 MB | 首启联网下载 130.8 MB 运行时(解压后 418.1 MB) | 存储紧张 / 想独立更新运行时 |
| 源码构建 | 视参数 | 需 JDK 17 / SDK 36 / NDK / CMake / Node / Python | 开发者 |
两个包跑的是完全同一套引擎,差别只是运行时在 APK 里还是在网络上。
- 引导页选语言 / 朝向 / 主题
- 选运行模式:普通(不需要 Root)/ Shizuku / Root
- 等运行时就位(完整包已内置,只需首次解压;简单包此时联网下载)
- 配置模型 API Key 或登录模型服务
- 选工作区
第一次可以这样测:
「在当前工作区建个
hello.c,用 clang 编出来跑一下,把输出发我。」
到系统设置里开启 DSH Screen Tap 无障碍服务。Android 13+ 还要确认「允许完全控制设备」。App 不能静默开启这个权限,必须由你手动点。
设置里切到 Linux 模式,首次会部署 Alpine(内置,离线)或 Ubuntu(需下载约 30 MB)。两条路径共用同一份工作区与配置。
# 前置:JDK 17+、Android SDK 36、Node 22、Python 3
# 1) 收集运行时(拉 bionic 包 + 打 Android 适配补丁)
./scripts/build-runtime.sh --abi arm64-v8a --out dist/runtime-arm64-v8a.zip
# 2) 构建 APK
./gradlew :app:assembleDebug -Pabi=arm64-v8a -Ppackage=full \
-PruntimeZip="$PWD/dist/runtime-arm64-v8a.zip"
# 输出:app/build/outputs/apk/debug/app-debug.apkWindows Git Bash 用 gradle.bat 替代 ./gradlew。
不打包运行时(体积小,首启要下载):
./gradlew :app:assembleDebug -Pabi=arm64-v8a -Ppackage=noruntime运行时流水线细节见 docs/CI_RUNTIME_PIPELINE.md。
| 文档 | 内容 |
|---|---|
| docs/CAPABILITY_MAP.md | 手机能力全量盘点、权限档位、实现状态 |
| docs/ACCESSIBILITY.md | 无障碍控制、视觉兜底、失败恢复 |
| docs/CI_RUNTIME_PIPELINE.md | 运行时收集、去重、校验、发布 |
| docs/RUNTIME_SWAP.md | 运行时两阶段换装与混合态恢复 |
| docs/LAN_WEBUI.md | 局域网 WebUI 代理与验证码 |
| docs/SSH.md | SSH 远程工作区 |
| docs/android-agent-routes.zh.md | Android Agent 路线对比长文 |
| docs/RELEASE.md | 签名、构建、发布流程 |
不是端侧大模型。 模型不在 APK 里,走你配置的 API 或模型服务。
不是安全沙箱。 Linux Workspace 也一样——它保证兼容性和性能,不隔离不可信代码。Root 模式下 Agent 拥有整个设备的读写能力。
有些权限静默不了(系统不让,会返回 user_confirmation_required,绝不假装成功):无障碍服务、MediaProjection、Android 13+ 的 WiFi / 蓝牙开关、部分 OEM 的 HID Profile。
长任务不等于永不中断。 前台服务 + 监督器尽力保活,但用户强停、极端省电策略、厂商冻结后台(如 OplusHansManager)、设备重启、Root 操作杀进程都会断。断了要重新派发。
无障碍读不全所有界面。 纯 Canvas、游戏画面、部分受保护页面没有可用节点树,程序会按层降级(节点树 → OCR → 图标几何 → 帧差),但仍不能把所有图形界面变成可靠的结构化信息。
不该拿它做的事。 不要用虚拟 HID / 无障碍 / Root 绕过风控、欺骗用户、操作未授权设备。项目不提供让注入输入「不可检测」的实现。
需要 Root 吗? 不需要。普通模式已经能跑引擎、编译、读文件、用多数常规能力;Root 只是把系统级那一层也打开。
Linux Workspace 需要 Root 吗? 不需要。它用 seccomp 在 App 沙箱内加载 rootfs,不依赖 Root / ptrace / user namespace / chroot。
真的能在手机上编 APK 吗? 能。OpenJDK 17 + gradle + aapt2 + apksigner + d8 全套,装 android-buildtools 扩展即可(其中的 gradle 启动脚本我们已按 issue #86 修好)。
完整包 161 MB 装了什么? 整套 bionic Node 运行时——解压后 418.1 MB、15292 个文件。简单包只带清单,首启下载同一个 zip。
为什么不走 proot? 它多了一层用户态翻译,还要解压 rootfs,直接吃掉启动速度。我们用 seccomp 做同样的事,省掉 ptrace 那一层:只比需要 root 的真 chroot 慢约 1 ms,比 proot 快 8~14 倍。
Alpine 和 Ubuntu 选哪个? 默认 Alpine(小、离线、快);需要 glibc-only 的预编译程序或 apt 生态,就选 Ubuntu。
能力「存在」但 Agent 不知道? 五个条件必须全部满足:① 实现已注册进桥;② 已登记进能力目录;③ 能力开关已由用户授权;④ 环境说明书已升级;⑤ 引擎已重启。自检:
dev
dev api GET /dev/help
dev api GET /dev/routes怎么从电脑访问手机 WebUI? 在设置里开启局域网 WebUI,启动代理,配合 6 位一次性验证码访问。这暴露的是能操作文件、执行命令的 Agent,只在可信网络用,用完就关。
和手工搭 Linux 环境能一起用吗? 能。跑的是同一个引擎,差别只在运行层。
独立社区项目,与 DeepSeek 官方无隶属关系。
只在你拥有或明确获授权的设备上使用。普通 / Shizuku / Root 三档模式的系统边界完全不同;Root 模式下 Agent 生成的命令可能修改或删除系统文件、应用数据、网络规则、设备配置。
使用者自行承担以下风险及后果:数据丢失、应用或系统损坏、设备无法启动、账号或服务条款风险,以及 Root / Shizuku / 无障碍 / 自动化带来的后果。
模型服务商的 API 使用、数据处理、计费规则由你配置的服务端点决定。
- GitHub Issues
- QQ 群:1128769016
- Telegram
提 issue 请附:设备型号、Android 版本、CPU ABI、版本号、当前运行模式、相关权限状态、logcat、最小复现步骤、失败时的原始输出。
感谢所有提 Issue、提供复现日志、参与真机测试、提交 PR 的贡献者。完整名单见 Contributors。
也要感谢上游与生态:DeepSeek Harness、AOSP、bionic 工具链、Node.js、ripgrep、OpenSSH、SQLite、FFmpeg、llama.cpp。
除特别声明外,仓库内容(Kotlin、Java、XML、脚本、探针)均使用 MIT License。
app/src/main/cpp/dsh_alpine.c 及其运行时实现使用专有许可证,条款见 dsh-linux-runtime LICENSE。专有许可不等同于仓库根的 MIT。 第三方组件原始许可证见 NOTICE。
修改 dsh_alpine 时必须遵守其专有许可证,且不得引入 GPL / AGPL 或与本项目许可冲突的代码。
