-
Notifications
You must be signed in to change notification settings - Fork 13
Build Android in Proot
本指南介绍如何在 AiCode 的 Linux 容器中搭建 JDK + Android SDK 环境,从源码编译 Android 应用。以 Debian 12 (bookworm) aarch64 为例,同样适用于 Ubuntu、Arch 等发行版。
建议使用自定义 Debian/Ubuntu 镜像:内置 Alpine 容器因 musl libc 与 PRoot 的交互限制,编译 Android 应用需要额外处理(见文末补充)。自定义镜像的导入方法见自定义容器镜像。
| 项 | 值 |
|---|---|
| 系统 | Debian GNU/Linux 12 (bookworm) aarch64 |
| JDK | OpenJDK 17 |
| Gradle | 项目 wrapper 自带,无需系统安装 |
| SDK | Android 36 / build-tools 35.0.0 |
| aapt2 | ARM64 静态编译版 35.0.2(社区构建) |
apt update
apt install -y openjdk-17-jdk-headless curl wget unzip ripgrep验证 JDK:
/usr/lib/jvm/java-17-openjdk-arm64/bin/java -version
# 应输出 openjdk version "17..."下载慢时可使用以下国内镜像:
组件 镜像地址 commandlinetools https://mirrors.cloud.tencent.com/AndroidSDK/commandlinetools-linux-13114758_latest.zipplatform-36 https://mirrors.cloud.tencent.com/AndroidSDK/platform-36_r01.zipbuild-tools 35.0.0 https://mirrors.cloud.tencent.com/AndroidSDK/build-tools_r35_linux.zipplatform-tools https://mirrors.cloud.tencent.com/AndroidSDK/platform-tools_r35.0.2-linux.zip
从 https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip(慢则用上方腾讯云镜像)下载,放到工作区后解压:
mkdir -p ~/android/sdk
unzip /workspace/commandlinetools-linux-*.zip -d ~/android/sdk
mkdir -p ~/android/sdk/cmdline-tools/latest
mv ~/android/sdk/cmdline-tools/bin ~/android/sdk/cmdline-tools/lib ~/android/sdk/cmdline-tools/NOTICE.txt ~/android/sdk/cmdline-tools/source.properties ~/android/sdk/cmdline-tools/latest/根据目标项目的 compileSdk 和 buildToolsVersion 选择对应组件:
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64
export ANDROID_HOME=/root/android/sdk
export PATH=$JAVA_HOME/bin:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
yes | sdkmanager --install "platforms;android-36" "build-tools;35.0.0" "platform-tools"sdkmanager 的下载源固定为 dl.google.com,无法改用镜像。下载反复失败时,可从腾讯云镜像下载组件 zip 手工解压组装(最终生成 build-tools/、platforms/、platform-tools/ 三个目录,与 sdkmanager 安装结果一致):
cd /workspace
curl -L -O https://mirrors.cloud.tencent.com/AndroidSDK/platform-36_r01.zip
curl -L -O https://mirrors.cloud.tencent.com/AndroidSDK/build-tools_r35_linux.zip
curl -L -O https://mirrors.cloud.tencent.com/AndroidSDK/platform-tools_r35.0.2-linux.zip
# 1) build-tools:zip 解压出的目录是 android-15/,把内容移到 build-tools/35.0.0/
unzip build-tools_r35_linux.zip -d ~/android/sdk
mkdir -p ~/android/sdk/build-tools/35.0.0
mv ~/android/sdk/android-15/* ~/android/sdk/build-tools/35.0.0/
# 2) platform:解压出 android-36/
unzip platform-36_r01.zip -d ~/android/sdk
mkdir -p ~/android/sdk/platforms
mv ~/android/sdk/android-36 ~/android/sdk/platforms/android-36
# 3) platform-tools:解压即得 platform-tools/,无需移动
unzip platform-tools_r35.0.2-linux.zip -d ~/android/sdk
ls ~/android/sdk # 应看到 build-tools/ platforms/ platform-tools/ 三个目录Google 官方的 aapt2、adb 等工具为 x86_64 编译,在 aarch64 上无法执行,需用社区维护的 ARM64 静态编译版本替换。
从 https://github.com/lzhiyong/android-sdk-tools/releases/download/35.0.2/android-sdk-tools-static-aarch64.zip 下载,放到工作区后解压:
unzip /workspace/android-sdk-tools-static-aarch64.zip -d ~/armtools35cp -p ~/armtools35/build-tools/* ~/android/sdk/build-tools/35.0.0/
cp -p ~/armtools35/platform-tools/* ~/android/sdk/platform-tools/验证 aapt2 可执行:
~/android/sdk/build-tools/35.0.0/aapt2 version
# 应输出版本号,不报错echo "sdk.dir=/root/android/sdk" > /workspace/local.properties此文件通常已被
.gitignore忽略,不会污染仓库。
aapt2 覆盖配置写入全局文件,不写入项目的 gradle.properties:
mkdir -p ~/.gradle
echo "android.aapt2FromMavenOverride=/root/android/sdk/build-tools/35.0.0/aapt2" > ~/.gradle/gradle.properties容器默认可能连不上官方源,建议换国内镜像:
URIs: http://mirrors.ustc.edu.cn/debian
URIs: http://mirrors.ustc.edu.cn/debian-security
项目使用 Gradle wrapper 时无需系统 Gradle,但首次从零搭建项目时需先下载发行版。官方源 https://services.gradle.org/distributions/gradle-8.11.1-bin.zip 慢的话可用以下镜像:
- 华为云:
https://mirrors.huaweicloud.com/gradle/gradle-8.11.1-bin.zip - 腾讯云:
https://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip
curl -L -o /workspace/gradle.zip https://mirrors.huaweicloud.com/gradle/gradle-8.11.1-bin.zip
unzip gradle.zip -d /workspace/tools
/workspace/tools/gradle-8.11.1/bin/gradle --version # 用它跑构建或生成 wrappercd /workspace
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64 \
ANDROID_HOME=/root/android/sdk \
./gradlew assembleDebug依赖下载较慢时,可在
settings.gradle.kts的dependencyResolutionManagement.repositories中改用阿里云镜像(AGP 默认从google()/mavenCentral()拉取依赖):maven { url = uri("https://maven.aliyun.com/repository/google") } maven { url = uri("https://maven.aliyun.com/repository/central") } maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
- 首次构建约需 10-15 分钟(下载依赖 + Kotlin 编译),后续增量构建约 1-2 分钟。
- Gradle daemon 在 PRoot 下可正常工作,加速增量构建。如遇 daemon 异常退出导致锁文件冲突,可加
--no-daemon单次禁用。
成功后的 APK 输出路径取决于项目配置,通常在:
app/build/outputs/apk/<flavor>/debug/app-<flavor>-debug.apk
| 现象 | 原因 | 解决 |
|---|---|---|
JAVA_HOME is not set |
未设环境变量 | 命令行加 JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64
|
SDK location not found |
缺 local.properties
|
创建 local.properties 写入 sdk.dir=/root/android/sdk
|
aapt2 syntax error
|
用了 x86 版 aapt2 | 确认 ~/.gradle/gradle.properties 里有 aapt2FromMavenOverride 指向 ARM 版 |
Failed to install build-tools |
AGP 联网验证失败 | 确认本地已装项目所需的 build-tools 版本 |
| apt 下载 403 | 镜像源临时不可用 | 换镜像源(清华 → 中科大) |
| Gradle 锁文件冲突 | 上次构建未正常退出 | 删除 ~/.gradle/caches/*.lock,或加 --no-daemon
|
内置 Alpine 容器编译 Android 应用时,可能遇到 Failed to delete /tmp/tempdir_* 错误。原因是 AGP 的 apkzlib 中 TemporaryFile 使用 File.delete() 删除临时目录,在 PRoot 环境下该操作可能失败。自定义 Debian 镜像不受此问题影响。
下述代码适用于 Gradle 8.11.1 / AGP 8.x 中的
apkzlib(包名com.android.tools.build.apkzlib.bytestorage.TemporaryFile)。不同版本包路径与内部结构可能不同,请根据实际使用的 jar 反编译结构调整。
解决方法:将 TemporaryFile 的 File.delete() 改为 Files.delete()(走 unlinkat 绕过底层删除失效):
package com.android.tools.build.apkzlib.bytestorage;
import com.google.common.base.Preconditions;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
public class TemporaryFile implements Closeable {
private boolean deleted;
private final File file;
public TemporaryFile(File file) {
this.deleted = false;
this.file = file;
}
public File getFile() {
Preconditions.checkState(!this.deleted, "File already deleted");
return this.file;
}
@Override
public void close() throws IOException {
if (this.deleted) {
return;
}
this.deleted = true;
deleteFile(this.file);
}
private void deleteFile(File f) throws IOException {
if (f.isDirectory()) {
File[] kids = f.listFiles();
if (kids != null) {
for (File kid : kids) {
deleteFile(kid);
}
}
}
if (f.exists()) {
try {
Files.delete(f.toPath());
} catch (NoSuchFileException e) {
// 已存在则忽略
} catch (IOException e) {
if (!f.delete()) {
throw new IOException("Failed to delete '" + f.getAbsolutePath() + "'", e);
}
}
}
}
}编译该源码,将 .class 文件替换回 Gradle 缓存中的 instrumented-apkzlib-*.jar 即可。