Android 发行版(APK Release)构建指南

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.9 stable
  • 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)
阅读 — · 全站 —
🎸 我的歌单 0 首