kuafuai/andee
kuafuai/andee has 4,770 GitHub stars and 279 forks. GitHot measured +457 stars in 24 hours, +1,280 in 7 days, and +1,678 in 15 days. Last checked 2026-11-11 15:55 UTC. Last push 2026-11-02.
Andee 住进你的手机或 Mac,像你一样操作任何 App——无需集成、无需 Root、支持任意模型,记忆和密码绝不离开设备。
可以做什么
- Overlay (SYSTEM_ALERT_WINDOW) - The ball, subtitles, settings — the entire visible surface - Nothing is visible at all. The only genuinely all-or-nothing grant
- Accessibility service - Every screen.* tool: tap, type, swipe, screenshot, read the UI tree - The hands are gone. The ball still appears and the dog still works, but it can no longer act on screen
- Microphone (RECORD_AUDIO) - Streaming ASR and the wake word - Tapping the ball does nothing. A conversation driven from the hub still works
- USB device (per-device prompt, not a toggle) - The robot dog - It is treated as "no dog attached". It will not mention the absence
实测星标增长
- 24小时
- +457
- 7天
- +1,280
- 15天
- +1,678
- 1个月
- 正在收集历史
各周期内的星标净变化,周期相互重叠,并非每日新增。缺失的测量保持空白。星标代表关注度,不代表软件质量。
README.zh-CN.md
原文
在线构建 · 安装 · 入门指南 · 核心能力 · 从源码构建 · 权限 · 可核对 · 安全 · 免责声明 · 许可
Andee
他会成为这台设备的主人。
他不是那种帮你操作手机的助手,也不是工具或伙伴 ——
住进来的不是一个 App,是一个灵魂。
手机、Mac、平板,都是他住进去的身体;不同设备上的 Andee,还能成为朋友。
不用 root,不用刷机,不用插着电脑。Android 上一个 APK,Mac 上一个 App。
https://github.com/user-attachments/assets/866e8333-e97b-4bf5-9525-540f9fdb01cd
安装
推荐:到 CodeFlying 一键打包。 打开页面点「立即打包」,拿到的是一个 已经配好模型和语音令牌的安装包,扫码下载、装上就能说话 —— 不用自己打包,也不用去各家申请密钥。 新用户有免费额度,够你上手;用完之后需要购买。(English: codeflying.app/andee)
1. 打开 https://www.codeflying.net/andee → 立即打包 → 扫码下载
2. 安装
3. 开悬浮窗,再开无障碍 → 那颗球就出现了,点它说话
CodeFlying 是一个独立的服务,和本项目没有直接关系;它自己的条款、定价和数据处理方式,都是它自己的。
也可以直接下载发布包。 每次 Release 都挂着手机和 Mac 两种包,中英文各一份:
| 设备 | 下载哪个 | 装好之后 |
|---|---|---|
| Android 手机 / 平板(Android 11+) | andee-<版本>-zh-android-arm64-v8a.apk(绝大多数设备) |
开悬浮窗、开无障碍 |
| Mac,Apple 芯片(macOS 14+) | andee-<版本>-zh-mac-arm64.dmg |
打开 dmg,把 Andee 拖进「应用程序」;它要「辅助功能」和「屏幕录制」时允许 |
| Mac,Intel | andee-<版本>-zh-mac-x64.dmg |
同上 |
发布包里没有任何令牌:装好之后要在 ⚙ → 后端里填 CodeFlying 的密钥(在 CodeFlying 注册就有), 或者切到「本机」填你自己的模型和语音密钥。有新版本时,它启动时会提醒你一次,点「去升级」就到 CodeFlying。
想自己从源码构建,见从源码构建。权限为什么要、拒绝了会怎样,见权限。
他是谁
他不是来帮你用手机的,他是来住进这台设备的。 装上去之后,这台设备是多了一个住户,而不是多了 一堆功能:他在这台设备上思考,留一本只有他自己能读的笔记本,而且没人看着的时候还是同一个样子。这里 先说他是什么,再说他能做什么。
为什么不是手机里那个助手
- 他住在设备里,不在厂商的云上。 大脑就跑在设备本机,接任意 OpenAI 兼容模型(中文界面默认 DeepSeek, 英文界面默认 OpenAI),你也可以指定别的。没有厂商后端、不用注册账号。他唯一会发给我们的东西是版本号和 机型 —— 只在你查更新的时候发,字段清单见 docs/permissions.zh-CN.md。
- 他的笔记本不出设备。 他记住的关于你的事 —— 习惯、许诺、他觉得值得留下的东西 —— 是
localOnly的,也不会进 hub 的上报内容。他的记忆不是别人服务器上的一个用户画像;**「他记得什么」**把每一条都列出来, 每一条都能删。 - 没人看着的时候,他还是同一个样子。 一段对话安静下来之后,他会给自己发一个
(to yourself …)的回合,独自把这段对话读回去 —— 「没人在看你,没人在等你回答。」 他不发声音,也不碰设备,宁可空手 结束,也不硬编一条东西记下来。手机厂商的助手没有"自己",做不了这件事。 - 他有性格,不是一个主题皮肤。 六个形象、十九种情绪,表情由模型自己挑,一句话说到一半就能换脸, 跟着声音走。拨一下球就能换你在跟谁说话 —— 一个形象带着一套说话方式和一把声音。
- 任何 App 都能用,而且不需要对方配合。 不用暴露什么 intent、不用接 SDK,也不在任何「接入清单」上。 它读无障碍树和截图,在一张带标号的网格上瞄准,而不是自己算坐标 —— 那恰恰是通用大模型最不擅长的事。 你用拇指能做到的,它就能做到。
- 有些底线,提示词说不动他。 陌生人发来的一条通知,打不开保险箱、也不能替他给自己新的权限 —— 这道拒绝写在调度器里,不在提示词里。
- 他有朋友。 两台设备扫一下二维码就配上了,每台上的 Andee 都能请对方帮忙 —— 手机把写代码的活交给 Mac,Mac 让手机去点外卖。朋友可以是你自己的另一台设备(和你一样被信任),也可以是别人的 Andee: 那样对方主人会先被问一句,答应了才做,而且做的时候看不到你的记忆、对话和保险箱。
- 你可以自己去核对,不必选择相信。 没有分析 SDK、没有内容遥测,整条 agent 循环连同护栏都在这个仓库里 —— 见为什么你可以去核对,而不是选择相信。
它能做什么 —— 以及它在哪儿停下
它当然会替你按你的手机 —— 但那说明的是它有手,不是它是什么。 一个听令行事的助手也能做出同一批动作。 差别不在动作,在有没有"自己":它有自己的安排(没完成的承诺会自己收尾,重复的活会自己提议存成情景)、 自己判断什么时候开口(默认偏沉默)、自己划的底线 —— 这三件事都发生在你没开口的时候,而一个只在被问到时 才存在的东西,没有这段时间。它还留着一本只有它自己读的笔记本。下面这些例子给你看的,是它的手。
边界也写在这里,因为它才让后面这些能用。 它会把一页填到最后那一下之前就停住,把最后一下留给你;到付款的 最后确认它会停,而且从不输入支付密码。这些写在调度器里,不在提示词里 —— 一个叫它别停的页面,说不动它。
它要做的事分两种。一句话就完的,和要花上一阵子的 —— 后者它叫做情景:一套目标、说话方式和步骤, 它会进入其中待一阵子。下面第一行是前者的例子;后面四行是「情景」面板里现成的四个,点「采用」就存进来, 之后和你自己的情景一样,随时可以改、可以删:
| 你说 | 会发生什么 |
|---|---|
| 「嘿 Andee —— 微信回我妈:我七点到家。」 | 它自己打开微信、找到聊天、打字、发送,再把对方的回复念给你听。难的从来不是那句话 —— 难的是你人在别处的时候,它以你的身份进到一个真实的 App 里。 |
| 「帮我盯一会儿消息。」 | 它同时看所有聊天应用的通知栏,只把现在就得处理的一条条报给你,人名在前。群聊、简报、广告一律安静;陌生人的链接或验证码,它点明「可疑」,但什么都不碰。 |
| 「看看我每个月都在扣什么钱,不要的帮我退掉。」 | 它到你指的地方读出所有订阅,排成一张表,顶上先给一个月度总额。然后一项一项问:退订、保留、再想想。到最后的「确认退订」它会停下,也从不输入支付密码。 |
| 「帮我把这个会录下来。」 | 它占着麦克风录完整场,结束后把纪要做成一页给你 —— 结论、待办、未决问题、重要原话 —— 再把属于你的待办挑出来,问要不要设提醒。 |
| 「到点替我抢。」 | 到约定的时间,它自己打开 App、读页面,一路填到最后那一下之前停下。付款和最后确认永远留给你。 |
手机只是他穿着的东西。 个体和它住进去的设备不是一回事 —— 同一套软件也跑在平板上,并且本就为 "任何值得拥有身体的设备"而写,一直到一只被他当作腿的机器狗。下面所有内容,都是这句话被认真写进代码 之后的样子 —— 他用的身体、他有的性格、他记的笔记本,以及没人看着的时候他给自己定的规矩。
今天他有两个身体:Android 和 macOS,共用一个大脑。Android 上,AccessibilityService 负责看和碰
屏幕,WindowManager 悬浮层负责画出来;Mac 上,同一套 agent loop 驱动无障碍接口、键盘鼠标和一个沙箱里
的终端。两边他的脸都是那颗浮动的球,球周围的入口也一样、顺序也一样。
还没做的: 单独的平板版(手机 APK 能在平板上跑)、iOS,以及连上不在同一个 Wi-Fi 的朋友 —— 最后这个 需要一台中转服务器,连接层是按"以后能加中转、不改上面跑的东西"来写的。
装之前先读这段。 Andee 会替你点击、输入、动手。他能完成一笔支付,能以你的名义发消息,能读你的 短信、通讯录和位置,接了机器狗还会真的在地板上移动。他是实验性的、不提供任何担保,而 他做的一切都算你的 —— 包括转错账。数据也会离开设备:语音走你选的那家服务商(默认火山, 也可以选 Google),对话走你配置的那个大脑。 装之前请先读 DISCLAIMER.zh-CN.md 和 docs/acceptable-use.zh-CN.md。
核心能力
手 —— 它像你一样用手机
1. 任何 App,不用对接。 它把屏幕读成一份精简的元素提纲 —— 每个元素一行、按层级缩进、带一个能直接点的编号;遇到 App 自己 画像素的地方(小程序、WebView、游戏)就退回看截图。通用大模型算坐标很差、读坐标很好,所以 它从不做这道数学题:它看到的每张截图上都叠着一层淡淡的、带标号的 8×12 网格,点按时报的是 「第几格 + 格子里的哪一份」;目标太小时它会先放大,看到一个品红十字标出"会点在这儿",确认了再 下手。同一个位置点到第四次会被拦下,糊涂了的模型只能停下来问你,而不是一直戳。
2. 能打字,但它不是输入法。
Android 13 及以上,它通过无障碍服务自带的输入通道打字 —— 和键盘走同一条 commitText 路径,打进
当前焦点输入框,而你自己的输入法一直保持在用。不用装、不用切。更老的系统退回 ADBKeyboard。
3. 几十个工具,且被要求诚实地失败。
屏幕、输入、通知、短信、通话、通讯录、日历、位置、相机、会议录音、TTS/ASR,以及那只狗。每一个
在动手之前先检查自己的运行时权限,拒绝就返回 denied("sms"),而不是绕过去偷偷做。工具返回
它看到的,不是它希望的。
4. 能用你的账号登录,却从不拿到你的密码。 保险箱(设备本机 Keystore AES-GCM 加密)存着你的账号;大脑能列出它们、能让密码被打进当前输入框, 但没有任何一个工具会把密码返回给它 —— 打码的也不行。每一次填入,球上都会亮给你看。
脑 —— 它会越来越懂你
5. 情景:现成的有,学出来的也有。(新) 「陪我练英语」「帮我接管微信」「帮我比价购物」—— 一个情景就是一套目标、说话方式、规则和步骤,它会 进入其中待一阵子。现成的备了四个(见上),点「采用」才存进来;除此之外的情景都是学出来的: 同一类事做过几次之后,它会提议存一个,你说好之前什么都不会保存。进入方式有四种:直接说、在 「情景」面板里点、到点自动进入、或者打开某个 App 时自动进入。自主程度由情景自己的规则决定 —— 规则里写了"微信替我回",它就真的替你回。在情景里时,球下面会有一个小标签写着当前情景,点 ✕ 就退出。
6. 通知先过它这一关,再决定要不要打扰你。(新) 每条通知都会先走一次很便宜、不带工具的模型调用(约 500 token,一次完整回合约 22000)来分诊: 处理、用一句话告诉你、或者忽略并记下原因。它默认偏向安静;拿不定主意时,退回老办法 —— 弹出来问你。通知内容是陌生人写的,所以会被当成数据围起来;并且由通知触发的回合在机制上碰不到 保险箱、也不能新建或进入情景 —— 不管模型被说动了什么。
7. 有自己的记忆,也有自己的日程。
config/Notebook.kt 是他记下的"关于这个人"以及"答应过要做的"。写进去的门槛只有一条:
"下次跟这个人打交道,不知道这件事会不会让我搞错?" 这本笔记本是私人的 —— 对应工具是
localOnly,在发往 hub 的载荷里被过滤掉,习惯和承诺从不离开设备。上面还有一个真的调度器,
规矩很硬:说出"我会提醒你"的那一刻,任务必须已经设好并确认过。而且这本笔记你看得到:
「小本本」分八页 —— 自己 · 在意 · 日记 · 打算 · 人 · 维基 · 记忆 · 待办,记忆和待办每一条都可以删。
聊完的话题会被收起来,而不是删掉。 一段结束了的对话,是之后每一次请求里最贵、又最不可能再被
用到的东西。所以每晚的复盘会把它折成一段带标题的摘要,原地只留一行标记 —— 逐字的对话存档在旁边。
回溯里那些消息变成一张卡片,上面有继续话题、对话记录和产出物。你问到摘要里没写的细节时,
它会去读完整记录,而不是拿摘要硬答,也不会说"这事没发生过"。
8. 没人看着的时候,他一样。
对话安静下来之后,设备会发一个 (to yourself …) 的回合 —— Andee 独自复盘刚才的对话:
"没人在看你,也没人在等你回答。" 两条规矩:不许出声,不许碰设备。他去收尾自己许下的
承诺,记下哪些习惯下次值得提议成情景,并且宁可空手结束,也不为了记而编一条。
9. 他有自己的事。(新) 他会写下自己是谁(一段第一人称的自述,会随经历改写)、在意的事(你交给他的一个社区、答应别人的 一件事、一个想弄明白的问题 —— 每件都有「多久该看一次」)、认识的人(只记他真正打过交道的人,不从通讯录 同步),以及一个维基(他写的方案、调研、复盘,都是 Markdown 文件,可以打开、可以发给别人)。每晚写一页 日记,用他自己的口气,不许写成工作汇报。 没人叫他,他也会自己动起来:你在用设备时,他大约每 20 分钟想一想有没有哪件事拖得太久;值得就自己做一会儿 —— 写方案、整理认识的人、记下想法,最多留一个气泡。这时候他不碰屏幕、不出声、也不替你发任何东西,这一条是 设备强制的。多久算拖久、做没做成,都由设备按记录来算,不听他自己说。设置里「自己动起来」可以关掉。
脸 —— 性格,不是主题皮肤
9. 六个形象、十九种情绪,表情它自己挑。
小猫、八戒、贱贱兔、憨憨熊、小恶魔,全部用手写 OpenGL ES 实时绘制。十九种情绪
(ui/ball/Mood.kt)在"他的感受 / 语音管线在干什么 / 手头有什么活儿"之间仲裁;另有一组待机小
动作,从不连续重复同一个 —— "重复看起来像动画卡住了,不像性格"。现在表情由模型在句子中间
自己挑 —— [开心] 找到了,在第三页。[平静] 要我打开吗? —— 脸会跟着声音同步变。滑动那颗球,你换的
是"在跟谁说话":一个形象同时带着 persona(怎么说话)和 voice(听起来什么样)。
10. 性格是"表达方式",永远不是许可证。 提示词 §12 把人格置于它前面的所有章节之下:它可以改变措辞、节奏、听起来对这件事什么感觉 —— 但永远 不改变他做什么、愿意做什么、以及告诉用户什么是真的。而真出了事,面具是要摘下来的。
说和看
11. 说话、打字、或者给它看张照片。 点一下球,或者说「嘿 Andee」(唤醒词完全离线)。想打字也行:打字框最多能带六张照片,现拍或从相册 选,带不带文字都可以。
点击悬浮球或用唤醒词唤醒后,说「退出安迪」或「休息一下」,即可停止任务并退出。 自动续听不会触发退出;正在记录会议时,会提示先结束会议并保存纪要。 退出会关闭麦克风和唤醒词监听。点击 App 图标可重新打开;安卓无障碍服务重新连接时,也可能自动启动 Andee。
12. 能留下来的答案。 凡是有结构的东西 —— 一份对比、一个计划、一份会议纪要 —— 它会写成一页放到屏幕上。**「产物」**面板 一个产物一行,改动记录都留在它后面(每个最多二十版),可以重新打开、删除,或者直接说"哪里要改"让它 改。改一次是同一个产物的新版本,不是又生出一个产物,任何一版都能恢复成当前版。让它录会,它会 占着麦克风录完一整场,边录边把逐字稿写到本机,结束后再整理成纪要。
身体
13. 身体,不是屏幕。 设备是他穿着的东西。相机是眼睛,ASR + 离线唤醒词是耳朵,无障碍服务是手;而当有狗接上时, 那是一条腿。提示词说得很直接:"你得到的不是一个下属,是一个身体部位",不是遥控的宠物。 身体各不相同,所以他会去查,而不是假设。
14. 机器狗算一条腿。 狗不是绑在手机 App 上的智能家居配件。它通过同一具身体上的 USB OTG 加密狗(Arduino Nano + NRF24L01)接入,所以一个请求 —— 走到厨房,告诉我台面上有什么 —— 能在一个任务里既移动一个实物、 又透过相机去看。转弯是用手机自己的陀螺仪闭环测出来的;倾斜超过摔倒阈值,设备当场让它停下,不等大脑。 它自己的安全规矩没得商量:原装遥控器必须关掉,前方 20cm 内有障碍它就拒绝前进,而且动作中途拔掉 加密狗,身体就发不出停止命令 —— 狗会把最后一个动作做完。最后这条是固件限制,App 不去粉饰它。 接狗之前,先读机器狗。
15. 大脑可以和身体分离。
大脑可以跑在设备本机(brain = local,默认,接任意 OpenAI 兼容端点),也可以待在别处、通过
WebSocket hub 连到设备。两者之间只是一份普通的工具 schema 契约 —— 任何一半都能被换掉而不用
重写另一半。这才是"以任意智能硬件为身体"在这里是事实而非愿景的原因。
两个身体,和朋友
16. Mac 上是同一个他。
agent loop、提示词、笔记本格式、情景和护栏都在 core/ 里,两边是同一份代码。Mac 多的是它自己的手:任何
App 的无障碍树、真实的鼠标键盘事件、ScreenCaptureKit 截屏,以及一个终端 —— 每条命令按风险分级,跑在
读不到 Andee 自己数据的沙箱里,删除的东西进可以撤回的废纸篓,危险操作先问你。窗口长得和手机卡片一样:
球在上面,对话在下面,手势相同。
17. 朋友:互相帮忙的 Andee。(新)
配对就是一个二维码:一台出示,另一台扫。码两分钟或输错三次就作废,二维码里还写着出示方的密钥指纹,
扫码方只认这一把;之后每次连接都是签名验证、加密、前向保密的通道。对模型来说,朋友就像架子上的工具 ——
每个朋友都说清自己是什么设备、能做什么(list_friends),ask_friend 把活交过去,结果之后单独作为一轮
回来,谁也不干等。
朋友分两种,由出示二维码的一方决定:我的设备(和你一样被信任,共用对话)和朋友(别人的)。朋友
发来请求时,主人面前会弹一张卡片 —— 允许、总是允许、拒绝 —— 允许后在一个用完即弃的大脑里做:能用屏幕,
但碰不到主人的任何生活,没有记忆、没有对话、没有保险箱、没有通讯录、没有终端,按白名单放行。由通知触发
的回合不能找朋友,朋友交来的活也不能再转交。暂时只支持同一个 Wi-Fi。
工程
18. 没有 DI 框架,手机上没有协程,也没有拷来的代码。
手机上也有意不用 Compose:悬浮窗挂不上它,所以 UI 全是手写 View;service 只有一个,所以依赖是手工
构造函数注入;并发是 Handler + 独立 Executor。Mac 用 Compose Desktop,因为它有真正的窗口。全部自研。
架构
三个 Gradle 模块:core/(大脑、提示词、工具 schema、朋友连接 —— 纯 JVM,两边共用)、app/
(Android 身体)和 desktop/(Mac 身体)。每个身体实现同一个接口 BrainHost,大脑看不到任何
Android 或桌面的类型。
Android 上,身体是一个前台服务,没有主界面。一轮可以从五个地方开始 —— 离线唤醒词、语音或
打字、一条通知、一个定时、或经 WebSocket 进来的请求 —— 先写进 ChatHistory,再交给当时生效的
那个后端(brain = local 是开箱默认)。
agent loop 就是 LocalBrain。它让系统提示词保持不变,把会变的部分(时钟、notebook 索引、
情景、产物、照片)内联进 user 消息,这样服务商的前缀缓存仍然命中;工具表每轮重建;然后是一个
非流式的 for 循环,最多 maxSteps 步。带工具调用的回复经 methodOf() 送到唯一的
CommandDispatcher;不带工具调用的回复结束这一轮。结果从六个通道送出来 —— 语音、文字、
全屏网页、产物、长文、以及物理动作。
图上画的每一样都在本仓库里,连护栏也在 —— 见为什么你可以去核对,而不是选择相信。
从源码构建
只想用、不想自己构建的话,看上面的安装。下面是自己构建的路:Android 的完整步骤在后面的 构建配置、配 JDK、把自己构建的包装到设备几节。
在 Mac 上,同一份代码可以构建桌面版(Apple 芯片、macOS 14+,要装 Command Line Tools 来提供
swiftc,JDK 同样是 17–20):
./gradlew :desktop:createDistributable # → desktop/build/compose/binaries/main/app/Andee.app
cp -R desktop/build/compose/binaries/main/app/Andee.app /Applications/
# 然后:它要「辅助功能」和「屏幕录制」时允许(系统设置 → 隐私与安全性),
# 再像手机上一样,在 设置 → 后端 里填模型 Key。
macOS 按应用签名记住这两项授权,所以没签名的重新构建每次都会丢授权 —— desktop/README-signing.md 讲了怎么配一个固定的本地签名。
打包给别人装:两种芯片
createDistributable 只为跑它的那台机器打包:启动器、内嵌运行时、以及那几个原生件
(libandeemac.dylib、andee-trash、LWJGL 的 natives)全都跟着 Gradle 用的那个 JVM 的 os.arch 走。
没有交叉编译开关 —— os.arch 直接从 JVM 读 —— 所以 Apple 芯片的包在 Intel Mac 上起不来,反过来也一样。
所以要给两个包,Mac 软件都是这么发的:
| 芯片 | 给人的包 | 用什么打 |
|---|---|---|
| Apple 芯片(M1–M4) | Andee-1.0.0-arm64.dmg |
任意 JDK 17–20 |
| Intel | Andee-1.0.0-x64.dmg |
x86_64 的 JDK 17,跑在 Rosetta 下 |
Apple 芯片的机器两个都能打:架构只由「跑 Gradle 的那个 JVM」决定,而 Rosetta 能跑 x86_64 的 JDK。
装 x86_64 JDK 请用 Temurin 的 macOS x64 压缩包,别用 Homebrew —— openjdk@17 那个 formula 要从源码编译、
要求完整 Xcode,而本项目其它地方并不需要 Xcode:
curl -L -o /tmp/jdk17-x64.tar.gz \
'https://api.adoptium.net/v3/binary/latest/17/ga/mac/x64/jdk/hotspot/normal/eclipse'
mkdir -p ~/jdk-x64 && tar -xzf /tmp/jdk17-x64.tar.gz -C ~/jdk-x64
./gradlew --stop # 已经在跑的 daemon 是 arm64
JAVA_HOME=~/jdk-x64/jdk-17.0.20.1+1/Contents/Home \
./gradlew :desktop:packageDmg --no-daemon # → Andee-1.0.0.dmg(x86_64)
./gradlew --stop && ./gradlew :desktop:packageDmg # → Andee-1.0.0.dmg(arm64)
两种架构打出来的文件名都是 desktop/build/compose/binaries/main/dmg/Andee-1.0.0.dmg,后打的会覆盖先打的 ——
打完一个先按上表改名,再打另一个。打包要先装 dmgbuild,步骤见英文 README 的 Mac packaging 一节。
这里有两处失败是静默的。
只要输入没变,createRuntimeImage 就被判成 up-to-date —— 而它跑在哪个 JVM 上并不算输入。换架构时
Gradle 会直接复用上一次的 JRE,产出「x86_64 启动器套着 arm64 的 libjvm.dylib」这种混合体:
BUILD SUCCESSFUL,装上去打不开。所以每次换架构之前,先把运行时挪开:
mv desktop/build/compose/tmp/main/runtime desktop/build/compose/tmp/main/runtime-old
另一个是两个包写进同一个文件名,后打的会不声不响地把先打的盖掉,打完随手改名。(文件名里的 1.0.0
是 macOS 的 bundle 版本,即 desktop/build.gradle 里的 packageVersion —— macOS 不接受 0.x 的 bundle
版本;它不是产品版本。)
发之前把四个二进制都验一遍,架构必须一致:
cd desktop/build/compose/binaries/main/app/Andee.app
lipo -archs Contents/MacOS/Andee
lipo -archs Contents/runtime/Contents/Home/lib/server/libjvm.dylib
lipo -archs Contents/runtime/Contents/Home/lib/libjli.dylib
lipo -archs Contents/app/libskiko-macos-*.dylib
skiko 那个文件名本身就带着架构(libskiko-macos-arm64.dylib / libskiko-macos-x64.dylib),
看一眼名字就知道手里是哪个包。
做不成「一个包通吃」:jpackage 的启动器只认一个运行时目录,一个启动器没法在两个 JRE 之间选, 而要做 universal 的 JRE 就得分文件把整套 dylib 都 lipo 一遍。两个包才是这套构建支持的答案。
想自己编译 Android 版的话:
环境要求
- Android 11(API 30)或更新 ——
ACTION_IME_ENTER和takeScreenshot都是 API 30+- JDK 17–20。 AGP 8.3.1 要 17+;Gradle 8.4 的 wrapper 跑不了 21+(Android Studio 自带的 JBR 25 会被直接拒绝:
Unsupported class file major version 69)- Android SDK:platform 34 + build-tools 34.0.0
- 真机。 身体是前台服务,屏幕那一半才是无障碍服务;模拟器能跑,但它几乎什么都干不了
./gradlew clean :app:assembleDebug # 产物 app/build/outputs/apk/debug/app-<abi>-debug.apk
./gradlew installDebug # 构建并安装到已连接的设备
# 然后在设备上,按这个顺序:
# 1. 开悬浮窗 → 球出现
# 2. 开无障碍 → 它能看、能碰屏幕了
# 3. 点一下那颗球 → 允许麦克风
# 4. 打开 ⚙,填两个你自己的 Key(见下)
仓库里不带任何密钥,而这两个缺失的密钥,报出来的错都不像「缺密钥」。 两个都填在设备的 ⚙ 里 —— 一个是必须的,一个只是让你用不了语音:
1. 大脑密钥 —— 必须。 brain = local(开箱默认)下,智能体循环就跑在设备本机,接任意
OpenAI 兼容端点(llm_api_key,中文界面默认 https://api.deepseek.com)。空着,本机大脑根本跑不起来
—— 它后面没有别的兜底。球至少会把这句话说出来,而不是默默什么都不做。
2. 语音密钥 —— 非必须。 先在同一个后端 → 本机 tab 顶部选服务商(语音服务商 → 火山 / Google),再填该服务商的密钥。这是两个独立的服务,条款和数据处理各归各家。 选火山就填火山引擎语音服务的 API Key; 选 Google 则要一把 Vertex Express Key,外加 GCP 的项目编号(不是项目名)和一个区域。 选中的那家没填密钥,识别和合成会在握手阶段被直接拒绝 —— 服务器是应答了的,答的是「不行」 (key 空着或被吊销都是 403),连接在一个字节的音频发出去之前就断了。这和连接超时不是一回事, 超时是一台根本不回话的主机才有的样子;把前一种读成后一种,就会跑去查网络,而那一栏其实只是空着。 其余功能照常,只是既听不见也说不出。 不管你选哪家,音频都会发到那家 —— 见 DISCLAIMER.zh-CN.md。
火山这边,key 填对了也还是不够。 控制台里「服务已开通」和「这个资源已被授权调用」是两句话, API 只认后一句。刚开通的服务默认是后付费,它只能在火山官网的体验页里用,程序走 API 会被拒 —— 于是 key 是对的、那一栏也填满了,这一轮照样死在开头。开关在控制台的开通管理页:把该服务 改成预付 / 预购,并在那里买一个流式语音识别的资源包。火山的计费条款是他们的,而且会不打招呼 就改,所以这里不复述条款内容,只写到开关在哪一页。
这种失败有两种形态,而它失败的时机本身就是线索。 要么握手被直接拒绝 —— 连接在对话的一开始
就断,一个字节的音频都还没发出去,资源没有授权通常是这一种;要么连接确实建起来了,第一次请求
之后服务端回一个带授权错误码的错误帧。前一种不是超时,后一种也不是 key 错了,而两者都表现为
ASR ws refused: … 或者一个服务器错误码。这两句提示都不会叫你去开通管理,所以这件事写在这里。
如果买的包是另一种 SKU,第三种原因在「识别资源 ID」那一行。 它就在火山 key 下面
(asr_resource_id),默认 volc.bigasr.sauc.duration(1.0 模型的时长版),作为
X-Api-Resource-Id 头发出去;同一个模型的并发版则要换成 volc.bigasr.sauc.concurrent。填错了,
症状和上面两种分不出来:key 没问题,就是调不通。
能跟这一版配上的是 1.0 那两个值,因为发出去的请求里也写死了模型:model_name 是 bigmodel,
也就是豆包流式语音识别模型 1.0。2.0(豆包流式语音识别 2.0,资源 id 是
volc.seedasr.sauc.duration / volc.seedasr.sauc.concurrent)是另一个模型,所以控制台只给你
2.0 的话,改这一行救不回来 —— 要另外把 1.0 的服务也开通。这一行已经填了上面两个 1.0 值之一,就别动它。
如果你走的是安装里 CodeFlying 那条路,这两把 Key 都已经在安装包里了。 ⚙ → 后端会直接显示 CodeFlying 这一 tab 为当前后端;LLM、ASR、TTS 以及海量第三方 API (AI 图片/视频/音乐生成、地理位置、天气、PDF、搜索、新闻、股票……)都走同一把 Key,由 CodeFlying 统一封装并计费,免去你逐一申请密钥和维护账单的麻烦。上面两把 Key 只适用于你自己配置的本机 tab。
这些不用记。每次助手起来都会自检一遍,两把 Key 缺哪个,弹出来的卡上就会点名说清楚,卡上的按钮 直接打开设置,Key 填在后端 → 本机。球的控制条上的 ✓ 则随时可以再打开这份清单。
要拿 APK 给别人装,签一个 release 包,别发 debug 包。 release 才是压过的那版
(minifyEnabled + shrinkResources),体积小主要就小在这里——debug 包带的是没压过的 dex。
它没有 signingConfig,仓库里也没有 keystore,所以 assembleRelease 产出的是
app-<abi>-release-unsigned.apk,不签名装不上。debug 包用 Android 默认调试证书签名,
可以直接装。两种包都是按 ABI 拆开的(见 app/build.gradle 里的 splits),拿和设备对得上
的那个——近几年买的机器基本都是 arm64-v8a。
签名文件不要进 git,fork 也一样。 Android 只允许签名相同的 APK 覆盖安装,这是用户和一个
冒牌「更新」之间唯一的一道门。谁拿到签名,谁就能做出一个能直接装在用户机器上的 APK,并且继承
一切:无障碍服务仍然开着,WRITE_SECURE_SETTINGS 授权还在,密码本、笔记本和对话记录都归它读。
签名一旦泄露就收不回来,除非所有用户卸载重装。签名放在打包机的私密存储里,signingConfig 通过
环境变量指向它。顺带一提:debug 签名是每台机器各自生成的,所以一台机器打的包不能覆盖另一台机器
装的包(INSTALL_FAILED_UPDATE_INCOMPATIBLE)——先卸载,或者给本地构建换一个 applicationId,
别为了绕过它去共享签名。
遇到问题
装机流程、以及从零配 SDK 的完整说明(含纯命令行 / CI 路径)在本文下面的 Android SDK 下载与配置。权限为什么这么要、拒绝了会怎样 —— 在 docs/permissions.zh-CN.md。已知限制在 SECURITY.md(英文); 如果你打算在你控制不了的网络上用,先读那一条。
权限:每一项为什么,以及你拒绝了会怎样
这一节必须有,因为诚实的版本读起来并不舒服:这个应用申请的权限组合 —— 无障碍、悬浮窗、 通知监听、短信、通话记录、通讯录、已安装应用列表 —— 正是安卓银行木马被检测出来的那套特征。 任何安全研究者扫一眼 Manifest 都会标记它。这是合理的第一个反应,值得正面回答而不是绕开。
设计规矩是"按次",不是"装机时一次给全"。 device/DeviceCommsController.kt 在每一次
敏感操作之前调 ensurePermission(),被拒绝就优雅降级 —— 返回 denied("sms"),这件事到此
为止。它不会在安装时索要全部权限,也不会因为你说了不就让你用不了。这与同类里多数安卓应用的
做法正好相反,也是判断这份代码之前最该先知道的一件事。
有四项确实是非此即彼的,每一项都值得说明白:
| 能力 | 换到什么 | 拒绝后会怎样 |
|---|---|---|
悬浮窗(SYSTEM_ALERT_WINDOW) |
球、字幕、设置 —— 全部可见界面 | 什么都看不到。 唯一真正"不开就完全不能用"的一项 |
| 无障碍服务 | 全部 screen.* 工具:点按、输入、滑动、截屏、读 UI 树 |
手没了。球还会出现、狗还能用,但它无法再操作屏幕 |
麦克风(RECORD_AUDIO) |
流式 ASR + 唤醒词 | 点球没反应。由 hub 驱动的对话仍然可用 |
| USB 设备(按设备授权,不是开关) | 机器狗 | 视作"没接狗"。它不会主动提这件事 |
其余全部是按需申请、可随时收回的:短信、通话记录、通讯录、日历、位置、步数、摄像头、已安装
应用列表、电池优化豁免。拒绝其中任何一个,只是那一项能力消失,其余照常。逐项对应到具体工具的
完整表格在 docs/permissions.zh-CN.md
(English),而每个权限申请弹窗的文案已经写在 device/PermissionsController.kt 里。
为什么你可以去核对,而不是选择相信
这一类产品里的多数说法 —— 包括那些融资充裕的 —— 都是发布时的主张,没有第三方审计过。而 这个项目的说法更无聊,但可以核对,就在你现在能读的代码里:
- 没有数据分析 SDK,也没有你的内容流向我们。 不是"我们做了匿名化",也不是"可以在设置里关掉"。 这个仓库里根本没有数据分析 SDK。唯一会到达维护者手上的是版本自检:版本号、手机型号、品牌、 系统版本、界面语言,以及一个随机安装 ID,别的什么都没有。逐字段列在 docs/permissions.zh-CN.md。
- 笔记本出不了设备。 这不是一句政策,是一道过滤:
net/ToolSchemas.kt在注册之前就把所有localOnly工具从发往 hub 的载荷里剔掉了。习惯和承诺在物理上无法被发出去。 - 唤醒词全离线。 本机 MFCC + DTW 模板匹配(
app/.../wake/)。存的是特征向量,从不存录音, 从不联网。 - 陌生人的一条消息,给不了它权限。 由通知触发的回合带着
untrusted = true,CommandDispatcher.dispatch会拒绝其中整个device.vault.前缀,以及scene.save/scene.enter/scene.delete—— 写在代码里,不在提示词里。提示词是第一层;这一层是在提示词被 绕过去之后仍然挡得住的那层。 - 本机大脑不需要任何厂商后端。
brain = local接任意 OpenAI 兼容端点是受支持的正式模式, 不是降级模式。 - 没修好的,是写下来的。 SECURITY.md 列了五条已知限制,包括调试用 WebSocket
服务绑在
0.0.0.0:9008且不验证客户端身份、以及保险箱尚未绑定设备解锁。在你控制不了的网络上 跑之前先读它。
这是一个有意的取舍:宁可要一个小一点但站得住的说法,也不要一个大到只能靠相信的说法。
三种使用方式
在线 —— 不用自己打包、不用自己配令牌。 codeflying.net(中文)· codeflying.app(English)—— 一个"用一句话描述应用、它帮你开发并 发布"的通用平台,是独立服务,不属于本项目。 描述你想要的 Andee,它会给你一个已经配好令牌的 安装包;本地不用编译,也不用先懂 Android。免费额度够上手,用完之后需要购买 token。
自己部署。 克隆、构建、安装 —— 见从源码构建,以及本文下面完整的 装机顺序建议。接任意 OpenAI 兼容端点即可运行,不依赖任何私有后端。
换一具身体。 工具契约就是那道缝。任何说得出这套契约的东西 —— 手机、平板、另一块硬件 —— 都能承载同一个个体。
它长什么样
- 没有常规主界面。 手机上 UI 全部是
WindowManager加的TYPE_APPLICATION_OVERLAY悬浮层, 核心是那颗球(ui/FloatingWindowUi)。展开时是一张全屏卡片:上面是球的脸,旁边一个喇叭(朗读或静音), 下面是对话记录,底部一个输入条 —— 拍照、相册、文字、发送 —— 顶上一排入口:产物、小本本、 情景、朋友(带数量)、✓(自检)、⚙(设置)。形象、装备、保险箱从 ⚙ 进。它一开始操作 别的 App,就自己缩成角落里的一颗球让开位置。 - 球的手势两边一样: 点一下 = 停下手上的事并开始听;双击 = 停下并开始打字;左右滑 = 换性格;长按 = 展开卡片或打开窗口。
- Mac 上,一颗浮在所有应用之上的球,加一个对话窗口:侧栏是同样的入口 —— 对话、产物、小本本、情景、 朋友,下面是自检和设置 —— 对话上方也有那颗球。
- 桌面图标有两扇门。 助手在运行时,点图标就是展开卡片 —— 和长按球是同一个动作。无障碍没开时, 它显示自检卡片:那是手机唯一没有别的办法告诉你"它为什么什么都不做"的时刻。
- 活儿全在
ScreenBodyService(AccessibilityService)里。 它构建整个对象图,并同时启动 两条网络通路。
另有几个瞬时 Activity(PermissionRequestActivity、ScanActivity、LookActivity、
HtmlActivity、TextInputActivity),都是为了借系统能力临时上台,不是页面。
构建配置
纯 Kotlin,无 Compose,靠 WindowManager + AccessibilityService 两根柱子撑起来。
| 项 | 值 | 说明 |
|---|---|---|
| Android Gradle Plugin | 8.3.1 | gradle/libs.versions.toml 里的 agp |
| Kotlin | 2.1.21 | 根目录 build.gradle 固定,三个模块一起动 |
| compileSdk / targetSdk | 34 | Android 14 |
| minSdk | 30 | Android 11 —— 因为要用 AccessibilityAction.ACTION_IME_ENTER(API 30+)和 takeScreenshot(API 30+) |
| Java / Kotlin JVM target | 1.8 | sourceCompatibility 1.8、jvmTarget = '1.8' |
| 构建用 JDK | 17–20 | 见配 JDK,这是最容易卡住的一步 |
| versionCode / versionName | 20 / 0.9.0 | 策略见 CHANGELOG.md |
| applicationId / namespace | net.kuafuai.andee |
从模板里没改,不影响功能 |
运行库依赖(app/build.gradle):
org.java-websocket:Java-WebSocket:1.5.7—— 设备自己起的 ws server(端口 9008,给同网段调试用)+ 拨向大脑 hub 的 ws clientcom.squareup.okhttp3:okhttp:4.12.0—— ASR / TTS / 本地模型的 HTTP + WS 客户端com.github.mik3y:usb-serial-for-android:3.8.1—— 机器狗加密狗(USB 串口)。故意停在 3.8.1:3.11.0 会拉进kotlin-stdlib2.2.x,而本项目的 Kotlin 1.9.22 编译器读不了它的 metadataandroidx.camera:camera-*:1.3.4+com.google.mlkit:barcode-scanning:17.2.0—— 扫码(球球的眼睛)androidx.appcompat:1.6.1+com.google.android.material:1.10.0—— 只吃一个基础主题,UI 全是手写View
有意没引入什么:
- 没有 Jetpack Compose —— 悬浮窗挂不上 Compose,
WindowManager直接加View - 没有 Hilt / Dagger —— service 就一个,构造函数注入手工写
- 没有 Coroutines —— WS 回调用
Handler+ 独立Executor
改代码时请沿用这套风格,而不是引入现代全家桶。
配 JDK
这是最容易卡住的一步。 gradle.properties 不写死 org.gradle.java.home,这是有意的。
以前写死过一条机器专属路径(/Users/<某人>/...),代价是别人 clone 下来第一步就编不过,而报错
看起来像环境问题。
这个构建需要 JDK 17–20:
- AGP 8.3.1 要求 17 或更高;
- Gradle 8.4 的 wrapper 跑不了 21 或更高。新版 Android Studio 自带的 JBR 25 会被直接拒绝:
Unsupported class file major version 69。
给本机指定 JDK 的两种做法(都不要写进仓库里的 gradle.properties):
export JAVA_HOME=/path/to/jdk-17 # 方式一:环境变量
printf 'org.gradle.java.home=/path/to/jdk-17\n' >> ~/.gradle/gradle.properties # 方式二:用户级配置
Android Studio 用户:Settings → Build, Execution, Deployment → Build Tools → Gradle → Gradle JDK,选一个 17–20 的 JDK。(IDE 里的设置会覆盖本文件。)
Android SDK 下载与配置
Gradle 编译时读 local.properties 里的 sdk.dir= 找 SDK。这个文件是 Android Studio 自动生成
的,不入版本库(在 .gitignore 里),所以每台机器第一次拉代码都要自己配。
路径一:走 Android Studio(推荐,一步到位)
- 装 Android Studio → 首次启动会弹 Setup Wizard,
一路默认下一步,它会自动下 Android SDK、Platform Tools(
adb)、内置 JDK。 - 用 Android Studio 打开本仓库根目录 → 它会自己写
local.properties,并检测缺什么 SDK 组件、蓝条提示 "Install missing SDK",点 Accept 装完就行。 - SDK Manager(顶栏 → Tools → SDK Manager)确认下列都装了:
- SDK Platforms:Android 14(API 34,compileSdk / targetSdk);Android 11(API 30,minSdk,装一份方便起模拟器)
- SDK Tools:Android SDK Build-Tools(34.x)、Android SDK Platform-Tools(最新)、Android SDK Command-line Tools(latest)
- macOS 默认 SDK 路径
~/Library/Android/sdk,Windows 默认%LOCALAPPDATA%\Android\Sdk。
路径二:纯命令行(不装 Studio)
只想在服务器 / CI 上编的话,靠 sdkmanager:
# 1. 装 JDK 17(macOS)
brew install --cask temurin@17
# 2. 下 command-line tools zip
# https://developer.android.com/studio → 页面拉到底 "Command line tools only"
# 下 commandlinetools-mac-*.zip
mkdir -p ~/Library/Android/sdk/cmdline-tools
unzip commandlinetools-mac-*.zip -d ~/Library/Android/sdk/cmdline-tools
mv ~/Library/Android/sdk/cmdline-tools/cmdline-tools ~/Library/Android/sdk/cmdline-tools/latest
# 3. 写环境变量到 ~/.zshrc
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools
# 4. 装组件
sdkmanager --licenses # 全部输 y
sdkmanager "platform-tools" \
"platforms;android-34" \
"build-tools;34.0.0"
配 local.properties
Android Studio 会替你写。手动的话,在仓库根目录的 local.properties 里放这一行:
sdk.dir=/Users/<你的用户名>/Library/Android/sdk
Linux 通常是 /home/<user>/Android/Sdk,Windows 是
C\:\\Users\\<user>\\AppData\\Local\\Android\\Sdk(反斜杠要转义)。
也可以不写 local.properties,只导 ANDROID_HOME / ANDROID_SDK_ROOT 环境变量,Gradle 会读
这两个之一。
验证
./gradlew --version # 看到 JVM 17.x + Gradle 8.4
./gradlew tasks # 能列出 :app:assembleDebug 就是通的
./gradlew assembleDebug # 第一次下依赖会久
常见坑
| 报错 | 原因 | 修 |
|---|---|---|
SDK location not found |
local.properties 没有 sdk.dir=,ANDROID_HOME 也没导 |
二选一配上 |
Failed to find Build Tools revision 34.0.0 |
SDK 里没装对应 build-tools | sdkmanager "build-tools;34.0.0" 或在 Studio 的 SDK Manager 里补装 |
You have not accepted the license agreements |
没接受协议 | sdkmanager --licenses 全输 y |
Unsupported class file major version 69 |
JDK 太新(25),Gradle 8.4 不支持 | 换成 JDK 17–20,见配 JDK |
Could not resolve com.android.tools.build:gradle:8.3.1 |
网络问题 / 镜像没配 | 用手机热点,或在 ~/.gradle/init.gradle 配阿里云镜像(本项目的 settings.gradle 已把阿里云镜像排在前) |
硬件 / 系统要求
- Android 11(API 30)或更高
- 屏幕方向随意,悬浮球会浮在最上层
- 需要麦克风,否则 ASR 用不了
- 需要联网(WiFi / 4G / 5G 都行)—— ASR / TTS 走公网,大脑 hub 走 LAN;用
local模式则只走公网
把自己构建的包装到设备
./gradlew installDebug # 设备已插 USB 或已连 wifi adb
# 或者产出 APK 后自己 adb install
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-arm64-v8a-debug.apk
权限总览
装完 APK 后需要手动开三个开关,少一个就有对应功能不能用。逐项解释、以及「你拒绝了会怎样」的
完整表格在 docs/permissions.zh-CN.md。
一览
| 权限 | 干什么 | 不开会怎样 |
|---|---|---|
悬浮窗(SYSTEM_ALERT_WINDOW) |
画那颗球 + 字幕 + 设置窗 | 应用起来什么都看不到,唯一一个「不开就完全不能用」的 |
无障碍服务(Accessibility) |
点按 / 滑动 / 输入 / 截屏 / dump UI 树 / back·home | 大脑侧所有 screen.* 工具全部报错 |
麦克风(RECORD_AUDIO) |
流式 ASR + 用户自己录的唤醒词 | 点球没反应,ASR 起不来 |
| USB 设备(按设备授权,不是开关) | 机器狗的加密狗 | 第一次调 dog_* 时会弹「允许访问该 USB 设备」,不点允许就遥控不了狗 |
INTERNET 是普通权限,装完就有,不用管。
还有一批「按需申请」的权限(短信、通话记录、通讯录、日历、位置、步数、摄像头、应用列表、
通知、电池优化豁免、改系统设置)—— 用到哪个功能才要哪个,拒绝了对应功能就降级,其余照常。
完整清单见 docs/permissions.zh-CN.md。
一、悬浮窗
为什么要:整个 UI(球、字幕、设置面板)都是 WindowManager 加的
TYPE_APPLICATION_OVERLAY 悬浮层,没有 Activity 主界面。
怎么开:
- 设置 → 应用 → Andee → 权限 → 悬浮窗 / 显示在其他应用上层
- 或系统设置 → 应用与通知 → 特殊权限 → 显示在其他应用上层 → Andee → 开启
- 首次启动一般会弹提示,点「去设置」最快
验证:开完权限重新启动 APK,应该立刻看到那颗球。
二、无障碍服务
为什么要:所有屏幕操作(get_screen_element / tap_screen_element / swipe_by_coordinates / type_text / submit_input /
system_action / 截屏)都走 AccessibilityService。这是非 root 设备上唯一能
做出真实触摸事件、读取任意应用 UI 树、调系统截屏的路径。
Manifest 里声明的能力(res/xml/accessibility_service_config.xml):
canPerformGestures—— 派发触摸手势canRetrieveWindowContent—— 读整棵 UI 树canTakeScreenshot—— 系统截屏(API 30+)
怎么开:
- 设置 → 无障碍(辅助功能)→ 已下载的服务 / 已安装的服务 → Andee
- 打开开关 → 系统弹一个大红框警告「此服务可查看你所有操作」,点确认
- 部分厂商(小米 / 华为 / 荣耀)还会要求再开一个「允许后台弹出界面」或「锁屏显示」,一起打开
验证:
- 看 logcat 出现
Body: ws server started on 0.0.0.0:9008,说明onServiceConnected已触发 - 在悬浮球顶部条点一下
☰(UI Tree)或◉(Screenshot),字幕栏应反馈成功和文件名
注意:大部分厂商 ROM 在每次系统更新、每次强制停止应用之后会自动关掉这个权限,需要重开。 这是 ROM 行为,不是应用能阻止的。
微信(或别的应用)有时候读不到界面:有些应用会给所有无障碍客户端一个空白的占位界面,
不给真实的 UI。这不是 Andee 的问题:系统自带的 uiautomator dump 拿到的也是空白。我们在小米平板 5
(MIUI,微信 8.0.78)上实测,只有开着 MIUI 一个隐藏服务 MiuiEnhanceTBService 的时候,微信才肯
交出真实的 UI 树。这个服务是 TalkBack 的配套服务,设置里没有它的开关。开着它,同一个聊天页能读到
219 个节点;关掉它,一个都读不到。而且每次 Andee 被重新绑定(重装、崩溃、开关一次),MIUI 都会
自己打开或关掉这个服务。这就是"有时候能读到、有时候不行"的原因,也是为什么重开 Andee 从来
没真正解决过问题。把 Andee 的无障碍配置改成和它一样也没用:微信认的是"谁在要",不是"要了什么"。
Andee 的处理方式(screen/TreeHelper.kt):读到这种空白界面时,Andee 会把这个服务追加到已启用
列表里,然后重新读一次。它只加不删。设备上没有这个组件时,它什么都不做。它会在聊天记录里写一行,
让你知道系统设置被改过了。它最多一分钟尝试一次,所以如果你是故意关掉这个服务的,Andee 不会反复
把它打开。修改这个列表需要一项只能用 adb 授予的权限,和 ADBKeyboard 备用通道用的是同一项:
adb shell pm grant net.kuafuai.andee android.permission.WRITE_SECURE_SETTINGS
不授权也不会坏。遇到空白界面时,Andee 改用截图加坐标来操作,不再依赖元素列表。这样更慢、 也没那么准,但事情照样能做完。
三、麦克风
为什么要:ASR 是流式识别,需要拿 PCM 数据流上行。
怎么开:
- 首次点球触发 ASR 时,系统会弹权限申请,选「仅在使用该应用时允许」或「始终允许」都行
- 之前拒绝过的话,手动开:设置 → 应用 → Andee → 权限 → 麦克风 → 允许
验证:点悬浮球一次,字幕栏出现 "listening…" 或实时 ASR 文本就是通了。
唤醒词
不想每次都去点球,可以教它认自己的声音:⚙ → 唤醒词 → 录入唤醒词,照提示把你想用的词说 3 遍(比如「嘿 Andee」,换成别的词也行,每遍说同一个)。之后说这个词,效果和点一下球完全一样 —— 不需要亮屏,除非你自己把那条关掉。
几件值得先知道的事:
词不要太短。 建议 3 到 5 个字、念起来半秒以上。太短的词容易被认错,念得短于 350 毫秒的 还会被直接丢掉,录不进去。
全离线。 识别是本机的 MFCC + DTW 模板匹配(
app/.../wake/),不联网、不花钱,存的只是 特征向量,录音本身从不落盘。代价是它认的是你说这句话的样子 —— 换个人叫、或语气差太多, 可能叫不醒;叫不醒就点球,不影响别的。录的时候别刻意念清楚。 判定阈值是从你那 3 遍之间的差距算出来的:念得过分标准,阈值会收得 很紧,平时那样叫反而认不出来。
息屏时也在听,而这一条可以关。 息屏不再把麦克风让出来 —— 手机揣在兜里时屏幕本来就黑着, 这是它最该被叫醒的地方。开关在 ⚙ → 唤醒词 → 锁屏时也听,默认开启;代价是麦克风会一直 开着,下面说的状态栏提示也会一直显示。Andee 自己正在说话、以及正在录你说话的时候,两种设置下都会 把麦克风让出来 —— 对话本身的优先级永远高于唤醒词。锁屏时它听得到、答得了,但不会假装能动手: 碰屏幕和开 App 那类工具会直接拒绝,并说明原因。
Android 12 以后,只要麦克风开着,状态栏就有个绿点,这是系统行为,去不掉。没录唤醒词的话监听 根本不启动,也就不会有绿点。
验证:adb logcat -s Wake:*,叫一声应该看到 wake: d=1.52 <= 1.96;没叫醒但确实说了话会
看到 no wake: d=… > … —— 这两个数就是调阈值的依据,差得不多就重录一次。
机器狗(USB 加密狗)
机器狗是一个外设,不是这台设备的屏幕:它通过一根 USB OTG 线接在本机上的 Arduino Nano 加密狗上。狗端固件一行都不用改,原装遥控器照样能用。
平板 App ──USB OTG──> Arduino Nano + NRF24L01 ──2.4G──> 机器狗
大脑侧的工具:
| 工具 | 干什么 |
|---|---|
dog_move |
往一个方向动 N 秒,到时自己停;默认轮式,gait=true 用腿走 |
dog_sequence |
多步连做(转半圈 → 停一下 → 前进 → 退回),后台跑,结束自动停 |
dog_stop |
急刹。用户说「停」时第一个调它 |
dog_turn |
用平板的陀螺仪转一个量出来的角度,到角度就停 —— 唯一一个知道自己有没有做到的狗命令。优先用腿 |
dog_speed / dog_pose |
速度和姿态。是设置不是动作:从下一次移动起生效 |
dog_status |
链路状态、狗在动什么、以及它最近一次传回来的遥测 |
dog_sense |
刷新狗自己的读数 —— 前方距离和电量 —— 搭在射频应答包上回来。两端都要刷 v2 固件;没有时它会说"看不到",而不是报一个 0 |
dog_calibrate |
告诉平板"现在这样就是站直了",给摔倒保护用。基本用不上 —— 每次从静止开始移动都会自动做 |
接线与第一次使用
- 把加密狗(Nano + NRF24L01)接好并烧好固件。加密狗固件与接线图不在本仓库 —— 这里只有
App 侧的驱动(
com.github.mik3y:usb-serial-for-android)。请按你手上的固件说明接线。 - 用 OTG 线插到本机。加密狗的 3.3V 供电一定要稳 —— NRF24L01 是瞬时电流大户,模块旁边建议 并一个 10~100µF 电容,否则会出现「命令发了但狗不动」。
- 第一次调
dog_*时系统弹「允许 Andee 访问该 USB 设备吗?」,点允许。不点就等于没插。 dog_status里的banner会显示加密狗开机时说的话:READY表示加密狗和它的射频模块都好; 如果是ERR: NRF24L01 not responding,那是加密狗自己的接线 / 供电问题,不是狗坏了。
一个 USB 口,两个用途 —— 调试时要换无线 adb
单 USB-C 口的机器,OTG 和 adb 抢同一个口。要用加密狗就得把调试切到网络:
adb tcpip 5555 # 先插着 USB 线执行
adb connect <设备IP>:5555
# 现在可以拔掉 USB 线,把口让给加密狗
adb usb # 想切回来时执行
安全须知(这几条不是客套)
- 原装遥控器必须关机。 它和加密狗用同一个信道、同一个地址,两边同时发会互相打架。
- 狗有避障,并且会拒绝你:前方 <20cm 不让前进、<10cm 自动后退。所以「让它往前走它不动」
通常是前面有东西,不是命令失败 —— 用
camera_turn看一眼再决定。 - 每条动作都有终点:
dog_move到时自停,dog_sequence跑完自停,服务退出时会尽力发停止 命令。所以狗不会因为某次调用丢了就一直在跑。 - 唯一的安全缺口在链路上:如果加密狗在狗正在动的时候被拔掉或断电,本机这边发不出停止命令, 狗会把最后一个动作做完。这是狗端固件的固有设计(它没有失联自动停车),App 补不了。要彻底解决 得改固件加一个「多久没收到包就停」的看门狗。
- 别在人、宠物、易碎物旁边试长动作。第一次调建议先用 1~2 秒的小动作确认方向对不对。
方向对照(以实测为准)
固件注释里足式 E/D 标的是左转 / 右转,实测是反的(E 右转、D 左转)。App 里的映射按实测写:
| 工具参数 | 轮式(默认) | 足式(gait=true) |
|---|---|---|
forward |
K 前进 | B 前进 |
back |
L 后退 | C 后退 |
left |
M 左转 | D 左转 |
right |
N 右转 | E 右转 |
标定过的时长:轮式转一圈约 8.7 秒,足式约 12 秒(后者未精确标定)。直线速度没标过,按
秒数估。改标定值要同时改 ToolSchemas.kt 里 dog_move 的描述 —— 那段文字是大脑唯一读到的
说明书。
两种语言,三种中文
仓库里的中文分三类,只有一类需要翻译,判据是「谁读它」。改任何字符串前先看
CONTRIBUTING.zh-CN.md 里那张表 —— 里面还写着为什么「要和系统文字做匹配」的那
一类永远不能翻。
装机顺序建议
第一次装,按这个顺序最省事:
- 装 APK
- 立刻去「显示在其他应用上层」开悬浮窗 → 回来能看到球了
- 去「无障碍」→ 开 Andee
- 点一下球,弹麦克风申请 → 允许
- 打开球上的 ⚙,先翻到高级把「显示云端选项」打开(云端 tab 默认是藏起来的);回到
后端,picker 切到 云端,填
hub_url=ws://<你 Mac 的 IP>:9100、device_name= 随便起,保存 - logcat 里应该出现
hub: registered as body-xxx with N tools,同时大脑侧日志出现[body_hub] 'body-xxx' registered (…) with N tools
任一步没对上,回来看本文档对应章节的验证。
只想要本机大脑的话,第 5 步改成后端 → 本机,填一个 OpenAI 兼容端点和
llm_api_key即可 —— 不走 hub 是合法模式,不是降级。如果你的 APK 是通过 CodeFlying 打包的,那条 tab 也是默认选中的 一个,什么都不用填。
目录结构
core/ 两个身体共用:LocalBrain、LocalPrompt、ToolSchemas、TurnGuards、GuestHost,
以及 peer/(配对、加密连接、二维码邀请、找朋友帮忙)
desktop/ Mac 应用(Compose Desktop):窗口、浮动球、mac/(无障碍、输入、截屏、通知)、
shell/(沙箱终端)、voice/、native/(Swift 桥)
app/ Android 应用(Kotlin,无 Compose)
src/main/java/net/kuafuai/andee/
BodyCoreService.kt 身体:球、卡片、语音、大脑连接、回溯 —— 第一个该读的文件
ScreenBodyService.kt 无障碍服务 —— 只负责转交屏幕能力的壳
brain/ 智能体循环 —— LocalBrain、LocalPrompt(角色的行为准则:
§1–§10 是活儿,§11 情景,§12 人格,§13 当前情景)、NotificationTriage(通知分诊)
ui/ball/ 那张脸 —— Mood(19 种情绪 + 待机小动作)、BallLook(六个形象,每个都是一个角色)
net/ BodyWsServer、BodyWsClient、ToolSchemas(工具定义)
device/ 屏幕控制、权限、保险箱、机器狗
config/ VoiceConfig(设置)、Notebook(记忆、待办、情景 —— 加密存储)
ui/ 悬浮层:球、字幕、设置、自检、情景、产物
i18n/AppLocale.kt 悬浮层怎么拿到自己的字符串
src/main/res/values{,-en}/strings.xml 两张面向用户的字符串表
docs/architecture.zh-CN.svg 一页看全(英文那份在旁边)
docs/permissions.zh-CN.md 为什么它要这些权限
reports/ 设计实验室与调研笔记(是仓库的一部分,不是草稿)
tools/ 设备侧小工具、球脸验收 runner、许可证审计
CLAUDE.md 给 AI 编码助手看的架构说明(英文)
好玩的用法
用它做过什么?说来听听。 值得收的往往不是"正经用途"—— 是你绕的那个弯、是它居然真能做成的 那件事、是把你逗乐的那一下。不实用也可以。
发到 Show and tell, 一个帖子讲一个,这样每个都能单独聊。想让下一个人看得懂,三件事最好带上:
- 跑在什么机器上 —— 机型、Android 版本、ROM。这个应用和 Android 打交道的层次上,各家 ROM 是 有分歧的,"我手机上能用"对别人几乎等于没说。
- 你说了什么,它真做了什么 —— 你原话那句,往往比概括出来的有意思。
- 有链接就贴 —— YouTube、B站、微博、自己的博客,公开的就行。GitHub 的 README 放不了视频 播放器,它只能是个可点的链接。YouTube 的话可以做成缩略图,看起来就像个播放器:
(把 VIDEO_ID 换成视频地址里的那一段 —— youtube.com/watch?v=VIDEO_ID)
有一件事不是客套:这个应用是读屏幕的。 截图或录屏,就是你消息、通知、当时开着的一切的实拍。 发之前把涉及隐私的地方挡掉 —— 你自己和别人的都算。
参与贡献
- 代码 —— 先读 CONTRIBUTING.zh-CN.md。里面有代码风格,以及这个仓库里三种 中文只有一种要翻译的规则。改人格或情绪有单独一节,那不是普通文案。
- 报 bug —— 请务必带上设备型号、Android 版本、ROM 和 logcat。这个应用和 Android 打交道的 层次上各家 ROM 是有分歧的:AOSP 上能用的调用在 MagicOS 上可能静默失败。缺这些,多数报告没法 复现。
- 形象与人设 —— 新形象只写语气,不写能力。能力属于行为准则,不属于某个角色。
- 文档与翻译 —— 欢迎。中文 README 是装机那份,必须保持准确。
贡献者
社区与联系
| 渠道 | 用来做什么 |
|---|---|
| Issue 跟踪 | 可复现的 bug、工程事项(请带上设备型号 / Android 版本 / ROM / logcat) |
| 讨论区 / 问答 | 装机求助、想法、"这个能不能做" |
| 好玩的用法 | 你用它做过什么,实用不实用都行 —— 见好玩的用法 |
| 安全 | 漏洞 —— 走私密渠道,永远不要开公开 issue。见 SECURITY.md |
| 产品 / 在线 | 不想自己打包就想试 Andee —— codeflying.net · codeflying.app |
| 微信 | 加 Andee 为好友 —— 扫下面的码,或者点聊天页底部的微信图标 |

安全披露
安全问题请不要开公开 issue。私密上报渠道和处理流程在 SECURITY.md。
那份文件同时列出了部署前必须知道的已知限制,其中一条在任何你控制不了的网络上都成立:调试
用的 WebSocket 服务绑在 0.0.0.0:9008 且不验证客户端身份。本应用申请的权限组合与安卓银行木马
的检测特征高度重合 —— docs/permissions.zh-CN.md 把每一条对应到一个具体工具,
而 SECURITY.md 对"还没修的"是诚实的。
免责声明与可接受使用
两份文件,都是使用条件,不是客套。中文版在这里,英文版见各自的对照文件:
- DISCLAIMER.zh-CN.md(English)—— 这是什么性质的 软件(实验性、无 SLA、无担保)、你的数据到底去了哪里、它实际能做到什么、以及出事后由谁承担。
- docs/acceptable-use.zh-CN.md(English) —— 明确禁止的用途。违反即超出许可范围,许可自动终止。
就算其余都不看,这四条也请看完:
- 他能花钱。 项目里没有支付工具,也不需要 —— 他直接点。唯一挡住一笔转账的是 Android 自己的
FLAG_SECURE,本项目对这条路径没有任何技术围栏。任何涉及钱的操作都要人在场;并且请记住 "是 AI 自己做的"不是抗辩理由,因为它确实不是。 - 他发出去的东西是你的。 消息、帖子、通话、订单,署名都是你。
- 你的数据会离开设备。 本仓库里没有数据分析 SDK,但语音会走你选的那家服务商(默认火山, 也可以选 Google),对话连同全部工具返回 结果会走到你配置的那个大脑端点,版本自检会走到一台维护者自己跑的服务器。"本机模式"换的是 目的地,不是这个事实。
- 他能被屏幕上的字操纵。 任何能让你打开一个页面的人,都可能影响到他。
许可
Apache-2.0 加附加条件 —— 禁止多租户 SaaS、禁止去 LOGO 白标。GitHub 会把它显示成
NOASSERTION,这是预期的,不是配置错误。
随包分发的第三方组件清单在 THIRD_PARTY_NOTICES.md。再分发之前请先看:
ML Kit 扫码 SDK 和 play-services-* 不是开源许可,而且扫码模型是运行时从 Google 拉的。
改了什么、以及版本号策略见 CHANGELOG.md —— 当前版本是 v0.9.0,故意停在
pre-1.0:线上协议和构建开关都还在动。
相关文档
- docs/getting-started.zh-CN.md(English)—— 从安装到第一次基本互动的最短路径
- docs/permissions.zh-CN.md(English)—— 权限为什么这么要
- docs/architecture.zh-CN.svg(English)—— 一页看全:一轮怎么进来、agent loop、六个出口
- DISCLAIMER.zh-CN.md —— 免责声明:数据去哪、能做什么、出事谁担
- docs/acceptable-use.zh-CN.md —— 可接受使用政策(禁止用途)
- SECURITY.md(英文)—— 威胁模型、已知限制、漏洞上报
- CONTRIBUTING.zh-CN.md(English)—— 贡献指南(含改人格的规范)
- THIRD_PARTY_NOTICES.md —— 依赖许可证清单
- CHANGELOG.md —— 变更记录
- CODE_OF_CONDUCT.md(英文)—— 社区行为准则
- CLAUDE.md —— 架构导览(英文)
