简介:Android SDK Build-Tools 29.0.2 是面向 Android 应用开发者、构建工程师及移动端技术学习者的核心构建工具集,对应 Android 10(API 级别 29)版本,用于完成资源编译、字节码转换、代码混淆优化、APK 对齐与签名等构建环节。压缩包共 173 个文件,约 37.16MB,包含 85 个 h 头文件、24 个 so 动态库、17 个 rsh 脚本、13 个 exe 可执行程序、7 个 jar 包及 6 个 dll 等,覆盖 AAPT2、d8/dx、R8、Zipalign、apksigner 等关键工具的运行依赖。已有 1372 人学习下载,说明其在构建环境搭建与版本适配中具有实际参考价值。读者可借此理解各构建工具的组成与调用关系,掌握资源打包、dex 生成、代码缩减混淆、APK 对齐签名等流程,为排查构建报错、优化包体大小与提升构建效率提供依据,适合需要深入理解 Android 构建链路的开发者研读。
1. 从一次 Gradle 构建失败说起:build-tools.29.0.2.zip 到底是什么
如果你在 Android 项目里见过Failed to find Build Tools revision 29.0.2或者Installed Build Tools revision 29.0.2 is corrupted,那你大概率已经和这个包打过照面了。build-tools.29.0.2.zip是 Android SDK 构建工具链中一个具体版本的离线压缩包,解压后得到的是aapt2、d8、zipalign、apksigner等一整套命令行工具。它不包含 Android 平台框架,也不包含模拟器镜像,只负责把源码和资源「加工」成可安装的 APK 或 AAB。适合两类人:一是 CI 环境里需要固定构建工具版本、不想每次从远端拉取的工程师;二是本地 SDK 目录被污染、需要手动替换某个版本工具链的开发者。这个包解决的核心问题就一个——让构建工具版本可控、可复现、可离线部署。
2. 拆开这个 zip:目录结构、关键二进制与版本对应关系
2.1 解压后你会看到什么
把build-tools.29.0.2.zip解压到任意目录,典型的顶层结构是这样的:
build-tools/29.0.2/ ├── aapt2 ├── apksigner ├── d8 ├── dx ├── lib/ │ └── d8.jar ├── lib64/ ├── zipalign ├── source.properties └── ...其中source.properties是版本锚点文件,内容通常包含Pkg.Revision=29.0.2。Android Gradle Plugin 在解析 SDK 时,会读取这个文件来确认构建工具版本是否匹配。如果你手动拷贝目录却漏了这个文件,或者文件里的版本号对不上,Gradle 就会报「revision not found」。aapt2负责资源编译和链接,d8负责把 Java 字节码转成 DEX,zipalign做 APK 对齐,apksigner做签名。这四个是日常构建里出场率最高的。
2.2 为什么是 29.0.2 而不是别的版本
Build Tools 29.0.2 对应的是 Android 10(API 29)时代的工具链,但它并不强制你只能编译 API 29 的项目。构建工具版本和compileSdkVersion是解耦的——你可以用 29.0.2 的aapt2去编译compileSdkVersion 33的项目,只要资源格式没有用到更高版本才引入的特性。很多老项目锁死 29.0.2 是因为升级构建工具后aapt2对某些自定义资源属性的处理变了,导致编译报错。常见做法是:在项目根目录的build.gradle里显式指定buildToolsVersion "29.0.2",然后把这个 zip 预置到 CI 的 SDK 目录里,避免每次构建都去下载。
2.3 手动部署到 SDK 目录的完整步骤
假设你的 Android SDK 根目录是$ANDROID_HOME,标准做法是把解压后的29.0.2目录放到$ANDROID_HOME/build-tools/下面。操作如下:
# 设定 SDK 根目录,按你的实际路径改 export ANDROID_HOME=/opt/android-sdk # 创建 build-tools 目录(如果不存在) mkdir -p "$ANDROID_HOME/build-tools" # 解压 zip 到临时目录 unzip build-tools.29.0.2.zip -d /tmp/bt292 # 把 29.0.2 目录移动到 SDK 的 build-tools 下 mv /tmp/bt292/29.0.2 "$ANDROID_HOME/build-tools/29.0.2" # 给可执行文件加权限 chmod +x "$ANDROID_HOME/build-tools/29.0.2/"*这段脚本的逻辑很直白:先确保目标父目录存在,再解压到临时位置,最后移动并赋权。参数上唯一需要留意的是ANDROID_HOME必须指向 SDK 根目录,而不是build-tools本身。赋权那一步在 Linux/macOS 的 CI 环境里是必须的,否则aapt2会因为权限不足直接报Permission denied。Windows 下不需要chmod,但要确保解压后的aapt2.exe没有被杀毒软件拦截。
2.4 验证部署是否生效
部署完别急着跑 Gradle,先用命令行直接调一下aapt2和d8,确认二进制能正常执行:
# 检查 aapt2 版本 "$ANDROID_HOME/build-tools/29.0.2/aapt2" version # 检查 d8 是否能打印帮助 "$ANDROID_HOME/build-tools/29.0.2/d8" --help | head -5 # 查看 source.properties 确认版本号 cat "$ANDROID_HOME/build-tools/29.0.2/source.properties"如果aapt2 version输出了Android Asset Packaging Tool (aapt) 2.x之类的信息,说明二进制本身没问题。d8 --help能打印出用法说明,说明 Java 运行环境也能被正确调用。最后source.properties里必须能看到Pkg.Revision=29.0.2,这是 Gradle 识别版本的关键。三步都过了,再跑./gradlew assembleDebug基本就不会再报构建工具缺失了。
3. 在 Gradle 与 CI 里锁定 29.0.2:配置写法与缓存策略
3.1 Gradle 层面的版本锁定
在模块级build.gradle里显式声明构建工具版本,是最直接的锁定方式:
android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.example.app" minSdkVersion 21 targetSdkVersion 29 } }buildToolsVersion这一行就是告诉 AGP:别自己去挑最新版,就用 29.0.2。如果不写,AGP 会默认使用它自己推荐的一个版本,不同 AGP 版本推荐值不一样,容易导致本地和 CI 行为不一致。参数上,compileSdkVersion和buildToolsVersion不需要相同,但为了减少变量,老项目通常把两者对齐到 29。注意:从 AGP 7.0 开始,buildToolsVersion的默认行为有调整,但显式指定依然有效。
3.2 CI 环境里的预置与缓存
在 CI 里每次从远端拉构建工具是构建时间的大头。常见做法是把build-tools.29.0.2.zip作为构建缓存的一部分预置到镜像里,或者用 CI 的缓存机制把$ANDROID_HOME/build-tools/29.0.2整个目录缓存起来。以 GitHub Actions 为例,可以这样写:
- name: Cache Android Build Tools uses: actions/cache@v3 with: path: /usr/local/lib/android/sdk/build-tools/29.0.2 key: build-tools-29.0.2-${{ runner.os }}这个缓存步骤的逻辑是:如果缓存命中,直接复用已有的 29.0.2 目录;如果没命中,后续步骤需要自己解压 zip 并放到对应路径。key里带上操作系统,是因为不同系统的二进制不通用。参数上,path必须和实际 SDK 路径一致,GitHub 托管 runner 的 SDK 路径通常是/usr/local/lib/android/sdk,自建 runner 要按实际情况改。
3.3 用 sdkmanager 离线安装的替代方案
除了手动解压,Android SDK 自带的sdkmanager也支持从本地 zip 安装。前提是你已经把 zip 放到了某个本地目录:
# 假设 zip 放在 /tmp/packages/ sdkmanager --sdk_root="$ANDROID_HOME" \ --install "build-tools;29.0.2" \ --package_file=/tmp/packages/build-tools.29.0.2.zip不过sdkmanager对离线包的文件名和内部结构有要求,不是随便一个 zip 都能被识别。如果报Failed to read package,大概率是 zip 内部缺少package.xml或者目录层级不对。这时候退回手动解压反而更省事。我一般会在 CI 脚本里先试sdkmanager,失败就 fallback 到unzip加mv,两条路都留着。
3.4 多版本共存时的路径隔离
一台机器上同时存在 29.0.2、30.0.3、33.0.1 是常态。$ANDROID_HOME/build-tools/下每个版本一个目录,互不干扰。Gradle 会根据buildToolsVersion去对应目录找。需要留意的是环境变量PATH里不要硬编码某个版本的aapt2,否则命令行手动执行和 Gradle 内部调用可能走的不是同一个二进制。常见做法是:PATH里只放$ANDROID_HOME/platform-tools和$ANDROID_HOME/cmdline-tools/latest/bin,构建工具的具体版本交给 Gradle 去解析。
4. 避坑与排查:构建工具版本不匹配的五个血泪现场
4.1 现象:Failed to find Build Tools revision 29.0.2
原因通常有三种:目录根本没放对位置、source.properties缺失或版本号写错、目录权限不对导致 Gradle 无法读取。解决方式是先确认$ANDROID_HOME/build-tools/29.0.2/source.properties存在且内容包含Pkg.Revision=29.0.2,再检查目录权限是否为可读可执行。如果是 CI 容器,还要确认ANDROID_HOME环境变量在构建步骤里确实被 export 了。
4.2 现象:Installed Build Tools revision 29.0.2 is corrupted
这个报错比「找不到」更隐蔽。常见原因是 zip 解压不完整,比如aapt2文件大小为 0,或者lib/d8.jar缺失。另一个原因是手动替换了某个二进制但没替换配套的lib64目录,导致运行时链接失败。解决方式是重新解压一份完整的 zip,覆盖整个29.0.2目录,不要只替换单个文件。如果覆盖后仍然报错,检查磁盘是否写满。
4.3 现象:aapt2执行时报cannot execute binary file
这通常发生在跨平台拷贝的场景:把 Linux 版的构建工具拷到了 macOS 上,或者反过来。build-tools.29.0.2.zip里的二进制是平台相关的,Linux 版和 macOS 版不能混用。解决方式是确认 zip 的来源平台和当前系统一致。如果是在 Docker 里构建,基础镜像的架构(amd64/arm64)也要和二进制匹配,否则会报Exec format error。
4.4 现象:Gradle 构建成功但 APK 签名异常
有时候构建工具目录里混入了旧版本的apksigner,导致签名算法和预期不一致。比如 29.0.2 的apksigner默认使用 v1+v2 签名,但如果 PATH 里优先找到了 28.x 的apksigner,可能只签了 v1,安装到 Android 10 以上设备会报INSTALL_PARSE_FAILED_NO_CERTIFICATES。解决方式是显式指定apksigner的绝对路径,或者在build.gradle的signingConfigs里确认enableV2Signing true。
4.5 现象:CI 缓存命中后构建反而变慢
缓存目录里可能包含了上一次构建产生的临时文件,导致 Gradle 在扫描构建工具目录时做了多余的文件遍历。解决方式是缓存时只保留build-tools/29.0.2下的必要文件,或者干脆缓存整个 zip 而不是解压后的目录,每次构建时重新解压。后者虽然多了一步解压,但目录状态更干净,不容易出现「缓存污染」导致的玄学问题。
5. 进阶:用校验和与最小化目录做可复现构建
5.1 给 zip 加一道校验
离线包在传输过程中可能损坏,解压前先校验 SHA-256 是最省事的后悔药:
# 计算 zip 的 SHA-256 sha256sum build-tools.29.0.2.zip # 和已知正确的哈希比对(示例值,实际以你手里的包为准) # 如果哈希不一致,说明文件损坏或被篡改,不要继续解压这一步在 CI 里尤其值得加,因为缓存和制品在多个节点之间同步时,偶发的位翻转会导致后续构建出现难以定位的aapt2崩溃。把哈希校验放在解压之前,能把问题拦在最前面。
5.2 最小化构建工具目录
如果你只需要编译和打包,不需要lldb、renderscript等附带工具,可以在解压后删掉不用的目录,减小缓存体积:
| 文件/目录 | 是否必需 | 说明 |
|---|---|---|
aapt2 | 必需 | 资源编译与链接 |
d8/lib/d8.jar | 必需 | Java 字节码转 DEX |
zipalign | 必需 | APK 对齐 |
apksigner | 必需 | APK 签名 |
dx | 可选 | 旧版 DEX 工具,d8 已替代 |
renderscript | 可选 | RS 脚本编译,多数项目不用 |
lldb | 可选 | 原生调试,CI 构建不需要 |
按这张表裁剪后,目录体积能缩小不少,CI 缓存和传输的耗时也会下降。注意lib/和lib64/下的 jar 包不要随便删,d8和apksigner运行时依赖它们。
5.3 用 Docker 固化整个工具链
最彻底的可复现方式是把 SDK 和构建工具一起打进 Docker 镜像:
FROM openjdk:8-jdk-slim ENV ANDROID_HOME=/opt/android-sdk ENV PATH=$PATH:$ANDROID_HOME/build-tools/29.0.2 COPY build-tools.29.0.2.zip /tmp/ RUN mkdir -p $ANDROID_HOME/build-tools \ && unzip /tmp/build-tools.29.0.2.zip -d /tmp/bt \ && mv /tmp/bt/29.0.2 $ANDROID_HOME/build-tools/29.0.2 \ && chmod +x $ANDROID_HOME/build-tools/29.0.2/* \ && rm /tmp/build-tools.29.0.2.zip这个 Dockerfile 把解压、移动、赋权、清理串成一条链,构建出的镜像里构建工具版本是固定的。参数上,ENV PATH里加上build-tools/29.0.2是为了让命令行直接能调到aapt2,但前面提过,Gradle 构建时其实不依赖 PATH,所以这行更多是方便手动排查。rm那一步别忘了,否则镜像里会多留一份 zip。
5.4 一个我常做的验证习惯
每次在 CI 里更新了构建工具缓存或者换了基础镜像,我都会强制跑一遍aapt2 version和d8 --help,再跑一个最小的assembleDebug。这三步走完,基本能确认工具链是活的。从那以后我每次改 CI 的 SDK 相关配置,都强制走一遍这个验证流程,省得构建失败时在日志里大海捞针。希望帮到你。
本文还有配套的精品资源,点击获取