一、传递渠道号
在编译时可以通过 --flavor 或 --dart-define 来配置渠道信息,但 --flavor 配置的数据在 dart 层无法获取,而 --dart-define 数据在 dart层、build.gradle.kts 和 android原生层都容易获取,所以更适合使用。
另外 dart-define 有两种使用方式,一种是通过 --dart-define-from-file=dart_define.json 指定一个json文件的方式,文件里的内容可以是: {"CHANNEL":"vivo"} 形式。另一种是直接通过 --dart-define=CHANNEL=oppo 。如果只需要配置渠道号,用后一种就行了。
1、在dart层中获取渠道信息
const CHANNEL = String.fromEnvironment('CHANNEL');
2、在 build.gradle.kts 中获取渠道信息
//封装函数:获取 --dart-define 选项中传入的参数值,返回字符串形式,最常用来传入渠道信息fun Project.getDartDefine(key: String, defaultValue: String = ""): String { val dartDefines = this.findProperty("dart-defines")?.toString() ?: return defaultValue return dartDefines.split(",") .map { Base64.getDecoder().decode(it).toString(Charsets.UTF_8) } .firstOrNull { it.startsWith("$key=") } ?.substringAfter("=") ?: defaultValue}val channel = project.getDartDefine("CHANNEL", "default")println("build.gradle.kts -> CHANNEL: $channel");
3、在android原生层获取渠道信息
需要先在 build.gradle.kts 中把信息写入到 BuildConfig 中,但 AGP 7+之后默认关闭了 BuildConfig功能,需要显式打开。核心代码如下:
android { //AGP7+之后默认关闭 BuildConfig 功能,这里需要显式启用。 buildFeatures { buildConfig = true // 启用 BuildConfig 生成 } defaultConfig { //向 BuildConfig.java 中写入键值对数据,以便在Android原生层获取 buildConfigField("String", "CHANNEL", "\"$channel\"") //其它配置…… } //其它配置……}
然后在 MainActivity.kt 中获取的方法如下:
import android.os.Bundleimport io.flutter.embedding.android.FlutterActivityimport com.ruoxiaoya.dictionary.BuildConfig //导入 BuildConfig 类class MainActivity: FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //直接获取 CHANNEL 值即可 val channel = BuildConfig.CHANNEL println("MainActivity -> CNAHNEL: $channel") }}
二、在 build.gradle.kts 中获取当前 pubspec.yaml 中配置的版本号
//封装函数:获取 local.properties 中的属性值,最常用来获取 pubspec.yaml 中配置的 version 值(对应 flutter.versionCode和 flutter.versionName)fun Project.getLocalProperty(key: String, defaultValue: String = ""): String { return Properties().apply { rootProject.file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) } }.getProperty(key) ?: defaultValue}val versionCode = project.getLocalProperty("flutter.versionCode", "1")val versionName = project.getLocalProperty("flutter.versionName", "1.0")println("build.gradle.kts -> flutter.versionCode: $versionCode");println("build.gradle.kts -> flutter.versionName: $versionName");
三、自定义渠道包的apk名称
//自定义 APK 文件名和输出目录 applicationVariants.all { outputs.forEach { output ->// val outputImpl = output as com.android.build.gradle.internal.api.BaseVariantOutputImpl val outputImpl = output as com.android.build.gradle.api.ApkVariantOutput //默认情况下,Flutter 的 Gradle 插件会自动调整不同架构 APK 的 versionCode,计算公式为:最终 versionCode = abiVersionCode * 1000 + 原始 versionCode(pubspec.yaml 中定义) //但这样跟我们打包的预期不符,所以全部改成一样,也就是 pubspec.yaml 中配置的原始 versionCode outputImpl.versionCodeOverride = versionCode
//对于每种 abi,分别对应三种输出,比如 arm64-v8a 就对应着: app-arm64-v8a-debug.apk、app-arm64-v8a-release.apk、app-arm64-v8a-profile.apk //我们这边只需要处理release包,防止意外打了debug/profile包难以发现。 //需要注意,每次生成默认都会先清空目录,所以没办法并行打多个渠道包,会替换掉。设置apk输出目录应该可以解决这个问题。 if (output.outputFileName.contains("release")) { println("outputImpl.outputFileName1: ${outputImpl.outputFileName}") //设置输出的apk文件名 if (output.outputFileName.contains("armeabi-v7a")) { outputImpl.outputFileName = "dictionary_${versionCode}_${versionName}_${channel}_x32.apk" } else if (output.outputFileName.contains("arm64-v8a")) { outputImpl.outputFileName = "dictionary_${versionCode}_${versionName}_${channel}_x64.apk" } println("outputImpl.outputFileName2: ${outputImpl.outputFileName}") //todo:设置输出apk的目录,如果不设置的话,默认输出到 build\app\outputs\apk\release 目录 } } }
四、接下来就可以使用下面的命令打包了:
flutter build apk --target-platform android-arm,android-arm64 --split-per-abi --dart-define=CHANNEL=vivo --obfuscate --split-debug-info=.
这里备份一个可用的完整的 build.gradle.kts 文件
import java.util.Base64import java.util.Propertiesimport com.android.build.api.variant.ApplicationVariantimport com.android.build.gradle.api.ApkVariantOutput//封装函数:获取 --dart-define 选项中传入的参数值,返回字符串形式,最常用来传入渠道信息fun Project.getDartDefine(key: String, defaultValue: String = ""): String { val dartDefines = this.findProperty("dart-defines")?.toString() ?: return defaultValue return dartDefines.split(",") .map { Base64.getDecoder().decode(it).toString(Charsets.UTF_8) } .firstOrNull { it.startsWith("$key=") } ?.substringAfter("=") ?: defaultValue}val channel = project.getDartDefine("CHANNEL", "default")println("build.gradle.kts -> CHANNEL: $channel");//封装函数:获取 local.properties 中的属性值,最常用来获取 pubspec.yaml 中配置的 version 值(对应 flutter.versionCode和 flutter.versionName)fun Project.getLocalProperty(key: String, defaultValue: String = ""): String { return Properties().apply { rootProject.file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) } }.getProperty(key) ?: defaultValue}val versionCode = project.getLocalProperty("flutter.versionCode", "1")val versionName = project.getLocalProperty("flutter.versionName", "1.0")println("build.gradle.kts -> flutter.versionCode: $versionCode");println("build.gradle.kts -> flutter.versionName: $versionName");plugins { id("com.android.application") id("kotlin-android") id("dev.flutter.flutter-gradle-plugin")}android { //AGP7+之后默认关闭 BuildConfig 功能,这里需要显式启用。 buildFeatures { buildConfig = true // 启用 BuildConfig 生成 } //todo:据说下面是 AGP 8.2.0 之后官方推荐的设置apk文件名的方法,暂时没试。// androidComponents {// beforeVariants(selector().withBuildType("release")) { variant ->// variant.outputs.each { output ->// output.outputFileName.set("MyApp-v${versionName}.apk")// }// }// } //自定义 APK 文件名和输出目录 applicationVariants.all { outputs.forEach { output ->// val outputImpl = output as com.android.build.gradle.internal.api.BaseVariantOutputImpl val outputImpl = output as com.android.build.gradle.api.ApkVariantOutput //默认情况下,Flutter 的 Gradle 插件会自动调整不同架构 APK 的 versionCode,计算公式为:最终 versionCode = abiVersionCode * 1000 + 原始 versionCode(pubspec.yaml 中定义) //但这样跟我们打包的预期不符,所以全部改成一样,也就是 pubspec.yaml 中配置的原始 versionCode outputImpl.versionCodeOverride = versionCode //对于每种 abi,分别对应三种输出,比如 arm64-v8a 就对应着: app-arm64-v8a-debug.apk、app-arm64-v8a-release.apk、app-arm64-v8a-profile.apk //我们这边只需要处理release包,防止意外打了debug/profile包难以发现。 //需要注意,每次生成默认都会先清空目录,所以没办法并行打多个渠道包,会替换掉。设置apk输出目录应该可以解决这个问题。 if (output.outputFileName.contains("release")) { println("outputImpl.outputFileName1: ${outputImpl.outputFileName}") //设置输出的apk文件名 if (output.outputFileName.contains("armeabi-v7a")) { outputImpl.outputFileName = "dictionary_${versionCode}_${versionName}_${channel}_x32.apk" } else if (output.outputFileName.contains("arm64-v8a")) { outputImpl.outputFileName = "dictionary_${versionCode}_${versionName}_${channel}_x64.apk" } println("outputImpl.outputFileName2: ${outputImpl.outputFileName}") //todo:设置输出apk的目录,如果不设置的话,默认输出到 build\app\outputs\apk\release 目录 } } } namespace = "com.ruoxiaoya.simple_dictionary" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion //JAVA常用的稳定版本分别是11、17、21,目前Flutter推荐17。因为Flutter 3.35 依赖的 AGP 版本通常为 8.0 及以上(具体版本可在项目的 android/build.gradle 中查看),而 AGP 8.0 及以上明确要求使用 Java 17 进行编译(参考 Android 官方文档)。 java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 isCoreLibraryDesugaringEnabled = true } kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() } defaultConfig { applicationId = "com.ruoxiaoya.simple_dictionary" minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName //向 BuildConfig.java 中写入键值对数据,以便在Android原生层获取 buildConfigField("String", "CHANNEL", "\"$channel\"") } signingConfigs { create("release") { storeFile = file("..\\keystore\\key0.jks") storePassword = "Yf0943$$" keyAlias = "key0" keyPassword = "Yf0943$$" } } buildTypes { release { signingConfig = signingConfigs.getByName("release") // 其他release配置... } debug { // debug会自动使用默认的debug签名配置 // 如果需要覆盖默认配置,可以在这里修改 signingConfig = signingConfigs.getByName("debug") } }}dependencies { // 使用兼容Java 17的最新desugar版本 coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")}flutter { source = "../.."}