构建并发布 Android 应用
如何准备并向 Play 商店发布 Android 应用。
若要测试应用,可以使用命令行中的 flutter run,或 IDE 中的 Run(运行)和 Debug(调试)选项。
当您准备好创建应用的发布版本(例如为了发布到 Google Play 商店)时,本页面可以为您提供帮助。在发布之前,您可能需要为应用做最后的润色。本指南说明了如何执行以下任务:
- 添加启动图标
- 启用 Material 组件
- 应用签名
- 使用 R8 缩减代码
- 启用多 dex (Multidex) 支持
- 检查应用清单 (Manifest)
- 检查构建配置
- 构建发布版应用
- 发布到 Google Play 商店
- 更新应用版本号
- Android 发布常见问题解答
添加启动图标
#创建新的 Flutter 应用时,它会包含一个默认的启动图标。若要自定义此图标,建议查看 flutter_launcher_icons 包。
或者,您可以按照以下步骤手动操作:
-
查阅 Material Design 产品图标设计指南。
-
在
[project]/android/app/src/main/res/目录中,将您的图标文件放置在以配置限定符 (configuration qualifiers)命名的文件夹中。默认的mipmap-文件夹展示了正确的命名规范。 -
在
AndroidManifest.xml中,更新application标签的android:icon属性,以引用上一步中的图标(例如:<application android:icon="@mipmap/ic_launcher" ...)。 -
若要验证图标是否已替换,请运行您的应用并检查启动器中的应用图标。
启用 Material 组件
#如果您的应用使用了 平台视图 (platform views),您可以按照 Android 入门指南中描述的步骤启用 Material Components。
例如
- 在
<my-app>/android/app/build.gradle.kts中添加对 Android Material 的依赖:
dependencies {
// ...
implementation("com.google.android.material:material:<version>")
// ...
}
dependencies {
// ...
implementation 'com.google.android.material:material:<version>'
// ...
}
若要查找最新版本,请访问 Google Maven。
-
在
<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"> -
在
<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 商店文档。
若要对您的应用进行签名,请使用以下说明。
创建上传密钥库
#如果您已有密钥库,请直接跳至下一步。如果没有,请使用以下任一方法创建一个:
-
按照 Android Studio 密钥生成步骤进行操作。
-
在命令行中运行以下命令:
在 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 文件。
-
在
android属性块之前定义并加载密钥库属性文件。 设置
keystoreProperties对象以加载key.properties文件。
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 {
...
}
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 {
...
}
- 在
android属性块内部的buildTypes属性块之前添加签名配置。
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")
}
}
...
}
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 apk 或 flutter 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(调试)选项构建并运行应用时,您的构建可能会失败,并出现以下消息:
若要从命令行启用 Multidex,请运行 flutter run --debug 并选择一个 Android 设备。
收到提示时,输入 y。Flutter 工具会启用 Multidex 支持并重新尝试构建。
您也可以选择按照 Android 指南手动支持 Multidex,并修改项目的 Android 目录配置。必须指定一个 multidex keep 文件以包含必要类。
io/flutter/embedding/engine/loader/FlutterLoader.class
io/flutter/util/PathUtils.class
此外,还需包含应用启动时使用的任何其他类。有关手动添加 Multidex 支持的详细指南,请查阅官方 Android 文档。
检查应用清单 (Manifest)
#检查默认的 App Manifest(应用清单)文件。
<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。
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 商店和开发者设备上的唯一标识符。
如果您更新了 applicationId 和 namespace 属性,您还必须更新 MainActivity.kt 或 MainActivity.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 管理,以确保与框架和插件的兼容性。除非以下情况,否则通常不需要更改它们:
-
需要较新的 API:如果您使用的插件或功能要求的
minSdk高于 Flutter 的默认值,您可以手动将其设置为更高的版本号(例如minSdk = 24)。 - 需要锁定版本:如果您想防止在升级 Flutter 时自动更新这些版本,可以使用特定的整数值替换默认变量。
版本代码和版本名称
#versionCode 和 versionName 会根据您的 pubspec.yaml 文件(使用 version: 1.0.0+1 字段)自动设置。通常不需要在 Gradle 文件中修改它们。
构建发布版应用
#发布到 Play 商店时,您有两种可用的发布格式:
- App Bundle(首选)
- APK
构建 App Bundle
#本节介绍如何构建发布版 App Bundle。如果您已完成签名步骤,构建出的 App Bundle 将会自动签名。此时,您可以考虑 混淆您的 Dart 代码,以增加逆向工程的难度。混淆代码涉及在构建命令中添加标志,并维护额外的文件以反混淆堆栈跟踪。
从命令行操作:
- 输入
cd [project]。 - 运行
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 离线测试
#- 如果尚未操作,请从其 GitHub 仓库下载
bundletool。 - 从 App Bundle 生成 APK 集。
- 将 APK 部署到连接的设备上。
使用 Google Play 在线测试
#- 将您的 App Bundle 上传到 Google Play 进行测试。您可以使用内部测试轨道,或 Alpha/Beta 通道在正式发布前测试 App Bundle。
- 按照步骤 上传您的 App Bundle 到 Play 商店。
构建 APK
#虽然推荐使用 App Bundle 而非 APK,但仍有一些商店尚不支持 App Bundle。在这种情况下,请为每个目标 ABI(应用程序二进制接口)构建发布版 APK。
如果您已完成签名步骤,构建出的 APK 将会自动签名。此时,您可以考虑 混淆您的 Dart 代码,以增加逆向工程的难度。混淆代码涉及在构建命令中添加标志。
从命令行操作:
输入
cd [project]。-
运行
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:
从命令行操作:
- 使用 USB 电缆将您的 Android 设备连接到计算机。
- 输入
cd [project]。 - 运行
flutter install。
发布到 Google Play 商店
#有关将应用发布到 Google Play 商店的详细说明,请查阅 Google Play 发布文档。
更新应用版本号
#应用的默认版本号是 1.0.0。要更新它,请导航到 pubspec.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 文件中的 versionName 和 versionCode。
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)。
接下来,选择构建变体。点击主菜单中的 Build > Select Build Variant(构建 > 选择构建变体)。在 Build Variants 面板中选择任意变体(默认为 debug)。
生成的 App Bundle 或 APK 文件位于您应用文件夹内的 build/app/outputs 中。
如何判断 APK 是否使用了 Flutter?
#您可以使用 apkanalyzer 工具列出文件。
apkanalyzer files list --files-only <SOME-APK> files list --files-only <SOME-APK>
然后查找 /lib/<ARCH>/libflutter.so 文件。
例如,以下命令应返回大于 0 的数字:
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> 标签。该值可以是 1 或 2。
例如: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 库,此方法无效。
非技术性评估
#- 在设备上下载 Flutter Shark 并扫描本地应用。
- 访问 Flutter Hunt 网站。