华为畅享 70S,Android 12,aarch64,Termux 里跑的,没有 root。下面每条命令都在这台机器上执行过。

改完代码要出包,平时抬手就开电脑。那天人不在桌前,就想在手机上试试能不能就地编出来。

Android 那套编译工具,官方只认 x86-64 的 Linux。手机是 aarch64,从来不在支持范围里。所以我一开始当它不可能,纯当玩。

结果它成了。不是编个 hello world 那种成,是 Gradle 加 AGP 完整走完一个工程,签名、zipalign 全过。

三个坑都在下面,按我踩到的顺序写。

先说这台机器的底子


机型华为畅享 70S,Android 12,EMUI。Termux 是常规版,路径 /data/data/com.termux/files/home,没 root。

磁盘 105G,剩 21G。内存 7.6G,可用 3G 上下。这配置在手机上算宽裕,对编译来说不算多,后面 SDK 和 Gradle 缓存吃掉将近 1G。


看第二行那个 aarch64 就够了,后面所有麻烦都是它派生的


关键信息只有一条:架构是 aarch64。官方不支持,所以工具链里到处是给电脑准备的东西,得一个一个换掉。

第一个坑:aapt2 一起来就崩


先装一圈:

pkg install openjdk-17 aapt aapt2 apksigner d8 aidl android-tools gradle


装完核版本:JDK 21.0.13,Gradle 9.8.2,d8 和 apksigner 都是 37.0.0,adb 37.0.0p1。aapt 和 aapt2 是 16.0.0_r4,这个包是 ARM64 原生的,能用。

你装的时候版本号大概不一样,仓库一直在动。真正要盯的是三者匹配,Gradle 9.8 配 AGP 9.5,JDK 不能低于 17。这个组合是我试出来的,别的不敢打包票。

SDK platform 和 build-tools 不在 pkg 里,得自己下。platform 我下了 34 和 36,build-tools 下的 36.0.0。

装完随手敲了一句 aapt2 version,它直接退出:

CANNOT LINK EXECUTABLE "aapt2": cannot locate symbol
"_ZNSt6__ndk113__hash_memoryEPKvm" referenced by
"/data/data/com.termux/files/usr/bin/aapt2"...


那串 mangled name 读着费劲,意思很直白:aapt2 要调 libc++ 里的一个函数,这台机器的 libc++ 里没有。

查下来,装的是 libc++ 27c,Termux 仓库已经到 30 了。aapt2 按新版本编,链接到旧库就找不到符号。

pkg install --reinstall libc++


重装完就好。同一条报错 adb 也报,修一次两个都正常。

这个坑记住一句话:看到 cannot locate symbol 后面跟一串怪名字,先怀疑 C++ 运行库版本,别去找文件。文件在,是版本对不上。

第二个坑:build-tools 里全是给电脑的东西


aapt2 好了,我以为通了。打开 build-tools 目录才发现麻烦更大。

$ file ~/android-sdk/build-tools/36.0.0/aapt2
ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked


x86-64。手机是 aarch64,跑不了。aapt、aidl 一个样。Google 只发 linux-x86_64 的包,从来没出过 arm 版。

aapt、aapt2、aidl 这三个,Termux 仓库里有原生的,我写成 wrapper 把活转过去:

#!/data/data/com.termux/files/usr/bin/bash
exec /data/data/com.termux/files/usr/bin/aapt2 "$@"


存成 build-tools/36.0.0/aapt2,原来的挪开,chmod 755。aapt 和 aidl 照做。

zipalign 没有替代品,仓库里也没有。它干的事其实不复杂:把 APK 里每个条目的起始位置对齐到 4 字节,安卓加载 so 的时候才能直接 mmap。不做的包照样能装,只是 so 得先拷进内存再用;Play 上架是硬性要求,自己装没那么严。

我自己用 Python 写了一个。核心是往 local file header 的 extra field 里塞对齐标记,0xd935 是 zipalign 自己的 ID:

# 0xd935 是对齐标记,后面 2 字节是数据长度,再补零凑够
extra = struct.pack('

插进去之后所有偏移都变了,central directory 和 EOCD 里那几处得跟着改。这两步我漏过一次,apksigner 直接拒绝签,报 zip 结构错。

完整脚本在 ~/sdk-test/zipalign.py。够用,但只处理了 store 和 deflate 两种,别的压缩方式没测。


左边原样,右边换完的。下面两个坑是连着踩的,一起看省时间


第三个坑:AGP 说这个目录是坏的


工具全换成能跑的了,我开 Gradle。结果 AGP 不干:

* What went wrong:
  Build Tools revision 36.0.0 is corrupted.


原因是我上一步太狠,把 x86 的二进制全删了。AGP 根本不看你这个文件能不能执行,它按清单核对目录里有没有,少一个就判损坏。

补回去就行:dexdump、split-select、lld,还有几个 *-ld,原样放回。只让 aapt、aapt2、aidl 走我的 wrapper。这几样构建全程不会被真调用,占着位置就够了。

这一步花的时间比我预想的多,因为我一开始不信它只是在校验存在性,反复查 hash、查权限、对比官方压缩包。最后发现文件在、名字对,它就放行。

先不用 Gradle,手工打一个


动 Gradle 之前,我先手工走了一遍全链路。这样哪一环坏了能立刻看出来,不用在 Gradle 那一大段输出里翻。

javac -source 8 -target 8 -bootclasspath $SDK/platforms/android-36/android.jar \
      -d classes src/com/codelion/MainActivity.java
d8 --lib $SDK/platforms/android-36/android.jar --output dex classes/
aapt2 link -o base.apk -I $SDK/platforms/android-36/android.jar --manifest AndroidManifest.xml
cd dex && zip -j ../base.apk classes.dex && cd ..
apksigner sign --ks debug.keystore --ks-pass pass:android --out app-signed.apk base.apk
apksigner verify -v app-signed.apk


跑完是 BUILD OK,产物 8.4K。apksigner 验出 V3.0 Signer: CN=CodeLion, OU=Dev, O=7Tan。

这个包没实际用途,就一个空 Activity。但它证明了一件要紧的事:javac、d8、aapt2、apksigner 这条链在手机上每一环都能跑,剩下的问题只有 Gradle。

Gradle 那条也通了


工程是电脑上写的,拷到手机只动一个文件:

# local.properties
sdk.dir=/data/data/com.termux/files/home/android-sdk


然后:

$ cd ~/agp-test && gradle assembleDebug
BUILD SUCCESSFUL in 1m 39s
33 actionable tasks: 33 executed


1 分 39 秒。比电脑慢不少,但它是真编,33 个 task 全部 executed,不是拿缓存糊过去。

产物 app-debug.apk 865K。我逐项查了:package 是 com.codelion.agp v1.0,minSdk 21、targetSdk 36,compileSdk 36 也就是 Android 16,V2 Signer OK,zipalign 校验 succesful。

组合是 AGP 9.5.0 + Gradle 9.8.2 + JDK 21 + compileSdk 36。


上面那条短的验证工具链,下面那条才是真能用的


看到 BUILD SUCCESSFUL 那一刻是有点不真实的。前面三个坑加起来的活,全是为了这一行。

能干的,和干不了的


试过的和没试过的分开写,别拿我的结论去踩空。

想干的事结果
纯 Java 工程,Gradle + AGP实测通过
手工打 APK,不要 Gradle实测通过
C / C++ 原生代码要另装 NDK,约 1G,我没装
Rust仓库里有 rust-std-*-linux-android,没试
Kotlin / Compose没试,KGP 得从 maven 现拉
Flutter / React Native没试,得先装 node
编完装到本机跑不行,没 root,pm install 没权限


最后一行是我最想试却没办成的。手机编出来的包,装不回自己这台手机。要看效果,得 adb install 到别的设备,或者拷到另一台机器上装。

所以严格讲,这不是「在手机上开发 App」,是「在手机上把包编出来」。中间还差测试和调试,那部分仍然得有电脑。

留在机器上的东西,和一句提醒


环境变量写进 ~/.android_env.sh,也塞进 .bashrc 了,新开会话自动生效:

source ~/.android_env.sh
cd ~/agp-test && gradle assembleDebug


留下的几处:环境脚本 ~/.android_env.sh;Gradle 工程 ~/agp-test;手工构建示例 ~/sdk-test,带 build.sh;SDK 在 ~/android-sdk,platforms 34 和 36,build-tools 36.0.0。占用 SDK 503M,Gradle 缓存 444M。

提醒一句:雷电模拟器是 x86 加完整 SDK,那是官方支持的路子,什么都能用。这台是 aarch64 Termux,官方不支持,所以才有二进制替换和自写 zipalign 这些事。

两套环境的 local.properties 不能互拷,sdk.dir 完全不同。Gradle 工程本体是通用的,改个路径两边都能跑。

手上的活要是急,还是用电脑。这套东西适合两种场景:人不在桌前,或者单纯想知道手机能到哪一步。




写完这段我又重编了一次,1 分 40 秒,比上面多一秒。看来瓶颈不在磁盘缓存上。

你要是也在这条路上卡过,把报错贴在下面。aarch64 我踩到的坑差不多就这三个,第四个还没碰上,也许在你那儿。