上一篇文章最后,我把旧手机的 AI Agent 基础环境搭了起来。但是在测试手机硬件能力的时候,我遇到了第一个真正让我头疼的问题:Termux 的 API 全挂了。
问题最终是 Play 版 Termux 环境迁移到了另一套 Termux 构建,导致 App 与 Termux 内部软件包不匹配。
核心修复:
# 环境备份
# 更换软件源和 keyring
# 安装正确的 termux-api
# 安装正确的 termux-tools / termux-exec
# 重新打开 Termux
如果你想知道为什么会出现这个问题,再继续往下看。
命令全部超时
最开始发现问题,是因为我测试麦克风。
我想让这部手机以后能够一直听我说话,所以麦克风是必须打通的。
结果:
termux-battery-status
没反应。等到最后超时。
我又测试了几个:
termux-sensor
termux-clipboard-get
termux-notification
还是一样,全部超时。
后来我才发现,这不是某一个 API 的问题。我这台手机上,69 个 termux-* 命令基本全部不能用。
更奇怪的是,Termux 自己的日志里还出现了一句:
Invalid action: "com.termux.service_api"
这句话看起来像是说软件调用 api 接口报错,说接口不可用。按照我日常工作经验,这通常是版本不匹配导致的问题。
查看软件版本号是否匹配
于是我把 Termux、Termux API 和相关软件的版本号全部翻了一遍,看起来没问题。
我问 AI,得到的回答也是说版本没问题,官方就是这个版本,别人也在用。
于是我就顺着这个方向继续查,当时我以为是 Termux 最近有什么版本错位或者已知不匹配的缺陷,就把其中一个组件升级到了测试版本,结果还是不行。
这时候我已经开始怀疑人生了。如果版本没问题,为什么所有 API 都一起挂了?总不能 69 个命令同时坏掉吧?
后来我突然想到一个问题,我用的各种软件,特别是国产软件,Play 商店版,和国内应用商店版,以及官网版本,都会不一样,版本之间会有些许功能差异,这次会不会也是这个问题呢?
同一个版本号的两个构建
我决定不再看版本号,直接看二进制。
我把负责 Termux API 通信的程序提取出来,用 strings 看了一下里面到底写了什么。
操作很简单:
su -c 'cp -L $PREFIX/libexec/termux-api* /data/local/tmp/ && chmod 644 /data/local/tmp/termux-api*'
然后检查里面到底写了什么:
grep -aoE 'startservice|broadcast|service_api|TermuxApiReceiver' \
/data/local/tmp/termux-api-broadcast
结果很有意思。
我手机里这个版本出现的是:
startservice
com.termux.service_api
而我找到的另一个构建则是:
broadcast
com.termux.api/.TermuxApiReceiver
我当时第一反应就是这两个怎么不一样?
继续往下查,我才发现真正的问题:我这套 Termux 环境,实际上混进了两套不同构建体系的软件包。Play 商店版本和 F-Droid / GitHub 发布的 Termux,在部分组件上使用了不同的构建和版本线。它们的版本号甚至可能看起来一样,但内部实现并不一定相同。
如何修复
接下来,只需要替换软件版本,其实就可以了。
可麻烦的是,我这个环境已经安装好了 Python,node,Claude code,配置好了环境,让我从头来一次,工作量有点大,还可能又会产生新的问题。
所以我只想修坏掉的那一部分,也就是只替换软件版本,保留所有环境。
这就有点像,我重装了 Windows,可是里面的注册表,软件,个人文档,全部都要保留。有挑战性,可是做起来很有意思。
动手之前先备份,旧的二进制、apt 源文件、keyring、需要替换的 deb 包,都留一份。
再预演,任何 apt 操作,先使用下面的命令看它需要哪些包:
apt install -s
开干。
第一步:换掉旧的软件源
我首先检查 Termux 当前使用的软件源。旧环境是从 Play 版时代迁移过来的,所以这里依然指向 Play 系的软件仓库,需要把它换成对应的官方 Termux 软件源。
这里有一个很容易踩坑的地方:
软件源和签名 keyring 必须一起换。
不能只把 URL 改了就结束,因为 apt 不光要知道“东西从哪里下载”,还要确认“这个东西是谁签名的”。
所以我的修复路径是:
备份旧源
↓
换成官方 Termux 源
↓
更换对应的 termux-keyring
↓
apt update
如果只换源,不换 keyring,后面很容易直接遇到 NO_PUBKEY。
这一步其实是在修复整个软件包环境的“信任链”。
第二步:换 termux-api
软件源恢复以后,我没有直接:
apt upgrade
甚至没有直接把所有 Termux 包全部升级。
因为我只想解决一个问题:
Termux API 为什么和 App 不匹配?
换之前先做一次预演。
apt install -s termux-api
确认它到底准备动什么。
如果发现除了目标包以外,还有一大堆 Node、Python 或其他我不想碰的东西,就先停下来。确认依赖关系没有问题以后,再进行实际安装。
最终换成官方构建的:
termux-api 0.59.1-1
而不是原来 Play 系的:
termux-api 0.59.1
看起来只差一个 -1。
实际上里面的 helper 二进制都已经不是同一个东西。
换完以后,再验证二进制的 sha256,我这次拿到的官方构建是:
27e7057e...
而之前那个 Play 系构建是:
a484a6b3...
到这里,第一层问题终于解决。
API 修好以后,我重新打开 Claude Code:
claude
结果:
bad interpreter: No such file or directory
这次问题出在 termux-exec,Claude Code 本身没坏,它的启动脚本需要:
/usr/bin/env
但 Android 系统里没有这个路径。
Termux 平时通过 termux-exec 解决这个问题,简单来说,它会在程序执行系统调用的时候,把这些 Linux 环境里的路径转换成 Termux 自己的路径。
而它需要通过:
LD_PRELOAD
把自己加载进去。
所以我先不修改任何东西,直接做一个验证:
LD_PRELOAD=$PREFIX/lib/libtermux-exec.so claude --version
结果:
2.1.112 (Claude Code)
正常。
这一刻很关键,因为我已经证明:
Claude Code 没坏,Node.js 也没坏,真正缺的是 **LD_PRELOAD** 。
接下来只需要修复 Termux 的登录环境。
第三步:换 termux-tools 和 termux-exec
我继续检查:
grep -n LD_PRELOAD $PREFIX/bin/login
结果没有。
这意味着当前的 login 并不会帮我设置 LD_PRELOAD。继续查才发现,我这里的 termux-tools 依然是 Play 商店版本。我需要使用官方的 termux-tools 和 termux-exec。
这时候又遇到了一个非常有意思的问题。
我执行:
apt install termux-tools termux-exec
apt 告诉我:
termux-tools is already the newest version (3.0.9).
而官方版本是:
1.46.0+really1.45.0-1
因为当前 apt 的软件源里仍然认为 3.0.9 是可用的最新版本,所以即使我知道自己需要另一套构建,apt 也不会主动帮我切过去。按照 apt 的版本比较规则,3.0.9 更大。
所以它认为:
已经是最新了,不需要换。
于是我直接指定官方 .deb:
apt install -y --no-install-recommends --allow-downgrades \
/sdcard/Download/<job>/termux-tools_1.46.0+really1.45.0-1_aarch64.deb
这里的关键参数就是:
--allow-downgrades
因为从 apt 的角度看,我确实是在“降级”。
第四步:解决配置文件冲突
安装过程中,dpkg 又跳出来问我:
*** motd (Y/I/N/O/D/Z) [default=N] ?
还有另外一个 Termux 初始化脚本。
原因也很好理解:
两套 Termux 都曾经管理过这些文件。
这里我选择安装维护者版本。
因为这两个文件主要负责 Termux 的初始化和显示。
我自己真正使用的配置,比如:
~/.termux/termux.properties
并不属于这些软件包,不会因为这个操作被一起覆盖。
收尾验证
安装完成以后,我重新打开一个 Termux 会话。
这次:
claude
正常启动。
再测试:
termux-battery-status
也正常返回 JSON。
最后测试麦克风。
Recording started: /data/data/com.termux/files/home/mic-test.m4a
我把文件拉到电脑上检查。实际时长 2.936 s,单声道 AAC,8000 Hz。这次是真的录出来了,不是 API 给我返回了一个空文件。
这次终于结束了。
这次排错让我意识到,很多时候真正的问题并不在报错本身,而在于我默认了两个东西“应该是一样的”。
现在,这部手机终于可以继续往下一步走了。
下一步,我要解决的就是让她真正拥有“耳朵”,不是简单地录一段音频,我想让她能够一直听着,在合适的时候知道我在说话,再把我的声音变成她能理解的文字。
这件事情,才真正开始接近我脑子里的那部《Her》。