Android · build systems

Building Android apps on an ARM64 phone

No laptop, no emulator, no x86 machine. Gradle on a Galaxy S24 FE inside Termux — and the four traps that each cost an afternoon.

Why this is not supposed to work

Google ships its Linux Android SDK build tools for x86-64. On aarch64, sdkmanager installs fine, the platform zips unpack fine, and then the build dies with:

AAPT2 aapt2-9.1.1-14792394-linux Daemon #0: Daemon startup failed

That message appears repeatedly across process*DebugResources and library AAR transforms. It is not a resource bug. The aapt2 binary that Gradle fetched from Maven is an x86-64 ELF.

Trap 1 — the aapt2 barrier

Everything else in the toolchain is Java and therefore architecture-independent: d8, r8, zipalign, apksigner. aapt2 is the only native binary in the critical path, so it is the only one that needs replacing.

Termux ships an aarch64 build. Point Gradle at it in gradle.properties:

android.aapt2FromMavenOverride=/data/data/com.termux/files/usr/bin/aapt2

Verify it is the right architecture before trusting it:

$ aapt2 version
2.20-android-16.0.0_r4
Add this line the moment a project fails with “Daemon startup failed”. Debugging resources before checking the binary's architecture is the most common wasted hour in this setup.

Trap 2 — compileSdk 37 is invisible to sdkmanager

sdkmanager "platforms;android-37" answers “Failed to find package”, which reads like the platform does not exist. It does. Google moved the repository XML to a v4 schema that older cmdline-tools cannot parse, and the package is now named with a minor version: platform-37.2_r01.zip.

curl -sL -o /tmp/platform-37.2.zip \
  https://dl.google.com/android/repository/platform-37.2_r01.zip
unzip -q /tmp/platform-37.2.zip -d "$ANDROID_HOME/platforms"

The zip nests one android-37.2/ level. Move its contents up, or AGP will not see the platform. Then keep compileSdk = "37" — AGP 9.x resolves the .2 minor itself.

Do not “fix” a missing SDK by lowering compileSdk. Modern AndroidX artifacts check the AAR metadata and refuse to build: material-ripple 1.12.0-beta01 requires 37, and downgrading produces 14 AAR metadata errors instead of one missing-package error.

Trap 3 — toolchain spec is not JAVA_HOME

Setting JAVA_HOME to JDK 21 does not satisfy a Gradle toolchain request for languageVersion = 17. Gradle looks for an installed 17 and fails with a message that suggests the JDK is missing entirely. On Termux the JDKs live under:

/data/data/com.termux/files/usr/lib/jvm/java-17-openjdk
/data/data/com.termux/files/usr/lib/jvm/java-21-openjdk
org.gradle.java.installations.paths=/data/data/com.termux/files/usr/lib/jvm/java-17-openjdk

Guessing a Linux distribution path (/usr/lib64/jvm/...) is the usual failure: it does not exist in the proot, and Gradle reports the same “no matching toolchain” error.

Trap 4 — the configuration cache replays stale failures

After fixing the JDK path, the identical error comes back. The configuration cache stored the failed task graph and replays it. After any toolchain or environment change:

rm -rf .gradle/configuration-cache
./gradlew assembleDebug --no-daemon --no-configuration-cache

Also disable the daemon in a container or proot: a daemon that outlives the session holds stale environment variables.

What actually worked

What did not

Checklist

symptomcausefix
“aapt2 Daemon startup failed”x86-64 aapt2 from Mavenandroid.aapt2FromMavenOverride
“Failed to find package android-37”v4 repo XML, minor-version naminghand-install platform-37.2_r01.zip
“No matching toolchain”JAVA_HOME ≠ toolchain 17org.gradle.java.installations.paths
Same error after a fixconfiguration cache replay--no-configuration-cache