Skip to content

在容器中编译 Android 应用 ​

在 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
JDKOpenJDK 17
Gradle项目 wrapper 自带,无需系统安装
SDKAndroid 36 / build-tools 35.0.0
aapt2ARM64 静态编译版 35.0.2(社区构建)

1. 安装基础依赖 ​

bash
apt update
apt install -y openjdk-17-jdk-headless curl wget unzip ripgrep

验证 JDK:

bash
/usr/lib/jvm/java-17-openjdk-arm64/bin/java -version
# 应输出 openjdk version "17..."

2. 安装 Android SDK ​

默认使用国内镜像安装(下载更快、更稳)。除 sdkmanager 本体(cmdline-tools)需手工下载解压外,其余组件通过 SDK_TEST_BASE_URL 环境变量覆盖 sdkmanager 的默认仓库根 URL(需以 / 结尾,镜像需提供 repository2-3.xml),一条命令全部装完(--list 与 --install 均已实测可用):

bash
# 唯一手工步骤:获取 sdkmanager 本体(从镜像下载)
mkdir -p ~/android/sdk && cd ~/android/sdk
curl -fL -C - -o clt.zip https://mirrors.cloud.tencent.com/AndroidSDK/commandlinetools-linux-13114758_latest.zip
unzip -q clt.zip
mkdir -p cmdline-tools/latest && mv cmdline-tools/* cmdline-tools/latest/

# 组件全部走镜像安装(自动处理目录布局与许可接受)
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64
export ANDROID_HOME=$HOME/android/sdk
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
export SDK_TEST_BASE_URL=https://mirrors.cloud.tencent.com/AndroidSDK/

yes | sdkmanager --install "platforms;android-36" "build-tools;35.0.0" "platform-tools"

版本说明

按目标项目的 compileSdk 和 buildToolsVersion 选择组件版本,本文示例为 Android 36 / build-tools 35.0.0。

镜像与官方源

SDK_TEST_BASE_URL 虽为测试用途的环境变量(非官方文档化的常规配置),但已被镜像方案广泛使用。镜像安装的 SDK 与官方安装完全等价:即使网络可直接访问 dl.google.com,也无需还原为官方流程。若该方式在特定场景下失效,可改用官方源(见下)或手工组装。

可选:官方源安装 ​

若网络可直连 dl.google.com,也可使用官方标准流程:

bash
mkdir -p ~/android/sdk && cd ~/android/sdk
curl -O https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip
unzip -q commandlinetools-linux-*.zip
mkdir -p cmdline-tools/latest && mv cmdline-tools/* cmdline-tools/latest/

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64
export ANDROID_HOME=$HOME/android/sdk
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH

yes | sdkmanager --install "platforms;android-36" "build-tools;35.0.0" "platform-tools"

备选:从镜像手工组装 SDK ​

若 SDK_TEST_BASE_URL 方式不可用,可从腾讯云镜像下载组件 zip 手工组装,最终得到 build-tools/、platforms/、platform-tools/ 三个目录,效果与 sdkmanager 一致:

组件镜像地址
commandlinetoolshttps://mirrors.cloud.tencent.com/AndroidSDK/commandlinetools-linux-13114758_latest.zip
platform-36https://mirrors.cloud.tencent.com/AndroidSDK/platform-36_r01.zip
build-tools 35.0.0https://mirrors.cloud.tencent.com/AndroidSDK/build-tools_r35_linux.zip
platform-toolshttps://mirrors.cloud.tencent.com/AndroidSDK/platform-tools_r35.0.2-linux.zip
bash
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/ 三个目录

3. 替换 ARM64 原生二进制 ​

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 下载。下载缓慢或失败时,可以在原链接前加 GitHub 代理前缀(实测可用,2026-08):

加速服务用法
gh-proxy.comhttps://gh-proxy.com/https://github.com/...
ghproxy.viphttps://ghproxy.vip/https://github.com/...
bash
# 示例:走 gh-proxy.com 加速下载
curl -L -O https://gh-proxy.com/https://github.com/lzhiyong/android-sdk-tools/releases/download/35.0.2/android-sdk-tools-static-aarch64.zip
# 容器能直连 GitHub 时可直接下载
# curl -L -O 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 ~/armtools35

替换 SDK 里的二进制:

bash
cp -p ~/armtools35/build-tools/* ~/android/sdk/build-tools/35.0.0/
cp -p ~/armtools35/platform-tools/* ~/android/sdk/platform-tools/

验证 aapt2 可执行:

bash
~/android/sdk/build-tools/35.0.0/aapt2 version
# 应输出版本号,不报错

4. 配置 ​

local.properties(项目根目录) ​

bash
echo "sdk.dir=/root/android/sdk" > /workspace/local.properties

这个文件通常已被 .gitignore 忽略,不会污染仓库。

全局 Gradle 配置 ​

aapt2 覆盖配置写入全局文件,不要写进项目的 gradle.properties:

bash
mkdir -p ~/.gradle
echo "android.aapt2FromMavenOverride=/root/android/sdk/build-tools/35.0.0/aapt2" > ~/.gradle/gradle.properties

apt 源(可选) ​

容器默认可能连不上官方源,建议换国内镜像:

URIs: http://mirrors.ustc.edu.cn/debian
URIs: http://mirrors.ustc.edu.cn/debian-security

下载 Gradle 发行版(可选) ​

项目使用 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
bash
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   # 用它执行构建或生成 wrapper

5. 编译 ​

bash
cd /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() 拉依赖):

kotlin
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-toolsAGP 联网验证失败确认本地已装项目所需的 build-tools 版本
apt 下载 403镜像源临时不可用换镜像源(清华 → 中科大)
Gradle 锁文件冲突上次构建未正常退出删除 ~/.gradle/caches/*.lock,或加 --no-daemon

补充:内置 Alpine 容器的修复 ​

在内置 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 反编译结构调整。

把 File.delete() 换成 Files.delete()(走 unlinkat 绕过底层删除失效):

java
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 即可。

基于 GPL-3.0 协议开源