# 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`: ```bash # 1. 查 Termux App 的数字 UID adb shell dumpsys package com.termux | grep userId # 例:10329 (u0_a329) # 2. 用 su -G 切过去,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: ```bash # ✗ 会炸: 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 进目录」的批量语法: ```bash 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 路径再推上去: ```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` 短选项): ```bash 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 ` 机制上跑通(成功调用到原生 aapt2 并开始编译资源),但在这个特定测试 APK 上卡在 `$avd_hide_password__0` 这类 AndroidX 生成的、带 `$` 的资源名上——这是 **apktool/aapt2 本身的已知限制**(资源名非法字符),跟本文记录的环境搭建无关,换一个不带这种命名的目标 APK 应该不会复现。 ## 落地位置 - `openjdk-17`、`aapt2`/`aapt`:走 Termux `apt`(`dpkg -l` 可查) - `apktool` + `apktool.jar`:手动放进 `/data/data/com.termux/files/usr/bin/`(属主 `u0_a329`,不是 apt 管理的包,`pkg upgrade` 不会动它们,但也不会跟着 Termux 仓库更新版本——以后要升 apktool 版本得重复本文流程手动换 jar)