flutter多渠道打包

68 阅读3分钟

一、传递渠道号

在编译时可以通过 --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 = "../.."}