Commit a9656bc1801f8d4d8ae165bcf07c8b8f0884079d
1 parent
f400f99a
docs(phoneuse): 记录 apktool/JDK17/aapt2 在 Termux 上的安装踩坑
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Showing
1 changed file
with
102 additions
and
0 deletions
phoneuse/apktool-termux-install-notes.md
0 → 100644
| 1 | +# apktool(+ JDK 17 / aapt2)在 Termux(Pixel)上的安装记录 | |
| 2 | + | |
| 3 | +2026-06-25,在跟 `hermes-termux-install-notes.md` 同一台 Pixel 上,装 OpenJDK 17 + apktool 3.0.2 + aapt2,目标是 Termux/Hermes 都能直接调 `apktool`。全程通过 Mac 上 `adb shell` 远程操作(没有打开手机上的 Termux App 界面)。按踩坑顺序记录。 | |
| 4 | + | |
| 5 | +## 0. 前提:Termux 的 `apt`/`dpkg`/`pkg` 禁止以 root 身份跑 | |
| 6 | + | |
| 7 | +**现象**:`adb shell su -c "apt-get install -y openjdk-17"` 报 `Ability to run this command as root has been disabled permanently for safety purposes.` | |
| 8 | + | |
| 9 | +**真因**:Termux 故意拦掉 root 跑包管理器——`$PREFIX` 整棵树是 Termux App 自己的 UID 拥有的,root 装东西会把文件属主写成 root,后续 Termux 自己的进程反而读不了/Termux 升级时状态对不上。 | |
| 10 | + | |
| 11 | +**修法**:必须切到 Termux App 自己的非 root UID 来跑 `apt`/`pkg`: | |
| 12 | +```bash | |
| 13 | +# 1. 查 Termux App 的数字 UID | |
| 14 | +adb shell dumpsys package com.termux | grep userId # 例:10329 (u0_a329) | |
| 15 | + | |
| 16 | +# 2. 用 su -G <gid> <uid> 切过去,3003 是 AID_INET,没有这个组联网会断(DNS 解析失败) | |
| 17 | +adb shell su -G 3003 10329 -c "/data/data/com.termux/files/usr/bin/apt-get install -y openjdk-17" | |
| 18 | +``` | |
| 19 | +跟 CLAUDE.md 里记的「AID_INET (3003) gotcha」是同一个坑的另一个触发场景(那条记的是跑 python,这里是跑 apt)。 | |
| 20 | + | |
| 21 | +## 1. `su -G gid uid -c "cmd1; cmd2"` 一旦带 `;` 就会 `inaccessible or not found` | |
| 22 | + | |
| 23 | +**现象**:单条命令 `su -G 3003 10329 -c "/path/to/apt-get --version"` 能正常跑出版本号;但只要前面加一句 `export HOME=...; ` 之类,同一个 `apt-get` 路径立刻变成 `/system/bin/sh: ...: inaccessible or not found`,换成 `ls` 同样的二进制路径则报 `Permission denied`。 | |
| 24 | + | |
| 25 | +**真因**:没有 `;` 时,`su -c` 直接 execve() 目标二进制(不经过 shell),SELinux 域转换走的是允许的路径。一旦命令里出现 `;`(或任何需要 shell 语法解释的部分),`su` 会改用 `/system/bin/sh -c "整段字符串"` 来跑,而 `/system/bin/sh` 这个域被 SELinux 策略挡掉了「从 `/data/data/com.termux/...`(App 私有目录)里执行二进制」的权限——跟 DAC 的 rwx 位完全无关,纯 SELinux mlsconstrain。 | |
| 26 | + | |
| 27 | +**修法**:要带环境变量又不想触发 shell 解释,用 `env` 把整条命令压成一个不含 `;` 的单一 execve: | |
| 28 | +```bash | |
| 29 | +# ✗ 会炸: | |
| 30 | +su -G 3003 10329 -c "export HOME=/data/data/com.termux/files/home; apt-get install -y openjdk-17" | |
| 31 | + | |
| 32 | +# ✓ 没问题: | |
| 33 | +su -G 3003 10329 -c "/data/data/com.termux/files/usr/bin/env HOME=/data/data/com.termux/files/home PREFIX=/data/data/com.termux/files/usr DEBIAN_FRONTEND=noninteractive /data/data/com.termux/files/usr/bin/apt-get install -y openjdk-17" | |
| 34 | +``` | |
| 35 | + | |
| 36 | +## 2. 往 App 私有目录拷文件:用纯 root `su -c`,且别把 cp 写成多源 + 目录尾斜杠 | |
| 37 | + | |
| 38 | +**现象**:`su -c "mkdir -p .../bin && cp a b .../bin/ && chown ..."` 报 `cp: '.../bin/' not directory`(即便那个目录已经存在)。 | |
| 39 | + | |
| 40 | +**真因**:踩到的是这台机器上 toybox `cp` 对「多个源文件 + 目录结尾带 `/`」这种调用形式的兼容性问题,不是权限问题。 | |
| 41 | + | |
| 42 | +**修法**:写文件级别的 cp,一条命令对应一个目标文件,不要依赖「cp 进目录」的批量语法: | |
| 43 | +```bash | |
| 44 | +adb push apktool.jar /data/local/tmp/apktool.jar | |
| 45 | +adb shell su -c "cp /data/local/tmp/apktool.jar /data/data/com.termux/files/usr/bin/apktool.jar" | |
| 46 | +adb shell su -c "chown u0_a329:u0_a329 /data/data/com.termux/files/usr/bin/apktool.jar" | |
| 47 | +adb shell su -c "chmod 644 /data/data/com.termux/files/usr/bin/apktool.jar" | |
| 48 | +``` | |
| 49 | +注意:这种纯 root `su -c`(不带 `-G`)跑 `cp`/`chown`/`mkdir` 没问题——上一条的 SELinux 限制只挡「在 App UID 域下 exec 私有目录里的二进制」,不挡 root 域的文件 I/O。`chown` 偶尔会在文件已经是目标属主时报一次 `Permission denied`,无害,忽略即可。 | |
| 50 | + | |
| 51 | +## 3. 本机 Mac 的坑:别在调 `adb` 的同一条命令里设 `HOME` | |
| 52 | + | |
| 53 | +**现象**:`HOME=/data/data/com.termux/files/home adb shell ...` 直接报 `adb_utils.cpp:316 Cannot mkdir '/data/data/com.termux/files/home/.android'`,这是**本机 Mac 上的 adb 客户端自己**在找 `~/.android` 配置目录,跟设备端八竿子打不着。 | |
| 54 | + | |
| 55 | +**真因**:那个 `HOME=...` 赋值是写在本地 bash 里、给本地 adb 进程的环境变量,污染了 adb 客户端自己的 `$HOME`,跟 `su -G ... -c "...HOME=..."`(写在远端命令字符串里)完全是两件事,命名一样导致混淆。 | |
| 56 | + | |
| 57 | +**修法**:远端要设的环境变量只能出现在 `-c "..."` 的字符串内部(用 `env` 包一层,见上),本机 shell 里永远不要 export `HOME`/`PREFIX` 这类名字。 | |
| 58 | + | |
| 59 | +## 4. apktool 官方 wrapper 脚本的 shebang 在 stock Android 上找不到解释器 | |
| 60 | + | |
| 61 | +**现象**:`env ... /path/to/apktool --version` 报 `No such file or directory`,但文件确实存在、属主权限都对。 | |
| 62 | + | |
| 63 | +**真因**:apktool 仓库自带的 `scripts/linux/apktool` 脚本第一行是 `#!/bin/bash`,而 stock Android(非 Termux 系统分区)压根没有 `/bin/bash`,execve 找不到解释器,错误信息却指向脚本本身,容易误判。 | |
| 64 | + | |
| 65 | +**修法**:把 shebang 改成 Termux 自己的 bash 路径再推上去: | |
| 66 | +```bash | |
| 67 | +sed -i '' 's|#!/bin/bash|#!/data/data/com.termux/files/usr/bin/bash|' apktool # 在 Mac 本机改 | |
| 68 | +adb push apktool /data/local/tmp/apktool | |
| 69 | +adb shell su -c "cp /data/local/tmp/apktool /data/data/com.termux/files/usr/bin/apktool" | |
| 70 | +adb shell su -c "chmod 755 /data/data/com.termux/files/usr/bin/apktool" | |
| 71 | +``` | |
| 72 | + | |
| 73 | +## 5. apktool 自带/自动下载的 aapt2 不是 Android arm64 原生的,build 步骤直接炸 | |
| 74 | + | |
| 75 | +**现象**:`apktool b` 跑到 `Building resources with aapt2...` 时报: | |
| 76 | +``` | |
| 77 | +W: /data/data/com.termux/files/usr/tmp/aapt2_xxxxx.tmp[2]: syntax error: unexpected '(' | |
| 78 | +Exception ... Execution failed (exit code = 1): [.../aapt2_xxxxx.tmp, compile, --dir, ...] | |
| 79 | +``` | |
| 80 | +`tmp` 路径下的文件名一看就是 apktool 自己解压出来的临时 aapt2 可执行文件。 | |
| 81 | + | |
| 82 | +**真因**:Google 官方发的 `aapt2` Maven 包只有 Linux-x86_64 / macOS / Windows 三种 host 构建,没有「跑在 Android 设备上」这种 host 平台。apktool 拿到的是不兼容当前架构的二进制,execve 失败后内核 fallback 成用 shell 解释这段二进制内容,于是报 `syntax error: unexpected '('`(一段二进制垂直被当 shell 脚本读)。 | |
| 83 | + | |
| 84 | +**修法**:Termux 仓库自己打包了原生 aarch64 的 `aapt2`,装上之后用 apktool 的 `--aapt` 参数显式指过去(注意这个 flag 只有 `--aapt`,没有 `-a` 短选项): | |
| 85 | +```bash | |
| 86 | +adb shell su -G 3003 10329 -c "/data/data/com.termux/files/usr/bin/env DEBIAN_FRONTEND=noninteractive /data/data/com.termux/files/usr/bin/apt-get install -y aapt2" | |
| 87 | +# 用法: | |
| 88 | +apktool b --aapt /data/data/com.termux/files/usr/bin/aapt2 -o out.apk decoded_dir | |
| 89 | +``` | |
| 90 | + | |
| 91 | +## 验证结果 | |
| 92 | + | |
| 93 | +拿 Termux 自己的 `base.apk` 做端到端烟雾测试(`pm path com.termux` 拿路径,root cp 出来再跑): | |
| 94 | +- `java -version` → OpenJDK 17.0.19 ✓ | |
| 95 | +- `apktool --version` → 3.0.2 ✓(默认在 `$PREFIX/bin` 上,任何 Termux 进程/Hermes 子进程不用额外配置就能调到,不需要再装 `~/bin` 加 PATH) | |
| 96 | +- `apktool d` 完整跑通:29 个 `classes*.dex` 全部 baksmali 成功,资源/Manifest 全解出来 | |
| 97 | +- `apktool b --aapt <termux的aapt2>` 机制上跑通(成功调用到原生 aapt2 并开始编译资源),但在这个特定测试 APK 上卡在 `$avd_hide_password__0` 这类 AndroidX 生成的、带 `$` 的资源名上——这是 **apktool/aapt2 本身的已知限制**(资源名非法字符),跟本文记录的环境搭建无关,换一个不带这种命名的目标 APK 应该不会复现。 | |
| 98 | + | |
| 99 | +## 落地位置 | |
| 100 | + | |
| 101 | +- `openjdk-17`、`aapt2`/`aapt`:走 Termux `apt`(`dpkg -l` 可查) | |
| 102 | +- `apktool` + `apktool.jar`:手动放进 `/data/data/com.termux/files/usr/bin/`(属主 `u0_a329`,不是 apt 管理的包,`pkg upgrade` 不会动它们,但也不会跟着 Termux 仓库更新版本——以后要升 apktool 版本得重复本文流程手动换 jar) | ... | ... |