跳到主内容

构建并发布 Android 应用

如何准备并向 Play 商店发布 Android 应用。

若要测试应用,可以使用命令行中的 flutter run,或 IDE 中的 Run(运行)和 Debug(调试)选项。

当您准备好创建应用的发布版本(例如为了发布到 Google Play 商店)时,本页面可以为您提供帮助。在发布之前,您可能需要为应用做最后的润色。本指南说明了如何执行以下任务:

添加启动图标

#

创建新的 Flutter 应用时,它会包含一个默认的启动图标。若要自定义此图标,建议查看 flutter_launcher_icons 包。

或者,您可以按照以下步骤手动操作:

  1. 查阅 Material Design 产品图标设计指南。

  2. [project]/android/app/src/main/res/ 目录中,将您的图标文件放置在以配置限定符 (configuration qualifiers)命名的文件夹中。默认的 mipmap- 文件夹展示了正确的命名规范。

  3. AndroidManifest.xml 中,更新 application 标签的 android:icon 属性,以引用上一步中的图标(例如:<application android:icon="@mipmap/ic_launcher" ...)。

  4. 若要验证图标是否已替换,请运行您的应用并检查启动器中的应用图标。

启用 Material 组件

#

如果您的应用使用了 平台视图 (platform views),您可以按照 Android 入门指南中描述的步骤启用 Material Components。

例如

  1. <my-app>/android/app/build.gradle.kts 中添加对 Android Material 的依赖:
kotlin
dependencies {
    // ...
    implementation("com.google.android.material:material:<version>")
    // ...
}
groovy
dependencies {
    // ...
    implementation 'com.google.android.material:material:<version>'
    // ...
}

若要查找最新版本,请访问 Google Maven

  1. <my-app>/android/app/src/main/res/values/styles.xml 中设置浅色主题。

    xml
    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
    <style name="NormalTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
    
  2. <my-app>/android/app/src/main/res/values-night/styles.xml 中设置深色主题。

    xml
    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
    <style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    

应用签名

#

若要在 Play 商店发布,您必须使用数字证书对应用进行签名。

Android 使用两种签名密钥:上传密钥 (upload key)应用签名密钥 (app signing key)

  • 开发者使用上传密钥.aab.apk 文件进行签名并上传到 Play 商店。
  • 最终用户下载的是由应用签名密钥签名的 .apk 文件。

若要创建应用签名密钥,请使用 Google Play 应用签名功能,具体步骤请参考官方 Play 商店文档

若要对您的应用进行签名,请使用以下说明。

创建上传密钥库

#

如果您已有密钥库,请直接跳至下一步。如果没有,请使用以下任一方法创建一个:

  1. 按照 Android Studio 密钥生成步骤进行操作。

  2. 在命令行中运行以下命令:

    在 macOS 或 Linux 上,使用以下命令:

    keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \
            -storetype JKS -keysize 2048 -validity 10000 -alias upload
    

    在 Windows 上,在 PowerShell 中使用以下命令:

    keytool -genkey -v -keystore $env:USERPROFILE\upload-keystore.jks `
            -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 `
            -alias upload
    

    此命令将 upload-keystore.jks 文件存储在您的主目录中。如果您想将其存储在其他位置,请修改传递给 -keystore 参数的路径。请务必保护好 keystore 文件;不要将其提交到公共版本控制系统中!

在应用中引用密钥库

#

创建一个名为 [project]/android/key.properties 的文件,其中包含对您密钥库的引用。不要包含尖括号 (< >),它们仅表示该处是占位符。

属性:
storePassword=<password-from-previous-step>
keyPassword=<password-from-previous-step>
keyAlias=upload
storeFile=<keystore-file-location>

storeFile 在 macOS 上可能位于 /Users/<用户名>/upload-keystore.jks,在 Windows 上可能位于 C:\\Users\\<用户名>\\upload-keystore.jks

在 Gradle 中配置签名

#

以发布模式构建应用时,配置 Gradle 以使用您的上传密钥。要配置 Gradle,请编辑 <project>/android/app/build.gradle.kts 文件。

  1. android 属性块之前定义并加载密钥库属性文件。

  2. 设置 keystoreProperties 对象以加载 key.properties 文件。

[project]/android/app/build.gradle.kts
kotlin
import java.util.Properties
import java.io.FileInputStream

plugins {
   ...
}

val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

android {
   ...
}
[project]/android/app/build.gradle
groovy
import java.util.Properties
import java.io.FileInputStream

plugins {
   ...
}

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
   ...
}
  1. android 属性块内部的 buildTypes 属性块之前添加签名配置。
[project]/android/app/build.gradle.kts
kotlin
android {
    // ...

    signingConfigs {
        create("release") {
            keyAlias = keystoreProperties["keyAlias"] as String
            keyPassword = keystoreProperties["keyPassword"] as String
            storeFile = keystoreProperties["storeFile"]?.let { file(it) }
            storePassword = keystoreProperties["storePassword"] as String
        }
    }
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now,
            // so `flutter run --release` works.
            signingConfig = signingConfigs.getByName("debug")
            signingConfig = signingConfigs.getByName("release")
        }
    }
...
}
[project]/android/app/build.gradle
groovy
android {
    // ...

    signingConfigs {
        release {
            keyAlias = keystoreProperties['keyAlias']
            keyPassword = keystoreProperties['keyPassword']
            storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword = keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now,
            // so `flutter run --release` works.
            signingConfig = signingConfigs.debug
            signingConfig = signingConfigs.release
        }
    }
...
}

Flutter 现在将对所有发布版本进行签名。

要了解关于应用签名的更多信息,请查阅 Android 开发者文档中的 Sign your app(对您的应用签名)。

后量子密码学 (PQC) 混合签名 (Android 17+)

#

Android 17 引入了 v3.2 APK 签名方案。该方案结合了传统签名(如 RSA 或 EC)与 ML-DSA 签名,以实现后量子密码学 (PQC) 混合签名。这可以确保您的应用签名身份免受量子计算带来的潜在威胁。

  • 使用 Play 应用签名的应用:如果您使用 Play 应用签名,可以等待 Google Play 提供升级选项,以使用 Google Play 生成的 PQC 密钥切换到混合签名。
  • 使用自管理密钥的应用:如果您管理自己的签名密钥,可以使用更新后的 Android 构建工具(如 apksigner)切换到混合身份,将 PQC 密钥与新的传统密钥相结合。请注意,您必须创建新的传统密钥;不能重用旧密钥。

有关详细信息,请查阅 Android 关于 PQC APK 签名的文档

使用 R8 缩减代码

#

R8 是 Google 新的代码缩减器。在构建发布版 APK 或 AAB 时,它默认处于启用状态。若要禁用 R8,请向 flutter build apkflutter build appbundle 命令添加 --no-shrink 标志。

启用多 dex (Multidex) 支持

#

当编写大型应用或使用大型插件时,如果目标 API 版本为 20 或以下,您可能会遇到 Android 的 64k 方法数限制。在运行未启用代码缩减的调试版本(使用 flutter run)时,也可能会遇到此问题。

Flutter 工具支持轻松启用 Multidex。最简单的方法是在收到提示时选择启用 Multidex 支持。该工具会检测到 Multidex 构建错误,并在对您的 Android 项目进行更改前征求您的意见。选择加入后,Flutter 会自动添加 androidx.multidex:multidex 依赖,并使用生成的 FlutterMultiDexApplication 作为项目的 Application 类。

当您尝试通过 IDE 中的 Run(运行)和 Debug(调试)选项构建并运行应用时,您的构建可能会失败,并出现以下消息:

Build failure because Multidex support is required

若要从命令行启用 Multidex,请运行 flutter run --debug 并选择一个 Android 设备。

Selecting an Android device with the flutter CLI.

收到提示时,输入 y。Flutter 工具会启用 Multidex 支持并重新尝试构建。

The output of a successful build after adding multidex.

您也可以选择按照 Android 指南手动支持 Multidex,并修改项目的 Android 目录配置。必须指定一个 multidex keep 文件以包含必要类。

io/flutter/embedding/engine/loader/FlutterLoader.class
io/flutter/util/PathUtils.class

此外,还需包含应用启动时使用的任何其他类。有关手动添加 Multidex 支持的详细指南,请查阅官方 Android 文档

检查应用清单 (Manifest)

#

检查默认的 App Manifest(应用清单)文件。

[project]/android/app/src/main/AndroidManifest.xml
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="[project]"
        ...
    </application>
    ...
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

验证以下值:

标签属性
application application 标签中编辑 android:label,以反映应用的最终名称。
uses-permission 如果您的应用需要访问 Internet,请在 android:name 属性中添加 android.permission.INTERNET 权限值。标准模板不包含此标签,但在开发期间允许 Internet 访问,以支持 Flutter 工具与运行中的应用进行通信。

检查 Gradle 构建配置

#

要验证 Android 构建配置,请检查默认 Gradle 构建脚本中的 android 块。默认的 Gradle 构建脚本位于 [project]/android/app/build.gradle.kts

[project]/android/app/build.gradle.kts
kotlin
android {
    namespace = "com.example.[project]"
    // Any value starting with "flutter." gets its value from
    // the Flutter Gradle plugin.
    // To change from these defaults, make your changes in this file.
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion

    ...

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com.cn/studio/build/application-id.html).
        applicationId = "com.example.[project]"
        // You can update the following values to match your application needs.
        // For more information, see: https://fluttercn.cn/to/review-gradle-config.
        minSdk = flutter.minSdkVersion
        targetSdk = flutter.targetSdkVersion
        versionCode = flutter.versionCode
        versionName = flutter.versionName
    }

    buildTypes {
        ...
    }
}

应用 ID

#

applicationId 是您的应用在 Google Play 商店和开发者设备上的唯一标识符。

如果您更新了 applicationIdnamespace 属性,您还必须更新 MainActivity.ktMainActivity.java 文件中的 package 语句,并将该文件移动到相应的目录结构中。

例如

  • 在 Kotlin 中,如果您的新 ID 是 com.example.myapp,请将 MainActivity 文件移动到 android/app/src/main/kotlin/com/example/myapp/MainActivity.kt,并确保第一行是 package com.example.myapp
  • 在 Java 中,请将 MainActivity 文件移动到 android/app/src/main/java/com/example/myapp/MainActivity.java,并确保第一行是 package com.example.myapp

Android SDK 版本

#

Flutter 工具为 Android SDK 版本设置了默认值:

  • compileSdk:用于编译应用的 Android SDK 版本。
  • minSdk:应用支持的最低 Android 版本。
  • targetSdk:应用设计并测试所运行的 Android 版本。

这些默认值(flutter.compileSdkVersion 等)由 Flutter 管理,以确保与框架和插件的兼容性。除非以下情况,否则通常不需要更改它们:

  1. 需要较新的 API:如果您使用的插件或功能要求的 minSdk 高于 Flutter 的默认值,您可以手动将其设置为更高的版本号(例如 minSdk = 24)。
  2. 需要锁定版本:如果您想防止在升级 Flutter 时自动更新这些版本,可以使用特定的整数值替换默认变量。

版本代码和版本名称

#

versionCodeversionName 会根据您的 pubspec.yaml 文件(使用 version: 1.0.0+1 字段)自动设置。通常不需要在 Gradle 文件中修改它们。

构建发布版应用

#

发布到 Play 商店时,您有两种可用的发布格式:

  • App Bundle(首选)
  • APK

构建 App Bundle

#

本节介绍如何构建发布版 App Bundle。如果您已完成签名步骤,构建出的 App Bundle 将会自动签名。此时,您可以考虑 混淆您的 Dart 代码,以增加逆向工程的难度。混淆代码涉及在构建命令中添加标志,并维护额外的文件以反混淆堆栈跟踪。

从命令行操作:

  1. 输入 cd [project]
  2. 运行 flutter build appbundle
    (运行 flutter build 默认为发布版本构建。)

应用的发布包创建于 [project]/build/app/outputs/bundle/release/app.aab

默认情况下,App Bundle 包含您的 Dart 代码以及为 armeabi-v7a (ARM 32-bit)、arm64-v8a (ARM 64-bit) 和 x86-64 (x86 64-bit) 编译的 Flutter 运行时。

测试 App Bundle

#

App Bundle 可以通过多种方式进行测试。本节介绍两种方式:

使用 bundle tool 离线测试

#
  1. 如果尚未操作,请从其 GitHub 仓库下载 bundletool
  2. 从 App Bundle 生成 APK 集
  3. 将 APK 部署到连接的设备上。

使用 Google Play 在线测试

#
  1. 将您的 App Bundle 上传到 Google Play 进行测试。您可以使用内部测试轨道,或 Alpha/Beta 通道在正式发布前测试 App Bundle。
  2. 按照步骤 上传您的 App Bundle 到 Play 商店。

构建 APK

#

虽然推荐使用 App Bundle 而非 APK,但仍有一些商店尚不支持 App Bundle。在这种情况下,请为每个目标 ABI(应用程序二进制接口)构建发布版 APK。

如果您已完成签名步骤,构建出的 APK 将会自动签名。此时,您可以考虑 混淆您的 Dart 代码,以增加逆向工程的难度。混淆代码涉及在构建命令中添加标志。

从命令行操作:

  1. 输入 cd [project]

  2. 运行 flutter build apk --split-per-abi。(flutter build 命令默认为 --release。)

该命令会生成三个 APK 文件:

  • [project]/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
  • [project]/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
  • [project]/build/app/outputs/flutter-apk/app-x86_64-release.apk

移除 --split-per-abi 标志将生成一个包含所有目标 ABI 编译代码的 Fat APK。此类 APK 比拆分后的 APK 文件体积更大,导致用户下载了其设备架构不需要的原生二进制文件。

在设备上安装 APK

#

按照以下步骤在连接的 Android 设备上安装 APK:

从命令行操作:

  1. 使用 USB 电缆将您的 Android 设备连接到计算机。
  2. 输入 cd [project]
  3. 运行 flutter install

发布到 Google Play 商店

#

有关将应用发布到 Google Play 商店的详细说明,请查阅 Google Play 发布文档。

更新应用版本号

#

应用的默认版本号是 1.0.0。要更新它,请导航到 pubspec.yaml 文件并更新以下行:

yaml
version: 1.0.0+1

版本号由三个由点分隔的数字组成(如上例中的 1.0.0),后跟一个可选的构建编号(如上例中的 1),用 + 分隔。

通过指定 --build-name--build-number,可以在 Flutter 构建中覆盖版本和构建编号。

在 Android 中,build-name 用作 versionName,而 build-number 用作 versionCode。有关更多信息,请查阅 Android 文档中的 Version your app(应用版本管理)。

当您重新构建 Android 应用时,pubspec 文件中的版本号更新将自动更新 local.properties 文件中的 versionNameversionCode

Android 发布常见问题解答

#

以下是关于 Android 应用部署的一些常见问题。

我应该构建 App Bundle 还是 APK?

#

Google Play 商店建议您优先部署 App Bundle 而非 APK,因为它们能更高效地交付应用给用户。但是,如果您通过 Play 商店以外的渠道分发应用,APK 可能是您唯一的选择。

什么是 Fat APK?

#

Fat APK 是一种包含多个 ABI 二进制文件的单个 APK。其优点是兼容多种架构,但缺点是文件体积大,导致用户在安装时下载和存储更多数据。在构建 APK 而非 App Bundle 时,强烈建议按照 构建 APK 部分的说明,使用 --split-per-abi 标志构建拆分 APK。

支持哪些目标架构?

#

在发布模式下构建应用时,Flutter 应用可为 armeabi-v7a (ARM 32-bit)、arm64-v8a (ARM 64-bit) 和 x86-64 (x86 64-bit) 进行编译。

如何对 flutter build appbundle 创建的应用包进行签名?

#

请查看 应用签名

如何从 Android Studio 中构建发布版本?

#

在 Android Studio 中,打开您应用文件夹下的 android/ 文件夹。然后,在项目面板中选择 build.gradle (Module: app)

The Gradle build script menu in Android Studio.

接下来,选择构建变体。点击主菜单中的 Build > Select Build Variant(构建 > 选择构建变体)。在 Build Variants 面板中选择任意变体(默认为 debug)。

The build variant menu in Android Studio with Release selected.

生成的 App Bundle 或 APK 文件位于您应用文件夹内的 build/app/outputs 中。

如何判断 APK 是否使用了 Flutter?

#

您可以使用 apkanalyzer 工具列出文件。

sh
apkanalyzer files list --files-only <SOME-APK> files list --files-only <SOME-APK>

然后查找 /lib/<ARCH>/libflutter.so 文件。

例如,以下命令应返回大于 0 的数字:

sh
apkanalyzer files list some-flutter-app.apk | grep flutter.so | wc -l

为什么有效?

Flutter 依赖于 Flutter 引擎使用的 C++ 代码。在 Android 中,此代码与 Flutter 框架及开发者的 Dart 代码一起打包为名为 libflutter.so 的原生库。Java/Android 工具会将 flutter 库重命名为带有 lib 前缀的名称,并处理跨架构的库定位。这就是逆向工程师识别 Flutter 应用的方法。

辅助评估

#

运行 apkanalyzer manifest print <SOME-APK>,查找 android:name="flutterEmbedding"<meta-data> 标签。该值可以是 12

例如:apkanalyzer manifest print some-flutter-app.apk | grep flutterEmbedding -C 2 将返回类似以下内容的字符串。

<meta-data
   android:name="flutterEmbedding"
   android:value="2" />

为什么有效?

Flutter 曾拥有两种不同的嵌入器,该标志用于确定使用了哪种。Flutter 3.22 移除了 v1 嵌入器应用构建的能力。不建议使用此机制,因为不清楚 flutterEmbedding 值还会被保留在所有 Flutter 应用中多久。此外,对于作为 AAR 依赖项导入 Android 应用的 Flutter 库,此方法无效。

非技术性评估

#