把健康数据采集器流放到手机上
我写了一个用 ADB 和 uiautomator 自动采集小米运动健康 App 数据的小工具,Go 写的,几百行代码。它能解锁手机、启动 App、爬取屏幕上的控件树、把步数睡眠心率之类的数字刮下来,传到 Cloudflare D1。
问题是它只能在家跑——Mac 开着,手机连着同一个 WiFi,ADB 通过局域网无线调试连接。出了家门,这条链路就断了。像养了一只只认家门的猫。
解决思路很朴素:既然手机上能装 Termux,Termux 里能装 ADB,那让程序直接在手机上跑,ADB 连接自己,不就不需要 Mac 了吗。
听起来只差一步。实际上,这一步里埋着好几个坑。
代码改动:四行
Go 代码的改动小到令人不安。在 adb/adb.go 的设备检测函数开头加一个环境变量覆盖:
| |
加上 import 里补一个 "os",总共四行。其他文件一个字没改。
真正的麻烦不在代码里。
交叉编译三连坑
本以为在 Mac 上交叉编译一个 ARM64 二进制推到手机就完事了。结果连续撞了三面墙。
第一面墙:Android 不认静态二进制
| |
推到手机一跑:unexpected e_type: 2。
Android 从 API 21 开始强制要求 PIE(Position Independent Executable)。Go 默认产出的静态二进制是 ET_EXEC(类型 2),Android 内核直接拒绝加载。
第二面墙:TLS 对齐
加上 -buildmode=pie:
| |
这次报错更晦涩:executable's TLS segment is underaligned: alignment is 8, needs to be at least 64 for ARM64 Bionic。
Go 的 PIE 模式产出的二进制,线程局部存储段只做了 8 字节对齐,而 Android 的 Bionic 链接器要求 64 字节。这是 Go 工具链和 Android 运行时之间的一个裂缝,夹在中间的人只能干瞪眼。
第三面墙:SELinux
换成 GOOS=android:
| |
二进制终于能启动了。程序跑起来,连接 ADB,准备 dump UI——然后:fork/exec /data/data/com.termux/files/usr/bin/adb: permission denied。
GOOS=android 编译的二进制使用 /system/bin/linker64,运行时拿到的是系统级 SELinux 上下文,没有权限访问 Termux 的私有目录。程序能跑,但它调用的 adb 在 Termux 的地盘里,它够不着。
三次编译,三种死法。每一种都合理,每一种都让人无话可说。
最终方案:在 Termux 上编译
| |
最笨的办法。在目标环境里装 Go,把源码传过去,本地编译。产出的二进制天然拥有正确的链接器、正确的 SELinux 上下文、正确的一切。
有时候绕了一大圈,发现最短的路就是不绕。
ADB 连接自己
Termux 里的 ADB 通过无线调试连接手机自身。但无线调试的端口是随机分配的,重启就变。试过 getprop 读系统属性——没有。试过 /proc/net/tcp 扫端口——权限拒绝。Android 把这些信息藏得很深。
最后用了一个笨办法:
| |
重启后需要重新执行一次 adb tcpip 5555,但至少端口是确定的。在不确定性中找到一个锚点,够用了。
配对过程也有讲究:Android 无线调试的配对码页面一旦切走就失效。需要用分屏模式——上半屏看配对码,下半屏在 Termux 里输入。两个世界同时存在于一块屏幕上。
Termux 环境踩坑清单
| 坑 | 症状 | 解决方案 |
|---|---|---|
| /sdcard/ noexec | 复制到 sdcard 的二进制无法执行 | termux-setup-storage 后通过 ~/storage/shared/ 访问,cp 到 Termux home 再执行 |
| termux-services 不生效 | sv-enable crond 报 file does not exist | 安装后必须重启 Termux |
| Go 版本不匹配 | go.mod requires go >= 1.25.7 (running 1.25.6) | go.mod 降版本,或加 GOTOOLCHAIN=local |
| CGO 编译失败 | clang: No such file or directory | CGO_ENABLED=0 禁用 CGO |
| 手机打不出 ASCII ~ | 全角 ~ 不被 bash 识别 | 用 $HOME 代替 ~ |
| 配对码切走就失效 | 无线调试页面离开即刷新 | 分屏模式同时操作 |
| 端口自动探测不可行 | getprop 和 /proc/net/tcp 都不可用 | adb tcpip 5555 设固定端口 |
| git clone 私有仓库 | 手机上打 Personal Access Token 太痛苦 | Termux 生成 SSH key,添加到 GitHub |
每一行都是实际踩过的。
定时任务
Termux 端负责采集,每天两次:
| |
Mac 上的 OpenClaw Health Agent 在 7:40 和 17:40 从 D1 API 读取数据,结合七天历史做趋势分析,生成飞书卡片推送。采集和分析完全解耦——手机负责干活,Agent 负责思考。
手机 Termux cron 7:30/17:30 → ADB 本机 → 采集 → Cloudflare D1
↓
Agent cron 7:40/17:40 → 读 API → 7天趋势分析 → 飞书卡片
几条经验
关于交叉编译:Go 交叉编译到 Android/Termux,如果程序需要 fork/exec 其他 Termux 工具,交叉编译走不通。SELinux 会在你以为万事大吉的时候拦住你。老实在目标环境编译。如果是纯计算型二进制(不 fork 其他程序),GOOS=android 可行。
关于 Termux:它比想象中能干,也比想象中脆弱。能装 Go、能跑 cron、能连 ADB——但 Android 系统随时可能杀后台。termux-wake-lock + 关闭电池优化 + 锁定最近任务,三件套缺一不可。
关于自动化的边界:ADB + uiautomator 理论上可以操控任何 App。但像微信这样使用自定义渲染的 App,控件树里能拿到的信息远不如原生 UI 丰富。健康类 App 多用标准 Android 控件,是自动化的理想对象。
一个工具从依赖笔记本电脑到能在口袋里自己运行,改的代码不过四行。真正的工作量在于和运行环境的反复交涉——编译器、链接器、安全策略、文件权限、端口发现,每一层都有自己的规矩。《庄子》里说"庖丁解牛",讲的是顺着纹理走。这些纹理不在文档里写着,只有刀碰上去才知道。