flutter项目打包上架Google Play

我开发的flutter 项目已经上架了App store。近期要上传Google Play,我也是第1次上架,边学边记录。希望能帮到有需要的人。

前提

  1. flutter项目已经开发好了,应用图标,名称,启动图这些都好了。
  2. Google Play 开发者账号已经申请好了。

本文只介绍,对我来说必须做的事情。其他细节建议自行查官方文档。

flutter官方文档

从哪开始?问AI或者官方文档,我这里选择看官方文档:

为 app 签名

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

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

  1. 开发者上传到 Play Store 的 .aab 或 .apk 需要有上传签名
  2. 终端用户下载的 .apk 文件需要有 应用签名

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

应用签名很常见,做过android相关开发的,不管是原生开发,还是hbuilder之类的,RN 都应该接触过。我这边以前写过类似的文档

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

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

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

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

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

keytool这个命令是jdk中的,我这里有的是JDK11,建议用JDK 9以上的版本。

这里根据提示输入密码,公司信息即可。请妥善保管这个文件。

从 app 中引用密钥库

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

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

在 Gradle 中配置签名

这步,建议看 https://docs.flutter.cn/deployment/android#configure-signing-in-gradle

因为我的环境有点特殊,就不在这里写出来了,怕误导读者。

继续探索

以上内容本质上就是普通的android打包流程,那么上传Google Play 相关的呢?

Flutter文档中并没有详细的介绍,只是引用了一个链接

https://developer.android.com/distribute?hl=zh-cn

我看完以后还是不清楚怎么做,于是只能换个思路了。

于是我找到了google android 文档中,感觉有价值的文档:

这几篇文章写的很详细,让我对Google Play 上架有个基础的概念了。

这张图很关键,我个人理解:上传安装包使用Android App Bundle(AAB)格式,最终是Google 进行apk签名,而不是我们打包时自己签名。这样做有一些好处,防证书丢失,被盗,可以重置证书等,虽然这些好处对我来说目前没有什么作用。

那么如果打包成AAB格式呢?

打包脚本

Flutter flavor 就是 多环境、多版本打包方案。

我这个打包脚本是AI帮我生成的,我就直接贴出来了。只要关注Google Play相关打包就可以了。

我这里android目前包含了3种打包类型

  1. apk 测试环境的打包。
  2. apk 生产环境的打包。
  3. Google Play 生产环境的打包。

与本文相关的只有第3条,为了不遗漏,我把代码完整贴出来,仅供参考。

android/app/build.gradle.kts

kts 复制代码
import java.util.Properties

plugins {
    id("com.android.application")
    id("kotlin-android")
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
    id("dev.flutter.flutter-gradle-plugin")
}

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

val isPlayReleaseBuild = gradle.startParameter.taskNames.any {
    it.contains("PlayRelease", ignoreCase = true)
}

if (isPlayReleaseBuild) {
    val requiredSigningProperties = listOf("storeFile", "storePassword", "keyAlias", "keyPassword")
    val missingSigningProperties = requiredSigningProperties.filter {
        keystoreProperties.getProperty(it).isNullOrBlank()
    }

    require(keystorePropertiesFile.exists()) {
        "Google Play release signing requires android/key.properties."
    }
    require(missingSigningProperties.isEmpty()) {
        "Missing Google Play signing properties in android/key.properties: ${missingSigningProperties.joinToString()}."
    }
    require(file(keystoreProperties.getProperty("storeFile")).exists()) {
        "Google Play signing storeFile does not exist: ${keystoreProperties.getProperty("storeFile")}."
    }
}

android {
    namespace = "com.example.boilerplate"
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion

    signingConfigs {
        create("playRelease") {
            val configuredStoreFile = keystoreProperties.getProperty("storeFile")
            if (!configuredStoreFile.isNullOrBlank()) {
                storeFile = file(configuredStoreFile)
            }
            storePassword = keystoreProperties.getProperty("storePassword")
            keyAlias = keystoreProperties.getProperty("keyAlias")
            keyPassword = keystoreProperties.getProperty("keyPassword")
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }

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

    flavorDimensions += "environment"

    productFlavors {
        create("prod") {
            dimension = "environment"
            manifestPlaceholders["appName"] = "YouAppName"
            manifestPlaceholders["usesCleartextTraffic"] = "false"
        }

        create("play") {
            dimension = "environment"
            applicationId = "com.company.YouAppName"
            manifestPlaceholders["appName"] = "YouAppName"
            manifestPlaceholders["usesCleartextTraffic"] = "false"
        }

        create("staging") {
            dimension = "environment"
            applicationIdSuffix = ".test"
            versionNameSuffix = "-test"
            manifestPlaceholders["appName"] = "YouAppName Test"
            manifestPlaceholders["usesCleartextTraffic"] = "true"
        }
    }

    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 = if (isPlayReleaseBuild) {
                signingConfigs.getByName("playRelease")
            } else {
                signingConfigs.getByName("debug")
            }
        }
    }
}

flutter {
    source = "../.."
}

scripts/build_android_play_prod.sh

bash 复制代码
#!/usr/bin/env bash
set -euo pipefail

flutter build appbundle --release --flavor play \
  --dart-define=APP_ENV=prod \
  --dart-define=BASE_URL=https://app.xxxx.com/prod-api

亲测有效,打包生成后可以生成build/app/outputs/bundle/playRelease/app-play-release.aab

然后这个包上传Google Play Console就可以正确的识别了。

参考文档