Metadata-Version: 2.4
Name: apkbox
Version: 2.1.3
Summary: APK 工具集: 转成可引入的 AAR、反编译出 .java、看 APK 摘要(工具自动下载)
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# apkbox

APK 工具箱：把 APK 转成能直接引入工程的 AAR，反编译出 `.java`，或快速看一眼 APK 里有什么。

```bash
apkbox app.apk                 # app.apk → app.aar（aar 是默认子命令）
apkbox java app.apk -o src/    # 整个 APK 反编译成 .java
apkbox info app.apk            # 包名 / 权限 / 组件 / ABI / 签名摘要
```

## 依赖

- Python 3.9+
- JDK 8+（`java` 要在 `PATH` 里）
- 可选：Android SDK —— 提供 `android.jar` 做 AAR 的资源 link 自检，也是 aapt2 的首选来源（见下文）

就这些。wheel 只有约 40 KB、Python 侧无第三方依赖；apktool / dex-tools / aapt2 /
jadx 这些工具**不在包里**，系统装过就直接用，没有的话首次用到时自动下载到缓存
（约 110 MB，一次性）。

## 安装

```bash
pip install apkbox            # 从 PyPI 装

# 或从源码 / wheel 装
pip install --break-system-packages .
pip install --break-system-packages dist/apkbox-2.1.3-py3-none-any.whl

# 不想安装，直接在源码目录跑
python3 -m apkbox --version
```

> Debian/Ubuntu 的系统 Python 是 externally-managed，所以要加 `--break-system-packages`；
> 在 venv 里装则不需要这个参数。

## 工具从哪来

每个工具按这个顺序解析，**找到就用，不重复造轮子**：

```
命令行参数 > 环境变量 > 系统 PATH / Android SDK > apkbox 缓存 > 自动下载
```

- 缓存目录默认 `~/.cache/apkbox`（遵循 `XDG_CACHE_HOME`），下载前先比对
  sha256，装一次后续直接复用。
- 下载先走官方直链（GitHub releases、Google Maven），不通再换内置镜像；
  设置 `APKBOX_MIRROR` 可以指定自己的镜像（此时镜像优先、直链兜底）。
- 首次运行会看到类似输出，之后都是 `✓`：

  ```
  tools  : apktool ✓, dex2jar 已下载, aapt2 ✓
  ```

- 想提前把工具备齐（离线机、CI 预热）：

  ```bash
  apkbox fetch                  # 全部：apktool / dex2jar / aapt2 / jadx
  apkbox fetch jadx apktool     # 指定几个
  ```

  `fetch` 每个工具下载后都会**回验**：能跑的打 `✓`，跑不起来的打 `⚠` 并说明原因
  （退出码 1）——不会下载完就报成功。

### 平台注意（Termux / ARM）

apktool / dex-tools / jadx 都是纯 Java，跟着 JDK 走；**只有 aapt2 是平台相关的**，
Google 官方只提供 x86_64 Linux 版（没有 arm64，没得下）。在 ARM 设备（Termux、
树莓派、Apple Silicon）上：

- **apkbox 会先自己想办法**（都不需要 root，`fetch` 和转 AAR 都会触发）：
  - Termux：问一句后执行 `pkg install aapt2`
  - Debian / Ubuntu 等带 apt 的发行版：用 `apt --print-uris` 解析依赖，把官方
    仓里的 aapt2 及其依赖下载、解包进缓存（走你自己配置的 apt 源，glibc 天然匹配）
- 也可以手动装，装上就自动识别、优先使用：Termux `pkg install aapt2`、
  Debian/Ubuntu `apt install aapt2`，或用 `--aapt2` / `APKBOX_AAPT2` 指一个能跑的
- 或装 `qemu-x86_64` 模拟跑官方版（apkbox 检测到会自动用）
- 都不行时**不会白下 2.3 MB**，直接说清原因（官方版是 x86_64 二进制 / 本机
  架构不符）并给上面的解法：`fetch` 打 ⚠ 且退出码 1；转 AAR **降级继续**——
  跳过 aapt2 自检，`R.txt` / `public.xml` 缺失、资源 id 不固定（有警告），其余
  产物（classes.jar / res / manifest）照常可用

环境变量：

| 变量 | 作用 |
| --- | --- |
| `APKBOX_MIRROR` | 镜像前缀，逗号分隔，下载地址 = 前缀 + 官方 URL（与 gh-proxy.com 同规则）；设为空则只走直链 |
| `APKBOX_OFFLINE=1` | 禁止联网，缺工具直接报错并给出手动下载地址 |
| `APKBOX_CACHE` | 缓存目录（默认 `~/.cache/apkbox`） |
| `APKBOX_NO_VERIFY=1` | 跳过 sha256 校验（慎用） |
| `APKBOX_APKTOOL` / `APKBOX_DEX2JAR` / `APKBOX_AAPT2` / `APKBOX_JADX` | 分别指定工具路径 |
| `ANDROID_HOME` / `ANDROID_SDK_ROOT` | 找 aapt2 和 android.jar |
| `APKBOX_LANG` | 界面语言 `zh` / `en` |

当前自动下载的版本（升级时改 `apkbox/fetch.py` 里的 `SPECS`）：

| 工具 | 版本 | 来源 |
| --- | --- | --- |
| apktool | 2.7.0 | GitHub releases（单个 fat jar） |
| dex-tools | 2.4 | GitHub releases |
| jadx | 1.5.6 | GitHub releases |
| aapt2 | 9.4.1-15978811 | Google Maven（`dl.google.com`） |

## 转 AAR

```bash
apkbox app.apk                                  # → app.aar（和输入同目录）
apkbox aar app.apk -o dist/app.aar --keep        # 指定输出，保留中间文件
apkbox app.apk --code-only                       # 只要 classes.jar，不带 res/assets/jni
apkbox app.apk -w work/ --skip-decode            # 复用 work/ 里已有的解包结果
```

流程：apktool 解包 → dex2jar 出 classes.jar（两者互不依赖，**并行执行**，整体快约 1–2 分钟）→ 组装 res/assets/jni（`public.xml` 钉死资源 id）→ 裁剪 AndroidManifest → aapt2 compile + link 自检 → 打包 `.aar`。

常用选项：

| 选项 | 说明 |
| --- | --- |
| `-o, --output` | 输出 `.aar` 路径（默认与 apk 同名） |
| `-w, --work-dir` | 中间文件目录（默认临时目录，指定后保留） |
| `--code-only` | 只要 classes.jar，不带 res/assets/jni |
| `--keep-app-attrs` | 保留 `<application>` 的属性（默认清空以免与宿主合并冲突） |
| `--skip-decode` | 复用 `-w` 目录里已有的 apktool 解包结果 |
| `--keep` | 保留临时目录 |
| `--apktool` / `--dex2jar` / `--aapt2` | 手动指定工具（默认按上面的顺序自动解析） |
| `--android-jar` | android.jar 路径（用于 link 自检，缺省自动找 SDK） |
| `-v, --verbose` | 打印每步实际执行的子命令 |

### 产物注意事项

- 打印的 `可能与宿主 Duplicate class: androidx …` 是提示：这些包宿主大概率已有，合并前先看一眼。
- `<application>` 的 `android:name/icon/label/theme` 默认被清空（避免和宿主合并冲突），图标、label、主题、Application 类要宿主自己设置；确实想带上用 `--keep-app-attrs`。
- 没找到 android.jar 时跳过 link 自检，`R.txt` 按原 APK 的资源 id 生成，`minCompileSdk` 取原 APK 的 `compileSdkVersion`；找到时会尝试各平台，取能通过的最低版本。
- native 库按 APK 原样携带：APK 里有哪些 ABI，AAR 的 `jni/` 就有哪些；只有 arm 库的话 x86 模拟器跑不起来（会打印警告）。

## 反编译成 .java

```bash
apkbox java app.apk -o src/          # APK → src/**/*.java + resources/
apkbox java classes.dex              # 单个 dex → classes-java/
apkbox java ./smali_out/ -o src/     # 目录里挑 classes*.dex
apkbox java app.apk -r               # --no-res：只要代码，不解资源
apkbox java app.apk -j 4 --deobf     # 4 线程 + 反混淆
```

| 选项 | 说明 |
| --- | --- |
| `-o, --output` | 输出目录（默认 `<输入名>-java`） |
| `-r, --no-res` | 不反编译资源，只出 `.java` |
| `-j, --jobs` | 线程数（默认用 jadx 的） |
| `--deobf` | 反混淆：太短/太长的类名重新命名 |
| `-e, --export-gradle` | 输出成 Android/Java gradle 工程 |
| `--show-bad-code` | 反编译不出来的代码也照样输出（带注释标记） |
| `--jadx` | jadx 可执行文件/jar（默认自动解析/下载） |

- 有反编译错误时会在输出旁写一份 `<输出名>.log`（混淆过的 APK 出现几百处错误很常见）。
- 进度行只在终端里刷，重定向到文件时不会刷屏。

## 看 APK 摘要

```bash
apkbox info app.apk
```

```
apkbox info v2.1.3
  file      : app.apk (21.2 MB)
  sha256    : 7440d842…
  dex       : 3 文件, 15.1 MB
  res       : 766 条目, assets 98, arsc 3.3 MB
  abis      : arm64-v8a
  sign      : v1 + v2/v3
  package   : com.deepseek.chat (versionCode 279, versionName 2.6.1)
  label     : DeepSeek
  sdk       : min 23, target 36, compile 36
  launcher  : com.deepseek.chat.MainActivity
  perms     : 22
    - android.permission.INTERNET
    …
  components: 32 (activity 11, service 11, receiver 5, provider 5)
```

- zip 级信息（dex / res / ABI / 签名）纯 Python 读，秒出；manifest 级信息用
  `aapt2 dump badging` + `xmltree`，没有 aapt2 时自动降级为 zip-only 摘要并提示。
- 组件统计数的是 `AndroidManifest.xml` 里的 activity / service / receiver /
  provider（含 activity-alias）。
- 不解包、不反编译，适合 CI 里做前置检查。

## 体积分析

```bash
apkbox size app.apk                  # 按类别统计 + 最大的 10 个文件
apkbox size app.aar --top 20         # .aar 同样可用
```

```
apkbox size v2.1.3
  file: app.apk (21.2 MB), 1023 个文件, 压缩后 21.0 MB (原始 43.5 MB, 2.1x)

  类别       文件数        压缩后       占比         原始       膨胀
  代码        11     7.2 MB    34.3%    15.1 MB     2.1x
  原生库      16     8.7 MB    41.4%    20.5 MB     2.4x
  资源       767     3.9 MB    18.3%     4.1 MB     1.1x
  …

  最大的 5 个文件(压缩后):
     1. classes.dex  5.7 MB → 11.7 MB
     …
```

- 类别口径：dex/jar/kotlin → 代码；`res/`+`resources.arsc` → 资源；`lib/`/`jni/`
  → 原生库；`AndroidManifest.xml` → 清单；`META-INF/`+R.txt 等 → 元数据。
- 纯 zip 读，秒出；"膨胀"列是原始大小 ÷ 压缩后大小。

## 对比两个包

```bash
apkbox diff old.apk new.apk          # 版本升级前后 / CI 里做回归检查
```

- **zip 级**：文件增减、内容变化（按未压缩大小，同内容重新打包不算差异）、
  各类别体积增减、大文件清单。
- **manifest 级**：包名/版本、sdk、label、launcher、权限增减、组件数量变化。
  AAR 直接解析纯文本 manifest；APK 走 `aapt2 dump`，没有 aapt2 时跳过该段并提示。
- 退出码 `0` 完全一致 / `1` 有差异 / `2` 输入错误，可直接用于 CI 卡点。

## 列 dex 类名

```bash
apkbox classes app.apk               # 全部类名
apkbox classes app.apk com.foo       # 子串过滤
apkbox classes app.apk -c            # 只要数量
apkbox classes classes.dex kotlin.   # 单个 dex 也行
```

- 纯 Python 解 dex（class_defs → type_ids → string_ids），不解包、不反编译，秒出。
- `.dex` / `.apk` / 含 `classes*.dex` 的 zip 都支持；多 dex 按 classes.dex, classes2…
  顺序合并。
- 统计行走 stderr（`24 / 15484 类`），stdout 可以直接管道给 grep/wc。

## dex 成员与统计

```bash
apkbox members app.apk MainActivity    # 这些类的字段/方法（类名子串匹配）
apkbox members app.apk 'okhttp3.'      # 一批类的成员一起看
apkbox dexinfo app.apk                 # 每个 dex 的大小/类/方法/字段数
```

- `members` 输出 `类名#方法(参数)返回值` / `类名#字段:类型`，参数按 dex 描述符
  还原成 Java 写法（`android.os.Bundle`、`int[]`），和 javap 的签名一致。
- `dexinfo` 的"方法"列是 `method_ids` 数——**65536 上限算的就是它**；单个 dex
  接近 60000 会打警告（混淆重的大包很容易撞上）。
- 都是纯 Python 解 dex，统计行走 stderr，stdout 可直接管道。

## 清单转 XML

```bash
apkbox manifest app.apk                # 二进制清单 → 可读 XML（走 aapt2）
apkbox manifest app.aar                # 文本清单 → 重新排版
apkbox manifest app.apk -o m.xml       # 写到文件
```

- 输出标准 XML（带 xmlns 声明、属性全部展开），可以直接 diff、搜索、给脚本吃；
  不解包、不反编译。
- APK 需要 aapt2（自动解析/下载）；AAR 或纯文本 XML 不需要，走 ElementTree
  美化。

## 组件审计

```bash
apkbox components app.apk                  # 列四大组件: 类型/exported/类名/权限
apkbox components app.apk --exported       # 只看导出的(攻击面)
apkbox components app.apk --type service   # 只看某一类
apkbox components app.aar                  # AAR/文本清单不需要 aapt2
```

- `exported` 列是**生效值**：显式 `android:exported` 优先；没声明时带
  intent-filter 的标 `default*`（旧规则默认可导出），否则 `-`。
- 统计行（`共 n 个组件 (…)`) 走 stderr，stdout 是纯数据可直接管道；组件带
  permission / readPermission / writePermission 时行尾追加 `[权限]`。

## XML 条目转文本

```bash
apkbox xml app.apk res/xml/network_security_config.xml
apkbox xml app.apk res/layout/main.xml -o out.xml
```

- 任意二进制 XML 条目（layout / xml / drawable…）→ 可读 XML，和 `manifest`
  同一条解码链路；文本条目直接美化输出。
- `manifest` 就是 `xml … AndroidManifest.xml` 的特例。

## 提取图标

```bash
apkbox icon app.apk                       # → app-icons/<dpi 目录>/
apkbox icon app.apk -o icons/             # 指定输出目录
```

- 优先 `aapt2 dump badging` 里的 `application-icon-<dpi>` 行，每个 dpi 精准取；
  没有 aapt2 时退回文件名启发式（`*ic_launcher*`）并提示。
- 按原目录结构落到输出目录；`adaptive-icon` 的 xml 是二进制，想看内容用
  `apkbox xml`。

## 完整性体检

```bash
apkbox check app.apk                      # zip / CRC / dex / 清单 / 签名
```

- 纯 Python 不联网：zip 结构 → 全量 CRC → dex magic → 清单在不在 →
  v1 / v2-v3 签名，逐行打 ✓/✗/⚠。
- ✗（损坏/缺失）退出码 1；⚠（没 dex、没签名，AAR 很常见）只提示，退出码 0。

## 找字符串

```bash
apkbox strings app.apk --only dex https    # 只扫 dex，正则过滤
apkbox strings app.apk --only native -i libfoo
apkbox strings classes.dex --min 6          # 单文件，unix strings 风格
```

- 语义同 unix `strings`：stdout 出串（zip 输入带 `条目名: ` 前缀），stderr 出
  统计，直接管道给 grep。
- 扫 APK 时按条目解压后逐个扫，`--only` 限定类别（dex/res/assets/native/…）；
  只匹配 ASCII 可打印串，`--min` 默认最短 4，`--icase` 忽略大小写。

## 只抽一类文件

```bash
apkbox extract app.apk native -o libs/    # 只要 .so（或 dex/res/assets/…）
apkbox extract app.apk manifest           # manifest 文本（见下）
apkbox extract app.aar all -o out/        # 整包解出
apkbox extract app.apk all --list         # 只列出条目（大小+路径），不写文件
apkbox extract app.apk all --glob 'lib/*' # 通配符过滤，和类别条件叠加
```

- 类别：`dex` / `res` / `assets` / `native` / `manifest` / `meta` / `all`，
  默认输出到 `<输入名>-<类别>/`。
- 防 zip-slip：拒绝 `..` 和绝对路径条目（会提示跳过了几个）。
- `--list` 不建目录不写文件；`--glob` 是 fnmatch 通配符（`*` / `?` / `[seq]`）。
- `manifest` 对 APK 用 `aapt2 dump xmltree` 转成 `AndroidManifest.txt`（文本格式，
  不是 XML 源文件）；没有 aapt2 时退成原始二进制并提示。AAR 的 manifest 本就是
  文本，原样抽出。想直接拿可读 XML 用 `apkbox manifest`。

## 界面语言

按系统 locale 自动选中文/英文，也可以强制指定：

```bash
apkbox --lang en java app.apk      # --lang 放子命令前后都行
APKBOX_LANG=en apkbox app.apk      # 环境变量
```

## Python API

```python
from apkbox import (convert, decompile_to_java, run_info, run_size,
                    run_diff, run_classes, run_members, run_dexinfo,
                    run_manifest, run_xml, run_components, run_icon,
                    run_check, run_strings, run_extract, ToolError)

convert("app.apk")                                   # → app.aar，返回退出码
convert("app.apk", "dist/app.aar", code_only=True)   # 只要 classes.jar
info = decompile_to_java("app.apk", "src/")          # 返回统计 dict
print(info["java_files"], info["errors"], info["log_file"])
run_info("app.apk")                                  # 打印 APK 摘要，返回退出码
run_size("app.apk")                                  # 体积分析
run_diff("old.apk", "new.apk")                       # 0 一致 / 1 有差异 / 2 输入错误
run_classes("app.apk", "com.foo")                    # 类名（stdout 打印）
run_members("app.apk", "MainActivity")               # 字段/方法
run_dexinfo("app.apk")                               # 每 dex 类/方法/字段数
run_manifest("app.apk")                              # 清单转可读 XML
run_xml("app.apk", "res/xml/network_security_config.xml")  # 任意条目转可读 XML
run_components("app.apk", exported_only=True)        # exported 组件审计
run_icon("app.apk")                                  # 抽启动图标
run_check("app.apk")                                 # 完整性体检
run_strings("app.apk", "https", only="dex")          # 在 dex 里找字符串
run_extract("app.apk", "native", "libs/")            # 抽原生库
run_extract("app.apk", "all", list_only=True)        # 只列出条目
```

参数与命令行同名（`convert` 的 `work_dir/code_only/keep/skip_decode/...`，
`decompile_to_java` 的 `no_res/deobf/jobs/show_bad_code/...`）。
工具级失败抛 `ToolError`；API 和命令行一样，缺工具会自动下载（`APKBOX_OFFLINE=1` 可禁止）。

## 退出码

| 码 | 含义 |
| --- | --- |
| 0 | 成功（`diff` 里表示两包完全一致；`check` 的 ⚠ 只提示不算失败） |
| 1 | 工具执行失败（外部命令报错，已打印 `error: …`）；`diff` 里表示有差异；`check` 发现损坏/缺失；`icon` 没找到图标 |
| 2 | 参数/输入文件问题 |
