Metadata-Version: 2.4
Name: apkbox
Version: 2.1.1
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.1-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     # 指定几个
  ```

环境变量：

| 变量 | 作用 |
| --- | --- |
| `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.1
  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 里做前置检查。

## 界面语言

按系统 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, 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 摘要，返回退出码
```

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

## 退出码

| 码 | 含义 |
| --- | --- |
| 0 | 成功 |
| 1 | 工具执行失败（外部命令报错，已打印 `error: …`） |
| 2 | 参数/输入文件问题 |
