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