构建并发布 Android 应用
如何准备并发布 Android 应用到 Play 商店。
要测试应用,您可以使用命令行中的 flutter run,或者 IDE 中的 运行 和 调试 选项。
当您准备好准备应用的发布版本时,例如要 发布到 Google Play 商店,此页面可以提供帮助。在发布之前,您可能需要对应用进行一些最后的润色。本指南解释了如何执行以下任务
- 添加启动器图标
- 启用 Material 组件
- 签名应用
- 使用 R8 缩小代码
- 启用多 dex 支持
- 检查应用清单
- 检查构建配置
- 构建发布版应用
- 发布到 Google Play 商店
- 更新应用的版本号
- Android 发布常见问题解答
添加启动器图标
#创建新的 Flutter 应用时,它会有一个默认的启动器图标。要自定义此图标,您可能需要查看 flutter_launcher_icons 包。
或者,您可以使用以下步骤手动执行此操作
-
查看 Material Design 产品图标 指南,了解图标设计。
-
在
[project]/android/app/src/main/res/目录中,将您的图标文件放置在使用的 配置限定符命名的文件夹中。默认的mipmap-文件夹演示了正确的命名约定。 -
在
AndroidManifest.xml中,更新application标签的android:icon属性,以引用来自上一步的图标(例如,<application android:icon="@mipmap/ic_launcher" ...)。 -
要验证是否已替换图标,请运行您的应用并检查启动器中的应用图标。
启用 Material 组件
#如果您的应用使用 平台视图,您可能需要通过遵循 Android 入门指南 中描述的步骤来启用 Material 组件。
例如
- 在
<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 使用两个签名密钥:上传 和 应用签名。
- 开发者将使用上传密钥签名的
.aab或.apk文件上传到 Play 商店。 - 最终用户下载使用应用签名密钥签名的
.apk文件。
要创建您的应用签名密钥,请按照 官方 Play 商店文档 中描述的 Play 应用签名方法操作。
要签名您的应用,请使用以下说明。
创建上传密钥库
#如果您已经有现有的密钥库,请跳过下一步。如果没有,请使用以下方法之一创建密钥库
-
按照 Android Studio 密钥生成步骤 操作。
-
在命令行中运行以下命令
在 macOS 或 Linux 上,使用以下命令
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \ -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/<user name>/upload-keystore.jks 或 Windows 上的 C:\\Users\\<user name>\\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 开发者文档中的签名应用。
使用 R8 缩小代码
#
R8 是 Google 的新型代码压缩器。当您构建发布 APK 或 AAB 时,默认情况下会启用它。要禁用 R8,请将 --no-shrink 标志传递给 flutter build apk 或 flutter build appbundle。
启用多 dex 支持
#在编写大型应用或使用大型插件时,您可能会遇到在目标 API 为 20 或更低版本时 Android 的 64k 方法限制。当使用未启用压缩的 flutter run 运行调试版本的应用时,也可能会遇到此问题。
Flutter 工具支持轻松启用多 dex。最简单的方法是在提示时选择启用多 dex 支持。该工具检测多 dex 构建错误并在进行更改之前询问您的 Android 项目。选择加入允许 Flutter 自动依赖 androidx.multidex:multidex 并使用生成的 FlutterMultiDexApplication 作为项目的应用程序。
当您尝试使用 IDE 中的 运行 和 调试 选项构建和运行应用时,您的构建可能会因以下消息而失败
要从命令行启用多 dex,请运行 flutter run --debug 并选择一个基于 Android 的设备
当提示时,输入 y。Flutter 工具启用多 dex 支持并重试构建
您还可以选择通过遵循 Android 的指南并修改项目的 Android 目录配置来手动支持多 dex。必须指定 多 dex 保留文件 以包含
io/flutter/embedding/engine/loader/FlutterLoader.class
io/flutter/util/PathUtils.class
此外,还包括应用启动期间使用的任何其他类。有关手动添加多 dex 支持的更详细指南,请查看官方 Android 文档。
检查应用清单
#检查默认的 应用清单 文件。
<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.permission.INTERNET 权限 值添加到 android:name 属性中。标准模板不包含此标签,但允许在开发期间通过 Flutter 工具与正在运行的应用进行通信。 |
检查 Gradle 构建配置
#要验证 Android 构建配置,请查看默认的 Gradle 构建脚本。默认 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://flutterdart.cn/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
buildTypes {
...
}
}
应用 ID
#applicationId 是您的应用在 Google Play 商店和开发者设备上的唯一标识符。
Android SDK 版本
#Flutter 工具为 Android SDK 版本设置默认值
-
compileSdk:用于编译应用的 Android SDK 版本。 -
minSdk:应用支持的最低 Android 版本。 -
targetSdk:应用设计和测试运行的版本。
这些默认值(flutter.compileSdkVersion 等)由 Flutter 管理,以确保与框架和插件的兼容性。通常情况下,您不需要更改这些,除非
-
您需要较新的 API:如果您正在使用需要高于 Flutter 默认值的
minSdk的插件或功能,您可以手动将其设置为更高的版本号(例如,minSdk = 24)。 - 您需要锁定版本:如果您想防止在升级 Flutter 时自动更新这些版本,您可以将默认变量替换为特定的整数值。
版本代码和名称
#versionCode 和 versionName 会从您的 pubspec.yaml 文件(使用 version: 1.0.0+1 字段)自动设置。通常情况下,您不需要在 Gradle 文件中修改这些。
构建发布版应用
#在发布到 Play 商店时,您有两种可能的发布格式。
- 应用包 (首选)
- APK
构建应用包
#本节介绍如何构建发布应用包。如果您已完成签名步骤,则应用包将已签名。此时,您可能需要考虑 混淆您的 Dart 代码,以使其更难以进行逆向工程。混淆您的代码涉及在构建命令中添加标志并维护额外的文件以解混淆堆栈跟踪。
从命令行
- 输入
cd [project] - 运行
flutter build appbundle
(运行flutter build默认构建发布版本。)
您的应用的发布包将在 [project]/build/app/outputs/bundle/release/app.aab 中创建。
默认情况下,应用包包含为 armeabi-v7a (ARM 32 位)、arm64-v8a (ARM 64 位) 和 x86-64 (x86 64 位) 编译的 Dart 代码和 Flutter 运行时。
测试应用包
#可以通过多种方式测试应用包。本节介绍其中两种。
使用 bundle tool 离线测试
#- 如果您尚未这样做,请从其 GitHub 仓库 下载
bundletool。 - 从您的应用包生成一组 APK。
- 将 APK 部署到已连接的设备。
使用 Google Play 在线测试
#- 将您的包上传到 Google Play 进行测试。您可以使用内部测试轨道,或 alpha 或 beta 通道来测试包,然后再在生产环境中发布它。
- 按照步骤 将您的包上传到 Play 商店。
构建 APK
#虽然应用包比 APK 更受青睐,但有些商店尚未支持应用包。在这种情况下,为每个目标 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 设备上。
从命令行
- 使用 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 文档中的 版本化您的应用。
当您为 Android 重新构建应用时,pubspec 文件中版本号的任何更新都会更新 local.properties 文件中的 versionName 和 versionCode。
Android 发布常见问题解答
#以下是一些关于 Android 应用部署的常见问题解答。
我应该在什么情况下构建应用包而不是 APK?
#Google Play 商店建议您部署应用包而不是 APK,因为它们允许更有效地将应用程序交付给您的用户。但是,如果您通过 Play 商店以外的方式分发您的应用程序,APK 可能是您的唯一选择。
什么是胖 APK?
#一个 fat APK 是一个包含嵌入其中多个 ABI 的二进制文件的单个 APK。它的优点是单个 APK 可以在多个架构上运行,从而具有更广泛的兼容性,但缺点是其文件大小更大,导致用户在安装您的应用程序时下载和存储更多字节。在构建 APK 而不是应用包时,强烈建议构建拆分 APK,如 构建 APK 中使用 --split-per-abi 标志所述。
支持哪些目标架构?
#在发布模式下构建应用程序时,Flutter 应用程序可以为 armeabi-v7a (ARM 32 位)、arm64-v8a (ARM 64 位) 和 x86-64 (x86 64 位) 编译。
如何签名由 flutter build appbundle 创建的应用包?
#
查看 签名应用。
如何从 Android Studio 中构建发布版本?
#在 Android Studio 中,打开应用文件夹下的现有 android/ 文件夹。然后,在项目面板中选择 build.gradle (Module: app)
接下来,选择构建变体。单击主菜单中的 Build > Select Build Variant。在 Build Variants 面板中选择任何变体(debug 是默认值)
生成的应用包或 APK 文件位于应用文件夹中的 build/app/outputs 中。
如何判断 APK 是否使用 Flutter?
#推荐:使用 APK 文件 apkanalyzer files list --files-only/lib/<ARCH>/libflutter.so
示例:apkanalyzer files list some-flutter-app.apk | grep flutter.so | wc -l 返回任何大于 0 的数字。
工作原理 Flutter 依赖于 Flutter 引擎使用的 C++ 代码。在 Android 中,此代码与 Flutter 框架和开发人员的 Dart 代码捆绑在一起,作为名为 libflutter.so 的本机库。Java/Android 工具会将 flutter 库重命名为带有 lib 前缀,并处理跨架构的库位置。这就是一些逆向工程师 APK 以识别它是 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 嵌入器应用的权限。https://blog.flutter.dev/whats-new-in-flutter-3-22-fbde6c164fe3 此机制不推荐使用,因为尚不清楚 flutterEmbedding 值将继续包含在所有 Flutter 应用中多长时间。此外,这不适用于作为 AAR 依赖项导入到 Android 应用中的所有用 Flutter 编写的库。
非技术评估
#- 在设备上下载 Flutter Shark 并让它扫描本地应用。
- 访问 Flutter Hunt 网站。