不装 Android Studio:我在服务器上手工打出 85KB 的安卓安装包

教程 2026-10-02 20:03 · 53 次阅读 ·安卓,打包,踩坑,实测

七步手工构建的实测数字:从空目录到能装的包一共 11.0 秒,最终 86,762 字节

结论先放这:这台服务器上没装 Android Studio,也没有 Gradle,我用七个命令行工具,把 16 个 Java 文件打成了一个 85KB 的安卓安装包。手机上能装,名字和图标都正常。

背景:不是不想用现成的,是装不下

机器只有 2 核、内存不到 2GB。Android Studio 是一整套图形界面。

Gradle 第一次打包还要再下几百 MB 依赖,这台机器扛不住。

我要的东西很小:一个能装到手机上的小程序,界面用代码画,不引别人的库。所以走手工路线。

工具只用官方那一小包,一共七个,顺序不能换。

  • aapt2:读界面资源和清单文件,先编译,再链接
  • javac:把 .java 编译成 class
  • d8:把 class 转成安卓才认的 classes.dex
  • python:把 dex 塞进空壳包
  • zipalign:把包里文件的位置对齐
  • apksigner:签名
  • keytool:第一次生成签名密钥时才用

做法:七步,我掐着表重跑了一遍

核心就是这段脚本(节选,能直接跑):

BT=/opt/android-sdk/build-tools/34.0.0
PLAT=/opt/android-sdk/platforms/android-34/android.jar
mkdir -p build/gen build/classes build/dex
$BT/aapt2 compile --dir res -o build/res.zip
$BT/aapt2 link -o build/base.apk -I $PLAT --manifest AndroidManifest.xml \
  --java build/gen -R build/res.zip --auto-add-overlay \
  --min-sdk-version 24 --target-sdk-version 34
javac --release 8 -encoding UTF-8 -cp $PLAT -d build/classes \
  $(find build/gen java -name '*.java')
$BT/d8 --lib $PLAT --min-api 24 --output build/dex \
  $(find build/classes -name '*.class')
$BT/zipalign -f -p 4 build/unaligned.apk build/aligned.apk
$BT/apksigner sign --ks mn.keystore --ks-pass pass:android \
  --out jizhang.apk build/aligned.apk

每步花多少时间、包涨到多大,实测是这样:

  • aapt2 compile:0.01 秒,中间文件 7,746 字节
  • aapt2 link:0.16 秒,空壳包 6,562 字节(里面还没有代码)
  • javac:4.52 秒,16 个 .java 编成 144 个 class
  • d8:5.02 秒,classes.dex 153,660 字节
  • 塞 dex:0.11 秒,78,728 字节
  • zipalign:0.01 秒,78,732 字节
  • apksigner:1.16 秒,86,762 字节

从空目录到能装的包,一共 11.0 秒。慢的是 javac 和 d8 这两步,加起来 9.5 秒,占了大头。

包为什么这么小?我拆开数过,整个包里只有 11 个条目。

没有 lib/、没有 assets/、没有 androidx,也没有一行 WebView 代码。85KB 里绝大部分是自己写的逻辑,dex 那 153,660 字节压缩后就是剩下的那点。

签名那一步不能省,安卓系统只装签过名的包。签名就像给包贴一张封条:证明是你发的,别人改过一个字节,手机就拒装。

装到手机上要在设置里允许"安装未知来源的应用";这个包最低支持安卓 7.0(代号 API 24)。

结论与踩坑

坑一:d8 不会自己建输出目录。我这次重跑就撞上了,报错原文是:

Error: Invalid output: dex
Output must be a .zip or .jar archive or an existing directory

补一句 mkdir -p build/dex 就过。

坑二:aapt2 link 出来的 base.apk 里没有代码。它只有 6,562 字节,是个空壳。

必须自己把 classes.dex 追加进去。不塞的话,装上打开就闪退。

用 python 的 zipfile 以追加模式写进去最省事,不用解压再重打。

坑三:版本号以清单文件为准,命令行写的不算。我实测过:AndroidManifest.xml 里写的是 versionCode 17、versionName 1.6.5。

我再加上 --version-code 1 --version-name 1.0 重新打包,查出来还是 17 和 1.6.5。清单文件相当于这个包的身份证,优先级更高。

后果很实在:发出去的新包,手机上的安装器以为还是老版本,不提示更新。

坑四:对齐要在签名前面。zipalign 会移动包里文件的位置,签完名再对齐,签名立刻失效。

顺序固定是:塞 dex → 对齐 → 签名。

坑五:签名只认 v2 就够了。apksigner verify -v 里 v1(旧的 JAR 方案)显示 false,v2 和 v3 显示 true,这是正常的。

最低版本写 24 以上,系统认 v2,不用为 v1 折腾。

坑六:字节数和 md5 都不能判断"包是不是新的"。我这次重编出来的包,和上一版打出来的包字节数一模一样,都是 86,762。

但 md5 不同(a66dbb2f2fd0… 对 1f4d8346ae05…),因为 zip 里存了每个文件的修改时间,重编一次它就变。

体积一样不等于没改动,md5 变了也不等于改动生效。要证明新代码真进包,直接查 dex 字节:

python3.12 -c 'import zipfile; d=zipfile.ZipFile("jizhang.apk").read("classes.dex"); print(b"你新加的字段名" in d)'

下次注意三件事:把 mkdir -p 写进脚本第一行;版本号只在 AndroidManifest.xml 里改一处;每改完一版先装到手机上点一遍,再对外发。

继续看

全部 →