环境:Arch Linux on WSL2,Flutter 3.47.9,Android SDK 36,16 核 / 23G 内存。 现象:flutter build apk 动辄十几分钟,甚至卡住不动。

本文把「Arch Linux 下 Flutter 环境搭建」和「构建慢的排查过程」合在一起写,既是一份安装记录,也是一份性能排障笔记。 环境搭建部分参考了 Archlinux 下 Flutter 开发环境的搭建。


0. 先给结论

构建慢不是单一原因,而是 5 个问题叠加:

#问题典型表现修复后
1JDK 27 与 Gradle 8.14 不兼容Unsupported class file major version 71固定 JDK 21
2pub.flutter-io.cn 实际不可用pub get 几分钟无输出换交大镜像,68s 完成
3~/.gradle 为空 + Gradle 发行包镜像慢每次冷启动下载 200MB+预置发行包 + 华为云镜像
4缺 build-tools;35.0.0 / platforms;android-35AGP 自动安装失败手动从国内镜像安装
5插件构建时从 GitHub 下载 libsmb2.so卡 180 秒后失败一次性国内加速拉取并缓存

最终效果:

场景修复前修复后
flutter pub get(首次)超时 / 数分钟68 s
首次完整 release 构建8~15 分钟或直接失败3 min 32 s
无改动二次构建分钟级12.5 s
补完 SMB 原生库后二次构建—21 s

1. 基础环境安装(Arch Linux)

参考文章的安装方式是用 yay 安装系统包:

yay -S flutter
yay -S android-sdk
yay -S dart
yay -S android-tools
yay -S android-sdk-platform-tools
yay -S android-sdk-cmdline-tools-latest
yay -S android-sdk-build-tools-31.0.0
yay -S android-platform-31

几点补充:

  • 不一定要锁死 31.0.0,但要保证 build-tools 与 platform 版本配套,否则会出现“找不到 SDK”的报错。
  • 现在 Flutter 3.47 默认要求更高的 compileSdk / build-tools,建议至少装 35 和 36:
yay -S android-sdk-build-tools-35 android-platform-35
yay -S android-sdk-build-tools-36 android-platform-36
  • Flutter 和 Android SDK 的目录权限要归当前用户,否则后面 AGP 自动安装组件会失败(这是本文第 4 个坑的根源):
sudo chown -R "$USER:$USER" /opt/flutter
sudo chown -R "$USER:$USER" /opt/android-sdk
sudo chown -R "$USER:$USER" /opt/dart-sdk

配置 SDK 路径并检查:

flutter config --android-sdk /opt/android-sdk
flutter doctor -v

如果需要 Flutter Web,再安装 Chrome:

yay -S google-chrome
sudo ln -s /usr/bin/google-chrome-stable /usr/bin/google-chrome

2. 国内镜像:把 ~/.env 一次配好

~/.env 由 .bashrc / .zshrc source,集中管理最省心:

# ===== 国内镜像 & 构建环境 =====

# Pub:pub.flutter-io.cn 已不可用,改用上海交大镜像
export PUB_HOSTED_URL="https://mirror.sjtu.edu.cn/dart-pub"
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
export FLUTTER_GIT_URL="https://mirrors.cernet.edu.cn/flutter-sdk.git"

# JDK 21:Gradle 8.14 不支持 JDK 27
export JAVA_HOME="$HOME/.local/jdk21"
export PATH="/opt/flutter/bin:$JAVA_HOME/bin:$PATH"

export ANDROID_HOME="/opt/android-sdk"
export ANDROID_SDK_ROOT="$ANDROID_HOME"

实测镜像速度(同一网络):

  • pub.flutter-io.cn:下载 tar 包直接超时
  • 交大 mirror.sjtu.edu.cn/dart-pub:可用,pub get 68 秒
  • 腾讯 mirrors.cloud.tencent.com/dart-pub:可用但更慢
  • 清华 / 中科大:目录已下线或 404

小贴士:国内 pub 镜像返回的 archive_url 仍可能指向 storage.flutter-io.cn,所以镜像只解决“元数据解析”,包体下载速度取决于 storage 节点。


3. JDK:Gradle 8.14 不认识 JDK 27

系统默认 Java 是 27,直接构建报错:

BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_'
Unsupported class file major version 71

原因:Gradle 8.14 最高只支持 JDK 24,而 JDK 27 的 class 文件版本是 71。

做法是单独装一个 JDK 21(不必动系统默认 Java):

mkdir -p ~/.local/jdk21
curl -L -o /tmp/jdk21.tar.gz \
  https://mirrors.huaweicloud.com/openjdk/21.0.2/openjdk-21.0.2_linux-x64_bin.tar.gz
tar -xzf /tmp/jdk21.tar.gz -C ~/.local/jdk21 --strip-components=1
~/.local/jdk21/bin/java -version

然后让 JAVA_HOME 指向它。flutter doctor -v 里应看到:

Java binary at: /home/xero/.local/jdk21/bin/java
Java version OpenJDK 21.0.2

4. Gradle:发行包和依赖仓库都要走国内

4.1 Gradle 发行包

android/gradle/wrapper/gradle-wrapper.properties 默认可能指向国内某镜像,但速度差异巨大。实测同一个 gradle-8.14-all.zip(214MB):

镜像速度预计耗时
腾讯云~0.28 MB/s~13 分钟
华为云~3.4 MB/s~1 分钟

建议把 distributionUrl 换成华为云:

distributionUrl=https://mirrors.huaweicloud.com/gradle/gradle-8.14-all.zip

如果暂时不想改仓库文件,也可以把发行包预置到 wrapper 缓存目录(目录名是 URL 的 MD5 的 base36):

~/.gradle/wrapper/dists/gradle-8.14-all/<hash>/gradle-8.14-all.zip

4.2 Maven 依赖:用 init 脚本全局加镜像

Flutter 项目的依赖来自三类仓库:pluginManagement(插件)、项目 repositories(依赖)、included build(Flutter 自带的 flutter_tools/gradle)。只改 build.gradle 往往漏掉插件解析。

创建 ~/.gradle/init.gradle:

def mirrorUrls = [
    'https://mirrors.huaweicloud.com/repository/maven/',
    'https://maven.aliyun.com/repository/google/',
    'https://maven.aliyun.com/repository/public/',
]

// 关键:pluginManagement 的仓库必须在 settings 求值前生效
beforeSettings { settings ->
    settings.pluginManagement.repositories {
        mirrorUrls.each { u -> maven { url = u } }
        gradlePluginPortal()
    }
    // included build 使用 PREFER_SETTINGS,需要 settings 级仓库
    settings.dependencyResolutionManagement.repositories {
        mirrorUrls.each { u -> maven { url = u } }
    }
}

// 只给根构建注入 project 级仓库;
// included build 若被注入 project 仓库会报
// "prefer settings repositories over project repositories"
if (gradle.parent == null) {
    allprojects {
        repositories {
            mirrorUrls.each { u -> maven { url = u } }
        }
    }
}

这里有两个容易踩的坑:

  1. 只用 allprojects 不够,插件解析在 settings 阶段就发生了,必须用 beforeSettings。
  2. 无脑给所有 build 注入 project 仓库,会让 Flutter 的 included build 直接报错,所以要判断 gradle.parent == null。

修复后日志里境外 Maven 下载次数为 0,华为云命中 140 次。


5. Android SDK 组件与协议

5.1 缺少 build-tools 35

构建时会报:

Failed to install the following SDK components:
  build-tools;35.0.0 Android SDK Build-Tools 35

即使协议已接受,AGP 自动安装仍可能失败。常见原因是 SDK 目录属主是 root:

sudo chown -R "$USER:$USER" /opt/android-sdk

本文环境没有免密 sudo,只能从国内镜像手动安装。组件清单在腾讯镜像的 repository2-3.xml 里:

curl -sL -o /tmp/repo23.xml \
  https://mirrors.cloud.tencent.com/AndroidSDK/repository2-3.xml

其中 build-tools;35.0.0 对应 build-tools_r35_linux.zip,platforms;android-35 对应 platform-35_r02.zip:

curl -L -o /tmp/build-tools_r35_linux.zip \
  https://mirrors.cloud.tencent.com/AndroidSDK/build-tools_r35_linux.zip
unzip -q /tmp/build-tools_r35_linux.zip -d /tmp/bt35
mv /tmp/bt35/android-15 /opt/android-sdk/build-tools/35.0.0

如果 build-tools/ 目录是 root 所有,又无法 sudo,可以临时改名原目录、新建目录并给旧版本建符号链接。能用 chown 就不要用这个办法,AGP 会提示 “inconsistent location”。

5.2 新版 cmdline-tools 不再支持 --licenses

新版 sdkmanager 会提示:

Warning: The --licenses option is no longer needed.

如果 AGP 仍然检查协议文件,可以手动写入 $ANDROID_HOME/licenses/:

mkdir -p /opt/android-sdk/licenses
cat > /opt/android-sdk/licenses/android-sdk-license <<'EOF'
24333f8a63b6825ea9c5514f83c2829b004d1fee
84831b9409646a918e30573bab4c9c91346d8abd
d975f751698a77b662f1254ddbeed3901e976f5a
EOF

6. 最隐蔽的坑:插件在构建时下载 GitHub Release

dart_smb2 0.0.8 的 android/build.gradle.kts 里有这样一段:

if (!isValid) {
    val url = "${SMB2_BASE_URL}/${filename}"
    println("[dart_smb2] Downloading libsmb2.so for ${abi} from ${url}")
    URL(url).openStream().use { input ->
        FileOutputStream(targetFile).use { output -> input.copyTo(output) }
    }
}

问题在于:

  • 下载发生在 Gradle 构建阶段,不是 pub get 阶段,所以换 pub 镜像没用;
  • URL.openStream() 没有设置超时,GitHub 不可达时会一直等到 TCP 超时;
  • 本次实测整整卡了 180 秒 后报 Unexpected end of file from server,构建失败。

输出类似:

> Task :dart_smb2:downloadSmb2Libraries FAILED
[dart_smb2] Failed to download libsmb2.so for arm64-v8a: Unexpected end of file from server

解决办法:一次性拉取并放进 pub 缓存

脚本会用 SHA-256 校验文件,只要文件存在且哈希匹配,就不会再联网。三个 ABI 的哈希在插件源码里写死,可以从日志或源码拿到。

使用国内 GitHub 加速镜像(示例,按需替换):

BASE="https://gh-proxy.com/https://github.com/ales-drnz/dart_smb2/releases/download/libsmb2-r5"
P="$HOME/.pub-cache/hosted/mirror.sjtu.edu.cn%47dart-pub%47/dart_smb2-0.0.8"

for abi in arm64-v8a armeabi-v7a x86_64; do
  f="libsmb2_android-$abi.so"
  curl -L -o "/tmp/$f" "$BASE/$f"
  sha256sum "/tmp/$f"          # 与插件里的哈希对照
  mkdir -p "$P/android/src/main/jniLibs/$abi"
  cp "/tmp/$f" "$P/android/src/main/jniLibs/$abi/libsmb2.so"
done

之后构建会直接复用缓存,不再访问 GitHub。确认 APK 中包含原生库:

unzip -l build/app/outputs/flutter-apk/app-release.apk | grep libsmb2
# lib/arm64-v8a/libsmb2.so
# lib/armeabi-v7a/libsmb2.so
# lib/x86_64/libsmb2.so

如果以后升级插件版本,哈希和 release 版本都会变,需要重新拉取。 dart_smb2 0.3.0 改用了 native assets hook,也支持通过 hooks.user_defines.dart_smb2.libsmb2 指向本地文件。


7. 其他值得做的优化

7.1 只编需要的 ABI

flutter build apk 默认编 3 个 ABI(arm64-v8a / armeabi-v7a / x86_64),AOT 编译和打包时间约为单 ABI 的 2~3 倍。

本项目 Makefile 已经写好了:

build:
	flutter build apk --release --target-platform android-arm64

平时直接用 make build。

7.2 打开 Gradle 并行和缓存

android/gradle.properties:

org.gradle.parallel=true
org.gradle.caching=true
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:+HeapDumpOnOutOfMemoryError

配置缓存(org.gradle.configuration-cache=true)对 Flutter 插件生态兼容性一般,建议先不开。

7.3 版本别太旧

Flutter 3.47 已经提示 Gradle 8.14 / AGP 8.11 / Kotlin 2.2.20 即将停止支持。升级前先确认插件兼容性,不要一次跳太多。


8. 快速检查清单

# 1. JDK 必须是 17~24(本机用 21)
java -version
flutter doctor -v | grep -i java

# 2. pub 镜像可用
curl -sI "$PUB_HOSTED_URL" | head -1

# 3. Gradle 发行包是否已缓存
ls ~/.gradle/wrapper/dists/

# 4. Maven 是否走国内镜像(构建日志里不应大量出现 dl.google.com / repo.maven.apache.org)
flutter build apk --verbose 2>&1 | grep "Downloading https://"

# 5. SDK 组件是否齐全
ls /opt/android-sdk/build-tools /opt/android-sdk/platforms

# 6. 单独测一次增量构建耗时
time flutter build apk --release --target-platform android-arm64

9. 经验总结

  1. 先看错误,再谈优化。 这次一半的时间不是“慢”,而是“悄悄失败”:JDK 版本、镜像失效、SDK 组件缺失、GitHub 超时。
  2. 把 SDK / Flutter 目录 chown 给当前用户。 否则 AGP 自动安装组件会以“看起来像网络问题”的方式失败。
  3. 国内镜像要分层配置。 Pub、Flutter artifacts、Gradle wrapper、Maven 依赖、GitHub Release 资源是四套不同的东西,只配一个不够。
  4. Gradle 的 init 脚本比改项目更通用,但要注意 settings 阶段和 included build 的仓库模式。
  5. 任何构建期下载都要有超时和离线回退。 插件里的 URL.openStream() 是本次最坑的地方。
  6. 缓存是性能的核心。 冷构建 3.5 分钟,增量构建 12~21 秒,差了一个数量级;不要把冷构建的耗时当成日常开发体验。

参考