-
Notifications
You must be signed in to change notification settings - Fork 7
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..."从 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"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
cd /workspace
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64 \
ANDROID_HOME=/root/android/sdk \
./gradlew assembleDebug- 首次构建约需 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 即可。