构建和发布为 Android 应用

在一般的开发过程中,我们可以使用 flutter run 命令,或者 IntelliJ 工具栏中的 RunDebug 来测试 app。

当想要发布 app 时,比如 发布到 Google Play Store,可以按照以下步骤来准备 Android 平台的 发布 版本。本指南将介绍如何执行以下步骤的内容:

添加启动图标

#

当我们创建一个新的 Flutter app 的时候,它会有一个默认的启动图标。要自定义这个图标,可以参考使用 flutter_launcher_icons 这个 package。

或者,如果我们想手动操作,可以参考以下方法:

  1. 查看 Material Design Product Icons 指南中图标设计部分。

  2. <app dir>/android/app/src/main/res/ 目录下,把我们的图标文件放在以 配置限定符 命名的文件夹中。类似默认的 mipmap- 文件夹这样的命名方式。

  3. AndroidManifest.xml 中,更新 application 标签中的 android:icon 属性来引用上一步骤中我们自己的图标文件 (例如,<application android:icon="@mipmap/ic_launcher" ...)。

  4. flutter run 运行 app,检查启动程序中的 app 图标是否已经替换成我们自己的图标文件。

启用 Material 组件

#

如果你的应用使用了 平台视图 (Platform Views),你可能要通过 Android 平台的入门指南文档 中的步骤使用 Material 组件:

举个例子:

  1. <my-app>/android/app/build.gradle 文件中添加 Android Material 组件依赖:

kotlin
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">
  1. <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">

Sign the app

#

为 app 签名

#

为了将你的应用发布到 Play 商店,你需要给你的应用进行数字签名。

Android 应用需要两个签名:上传签名应用签名

  • 开发者上传到 Play Store 的 .aab.apk 需要有上传签名。

  • 终端用户下载的 .apk 文件需要有 应用签名

请参考 Play Store 的官方文档 来创建你的应用签名。

参考以下步骤对你的应用进行签名。

创建一个用于上传的密钥库

#

如果你已经有一个密钥库了,可以直接跳到下一步,如果还没有,需要参考下面的方式创建一个:

  1. 参考文档 在 Android Studio 上为你的应用签名

  2. 在命令行窗口运行如下的命令:

    在 macOS 或者 Linux 系统上,执行下面的代码:

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

    在 Windows 系统上,在 PoweShell 内执行以下代码:

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

    该命令将会把 upload-keystore.jks 文件储存在你的主文件夹中。如果你想要储存在其他地方,请通过指定 -keystore 传入参数。 注意,请保证这个文件的私有性,不要将它提交到公共的代码管理空间

从 app 中引用密钥库

#

创建一个名为 [project]/android/key.properties 的文件,它包含了密钥库位置的定义。在替换内容时请去除 < > 括号:

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

storeFile 密钥路径在 macOS 上类似于 /Users/<user name>/upload-keystore.jks,在 Windows 上类似于 C:\\Users\\<user name>\\upload-keystore.jks

在 gradle 中配置签名

#

在 release 模式下构建你的应用时,可以通过配置 gradle 来使用你的上传密钥。请编辑 <project>/android/app/build.gradle 文件来配置 gradle。

  1. android 属性块之前定义并加载 keystore properties 文件:

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

    [project]/android/app/build.gradle
    kotlin
    def keystoreProperties = new Properties()
    def keystorePropertiesFile = rootProject.file('key.properties')
    if (keystorePropertiesFile.exists()) {
        keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
    }
    
    android {
       ...
    }
  3. android 属性块内的 buildTypes 属性块前面添加签名配置。

    [project]/android/app/build.gradle
    kotlin
    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 会在所有 release 版本上签名。

有关应用签名的更多信息,请查看 developer.android.com 的 为你的应用设置签名

使用 R8 压缩你的代码

#

R8 是谷歌推出的最新代码压缩器。当你打包 release 版本的 APK 或者 AAB 时会默认开启。如果要关闭 R8,请运行 flutter build apk 或在运行 flutter build appbundle 时加上 --no-shrink 参数。

启用 multidex 支持

#

当你在编写较大的应用或使用体量较大的插件时,你可能会在最低的 API 目标版本低于 20 时,遇到 Android 的 dex 的 64k 方法数限制问题。当 flutter run 以调试模式运行应用时,由于缩减机制没有运行,该问题也有可能发生。

Flutter 工具支持以便捷的方式启用 multidex 支持。当工具提示你需要支持时,跟随工具的指示进行调整,是最快的方式。 Flutter 工具会检测 multidex 的构建错误,并提示你是否要更改 Android 项目。在同意的情况下,项目会自动依赖 androidx.multidex:multidex,并且让项目的 Application 继承于 FlutterMultiDexApplication

当你尝试在 IDE 中使用 RunDebug 选项构建和运行应用时,你的构建可能会失败,并显示以下信息:

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 的指南,手动配置你的 Android 项目以支持 multidex。请务必指定 multidex keep 文件 以包含以下内容:

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

同时也要包含所有在应用启动时加载的其他类。参考 Android 文档的 multidex 了解更详细的手动适配指南。

检查 app manifest 文件

#

Review the default App Manifest file.

[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>

Verify the following values:

Tag Attribute Value
application Edit the android:label in the application tag to reflect the final name of the app.
uses-permission Add the android.permission.INTERNET permission value to the android:name attribute if your app needs Internet access. The standard template doesn't include this tag but allows Internet access during development to enable communication between Flutter tools and a running app.

Review or change the Gradle build configuration

#

To verify the Android build configuration, review the android block in the default Gradle build script. The default Gradle build script is found at [project]/android/app/build.gradle. You can change the values of any of these properties.

[project]/android/app/build.gradle
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/studio/build/application-id.html).
        applicationId = "com.example.[project]"
        // You can update the following values to match your application needs.
        minSdk = flutter.minSdkVersion
        targetSdk = flutter.targetSdkVersion
        // These two properties use values defined elsewhere in this file.
        // You can set these values in the property declaration
        // or use a variable.
        versionCode = flutterVersionCode.toInteger()
        versionName = flutterVersionName
    }

    buildTypes {
        ...
    }
}

Properties to adjust in build.gradle

#
Property Purpose Default Value
compileSdk The Android API level against which your app is compiled. This should be the highest version available. If you set this property to 31, you run your app on a device running API 30 or earlier as long as your app makes uses no APIs specific to 31.
defaultConfig
.applicationId The final, unique application ID that identifies your app.
.minSdk The minimum Android API level for which you designed your app to run. flutter.minSdkVersion
.targetSdk The Android API level against which you tested your app to run. Your app should run on all Android API levels up to this one. flutter.targetSdkVersion
.versionCode A positive integer that sets an internal version number. This number only determines which version is more recent than another. Greater numbers indicate more recent versions. App users never see this value.
.versionName A string that your app displays as its version number. Set this property as a raw string or as a reference to a string resource.
.buildToolsVersion The Gradle plugin specifies the default version of the Android build tools that your project uses. To specify a different version of the build tools, change this value.

更多信息可以参考 Gradle 构建文件 文档中模块级构建的部分。

构建生产版本应用

#

当要发布到 Play Store 时,你有两种发布方式的选择:

  • App bundle(推荐)

  • APK

构建一个 app bundle

#

这个部分描述了如何构建一个发布的 app bundle。如果在前面的部分已经完成了签名步骤,发布的 bundle 会被签名。这时你也许想要 混淆你的 Dart 代码 以加大反编译难度。混淆你的代码需要在 build 的时候添加一些标志,并维护其他文件以消除反编译的堆栈跟踪。

使用如下命令:

  1. 运行 cd [project]

  2. 运行 flutter build appbundle。 (运行 flutter build 默认构建一个发布版本。)

你的应用的 release bundle 会被创建到 <app dir>/build/app/outputs/bundle/release/app.aab.

此 app bundle 会默认地包含为 armeabi-v7a (ARM 32-bit)、arm64-v8a (ARM 64-bit) 以及 x86-64 (x86 64-bit) 编译的 Dart 和 Fluter 运行时代码。

测试 app bundle

#

一个 app bundle 可以用多种方法测试,这里介绍两种。

离线使用 bundle tool

#
  1. 如果你还没准备好,可以从 GitHub 仓库 下载 bundletool

  2. 从你的 app bundle 生成 APKs

  3. 将这 APKs 部署到 已连接的设备。

在线使用 Google Play

#
  1. 上传你的 bundle 到 Google Play 去测试它。或者在正式发布之前用 alpha 或 beta 频道去测试。

  2. 按照 这些步骤把你的 bundle 上传到 Play Store。

构建一个 APK

#

虽然 app bundle 比 APKs 更被推荐使用,但是有一些 Store 目前还不支持 app bundle方式。这种情况下,要为各种目标 ABI (Application Binary Interface) 分别构建发布的 APK 文件。

如果你完成签名步骤,APK 就被签名了。这时你也许想要 混淆你的 Dart 代码 以加大反编译难度。混淆你的代码需要在构建时添加一些参数。

使用如下命令:

  1. 输入命令 cd [project]

  2. Run flutter build apk --split-per-abi.

    运行 flutter build apk --split-per-abi
    flutter build 默认带有 --release 参数。)

这个命令会生成如下三个 APK 文件

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

如果移除 --split-per-abi 将会生成一个包含 所有 目标 ABI 的 fat APK 文件。这种 APK 文件将会在比单独构建的 APK 文件尺寸要大,会导致用户下载一些不适用于其设备架构的二进制文件。

在设备上安装 APK 文件

#

按照如下这些步骤,将前一步中构建出来的 APK 安装到 Android 设备上。

使用如下命令:

  1. 用 USB 线将 Android 设备连接到电脑上;

  2. 输入命令 cd [project]

  3. 运行 flutter install

发布到 Google Play Store

#

要了解如何发布一个 app 到 Google Play Store,可以参考 Google Play 发布文档

更新应用版本号

#

每个应用默认的初始版本号是 1.0.0。若要更新它,请转到 pubspec.yaml 文件并更新以下内容:

version: 1.0.0+1

版本号由三个点分隔的数字组成,例如上面样例中的 1.0.0。然后是可选的构建号,例如上面样例中的 1,以 + 分隔。

版本号与构建号都可以在 Flutter 打包时分别使用 --build-name--build-number 重新指定。

在 Android 中,build-number 被用作 versionCodebuild-name 将作为 versionName 使用。更多信息请参考 Android 文档中的 为你的应用添加版本

当重新构建 Android 应用后,任何在 pubspec 文件所做的版本号更新,都将会更新 local.properties 文件中的 versionNameversionCode

Android 发布常见问题

#

这里是一些关于 Android 应用发布的常见问题。

我应该什么时候构建 app bundles 而不是 APKs?

#

Google Play Store 相对于 APKs 更建议你发布 app bundles,因为那样应用会更有效率地交付给你的用户。但是,如果你想将应用发布到其他的应用商店,APK可能是唯一选项。

什么是 fat APK?

#

一个 fat APK 是一个包含了支持多个 ABI 架构的 APK 文件。这样做的好处是单个 APK 可以运行在多个架构上,因此具有更广泛的兼容性。但同时缺点就是文件体积会比较大,导致用户在安装你的应用时会下载和储存更多的字节。当构建 APK 而不是 app bundles 时强烈建议分开构建 APK,如 build an APK 所描述的那样,使用 --split-per-abi 指令。

哪些目标架构是被支持的?

#

当使用 release 模式构建你的应用时, Flutter app 可以基于 armeabi-v7a (ARM 32 位)、 arm64-v8a (ARM 64 位) 以及 x86-64 (x86 64 位) 被编译。 Flutter 目前支持通过 ARM 模拟 x86 Android。

如何为一个使用 flutter build appbundle 创建的 app bundle 签名?

#

请查看 为 app 签名

如何使用 Android Studio 构建一个发布?

#

在Android Studio中, 打开你的 app 文件夹下的 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 文件会在你的 app 所在文件夹下的 build/app/outputs 文件夹下。