Android 发行版(APK Release)构建指南
本文档记录本地音乐盒(Flutter)从「零环境」到「产出 Release APK」的完整过程, 包含所有踩过的坑和对应的解决方案。既可作为本项目的构建手册, 也可作为国内网络环境下 Flutter Android 构建的通用参考。
1. 背景
Flutter 构建 Android 发行版 APK 时会做以下几件涉及网络下载的大事:
| 步骤 | 下载内容 | 默认源 | 问题 |
|---|---|---|---|
| ① Gradle Wrapper | Gradle 发行版(~215MB) | services.gradle.org → GitHub CDN | 国内极慢 / 易卡死 |
| ② 依赖解析 | AGP / AndroidX 等 Maven 依赖 | google() / mavenCentral() | Google Maven 慢 |
| ③ NDK | Android NDK(~952MB,仅首次) | dl.google.com | 国内基本卡死 |
| ④ 插件编译 | 各 Flutter 插件的原生代码 | (本地,无下载) | 老插件有兼容性问题 |
核心教训:第①③步的下载源必须切到国内镜像,否则构建会挂死。
本次构建环境:
- Flutter
3.44.9stable - Gradle
9.1.0(wrapper 指定,本文统一切到腾讯镜像) - AGP 8.x、JDK 17
- 受影响插件:
on_audio_query_android 1.1.0、file_picker 8.0.7
2. 环境准备
2.1 依赖工具
- Flutter SDK(建议 3.4x stable)
- JDK 17(Homebrew:
brew install openjdk@17) - Android SDK(含 platform 34/36、build-tools、cmake)
2.2 环境变量(macOS/zsh)
export JAVA_HOME=$(brew --prefix openjdk@17)/libexec/openjdk.jdk/Contents/Home
export ANDROID_HOME=$HOME/Library/Android/sdk
export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk
路径请按本机实际输出调整:
brew --prefix openjdk@17的结果为 JDK 前缀,ANDROID_HOME指向 Android SDK 的实际安装位置。
2.3 构建命令
推荐把日志写到文件里再盯(不要用 | tail -40,会丢掉中间的错误详情):
flutter pub get
nohup flutter build apk --release > /tmp/flutter_build.log 2>&1 &
tail -f /tmp/flutter_build.log
产物路径:build/app/outputs/flutter-apk/app-release.apk
3. 坑①:Gradle 发行版下载卡死(必须切镜像)
现象:构建一直停在 Running Gradle task 'assembleRelease'...,
进程 10 多分钟几乎不耗 CPU;~/.gradle/wrapper/dists/ 下只有
gradle-9.1.0-all.zip.part 缓慢增长甚至不动。
原因:gradle-wrapper.properties 里 distributionUrl 指向
services.gradle.org,它 307 重定向到 GitHub CDN(cdn-*github.com),
国内实测 ~40KB/s。
解决:切到腾讯镜像,修改一次即可,后续所有 gradle 版本都走镜像:
# 文件:android/gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.1.0-all.zip
# 清掉半成品缓存(必须,否则会继续用坏的 .part)
rm -rf ~/.gradle/wrapper/dists/gradle-9.1.0-all
改完重启构建,下载速度 ~6MB/s,约 40 秒完成解压。
验证镜像可用性(可选):
curl -sI --max-time 15 "https://mirrors.cloud.tencent.com/gradle/gradle-9.1.0-all.zip" | grep -i content-length
阿里云也有镜像:
https://mirrors.aliyun.com/macports/distfiles/gradle/gradle-9.1.0-all.zip(实测阿里更快,但腾讯是官方 Gradle 镜像更稳,二选一皆可)。
4. 坑②:NDK 安装卡死(必须手动装)
现象:首次构建在依赖下载完成后进入 Installing NDK (Side by side) 28.2.13676358,
然后 $ANDROID_HOME/.temp/PackageOperation01/android-ndk-r28c-darwin.zip 停在十几 MB 不动。
原因:Gradle 会调 sdkmanager 从 dl.google.com 下载 NDK(~952MB),国内基本连不上。
解决:手动下载 + 解压安装,绕过 sdkmanager:
# 1) 从腾讯镜像下载 NDK(速度 ~5MB/s,约 3 分钟)
mkdir -p /tmp/ndk && cd /tmp/ndk
curl -L -o android-ndk-r28c-darwin.zip \
"https://mirrors.cloud.tencent.com/AndroidSDK/android-ndk-r28c-darwin.zip"
# 2) 校验 SHA1(务必做,防止坏包)
shasum -a 1 android-ndk-r28c-darwin.zip
# 期望:fc20a6bf15a30fb3428c9b60a7308793a362dc6d
# 3) 解压并把内容装进 SDK 的 ndk/<version> 目录
unzip -q android-ndk-r28c-darwin.zip
mkdir -p $ANDROID_HOME/ndk/28.2.13676358
mv android-ndk-r28c/* $ANDROID_HOME/ndk/28.2.13676358/
# 4) 确认版本标记文件存在
cat $ANDROID_HOME/ndk/28.2.13676358/source.properties # Pkg.Revision=28.2.13676358
装好后 Gradle 会认为 NDK 已安装,跳过下载直接开始编译原生代码。
为什么 source.properties 是关键:AGP 检查 ndk/<version>/source.properties
里的 Pkg.Revision 判断 NDK 是否就绪,没有它就会重新触发下载。
不同项目的
ndkVersion不同(在android/app/build.gradle.kts里, 本项目是flutter.ndkVersion)。如果版本不同,去 NDK 发布页拿对应版本的android-ndk-rXXc-darwin.zip下载 URL,SHA1 可在 Google 官方 repo XML 里查到 (缓存位置~/.android/cache/*repository2-3_xml)。
5. 坑③:老 Flutter 插件与 AGP 8 不兼容(3 连坑)
依赖装好后,构建会开始编译各插件原生代码。老插件(2023 年前的)在 AGP 8.x 下
会依次爆出以下三个错误,都是改插件 android/build.gradle即可修复。
本项目受影响的是 on_audio_query_android 1.1.0(来自 on_audio_query 2.x)
和 file_picker 8.0.7。
插件源码在 pub 缓存:
~/.pub-cache/hosted/pub.dev/<包名>-<版本>/android/build.gradle
5.1 缺失 namespace(必炸)
报错:
Could not create an instance of type ...LibraryVariantBuilderImpl.
> Namespace not specified. Specify a namespace in the module's build file:
.../on_audio_query_android-1.1.0/android/build.gradle
修复:在 android {} 块加上命名空间,值取插件清单里的包名
(grep package= android/src/main/AndroidManifest.xml):
android {
namespace 'com.lucasjosino.on_audio_query' // 与 manifest 的 package 一致
compileSdkVersion 36
...
}
5.2 JVM 目标不一致(Java 11 vs Kotlin 17)
报错:
task ':on_audio_query_android:compileReleaseKotlin'
> Inconsistent JVM Target Compatibility Between Java and Kotlin Tasks
compileReleaseJavaWithJavac (11) and compileReleaseKotlin (17)
原因:AGP 8 默认 Java 任务目标是 11,老插件里 Kotlin 1.6 插件按运行 JDK(17)统一, 两边不一致。修复:显式把两个目标对齐到 11:
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
5.3 AAR 元数据校验失败(compileSdk 太低)
报错:
task ':on_audio_query_android:checkReleaseAarMetadata'
> An issue was found when checking AAR metadata:
... requires libraries ... to compile against version 34 or later ...
:on_audio_query_android is currently compiled against android-33.
原因:插件声明的 compileSdk 低于其依赖链所需版本(本项目都是 33/34,
依赖里 androidx.fragment 等要求 ≥34,flutter 引擎要求 ≥36)。修复:
把插件的 compileSdk 提到 36(与 app 模块对齐):
android {
compileSdkVersion 36 // 或 compileSdk 36(新 DSL)
...
}
一次构建可能同时报多个这样的问题,先 grep "is currently compiled against" 日志
列出所有受影响的模块,一次性改完再重跑。
5.4 修复后重新构建
# 日志里若无 FAILURE,且出现:
# ✓ Built build/app/outputs/flutter-apk/app-release.apk (64.0MB)
# 即为成功
6. 持久化:把补丁同步进项目(dependency_overrides)
直接改 pub 缓存只对当前机器生效,换机器 / 重新 pub get 后就丢。
要让补丁永远生效,做法是把补丁后的包复制进项目仓库,再用
dependency_overrides 强制使用本地副本。
6.1 建立 vendor 目录并拷贝补丁包
mkdir -p vendor/on_audio_query_android vendor/file_picker
# on_audio_query_android 很小,整包拷
cp -R ~/.pub-cache/hosted/pub.dev/on_audio_query_android-1.1.0/. vendor/on_audio_query_android/
# file_picker 的 example(13M)/test(4M) 与构建无关,剔除
rsync -a --exclude 'example' --exclude 'test' \
--exclude 'analysis_options.yaml' --exclude 'CONTRIBUTING.md' \
~/.pub-cache/hosted/pub.dev/file_picker-8.0.7/ vendor/file_picker/
6.2 pubspec.yaml 添加 dependency_overrides
dependency_overrides:
on_audio_query_android:
path: vendor/on_audio_query_android
file_picker:
path: vendor/file_picker
overridden两个插件后,on_audio_query(主包,版本 2.9.0)对on_audio_query_android ^1.1.0的约束仍满足,不会触发兼容问题。
6.3 重新解析并验证
flutter pub get
# 输出里应有:
# ! file_picker 8.0.7 from path vendor/file_picker (overridden)
# ! on_audio_query_android 1.1.0 from path vendor/on_audio_query_android (overridden)
flutter build apk --release # 确认能正常构建
7. 排障速查
| 现象 | 排查命令 |
|---|---|
| 构建看似卡住 | ps aux \| grep -iE "GradleDaemon\|java" 看 CPU;建目录 find ~/.gradle -name "*.part" |
| 是不是在下载 | lsof -p <pid> \| grep TCP 看连到哪个 CDN |
| 下载哪一步了 | find ~/.gradle/wrapper/dists ~/Library/Android/sdk -name "*.part"* -exec stat -f%z {} + |
| 失败真因 | 全量日志 grep -nE "What went wrong\|> \|Caused by" /tmp/flutter_build.log |
| 哪个插件有问题 | grep "is currently compiled against" 日志 |
通用三件事:看日志 → 认准是下载还是编译问题 → 下载切镜像 / 编译补插件配置。
8. 本项目的当前状态(备忘)
android/gradle/wrapper/gradle-wrapper.properties:已切腾讯镜像 ✅- NDK 28.2.13676358:已手动装入
$ANDROID_HOME/ndk/✅ vendor/+dependency_overrides:两个插件补丁已持久化 ✅- APK:
build/app/outputs/flutter-apk/app-release.apk(约 61MB)