当前位置: 首页 > news >正文

Android中flavor的使用

在我的开发中,有这样的需求,有一个项目,需要适配不同的执法仪设备,这些执法仪都是Android系统的,而且有的有系统签名,有的没有,比如我共有四款型号,有三款有系统签名,每款系统签名各不一样,有一款无系统签名,总结就是我需要使用4个不同签名用到4个型号上,这就必须要有4个apk,因为一个apk不可能同时拥有4个不同签名,所以就会导致有如下需求:

  • 生成4个apk,每个apk的签名不相同,签名不相同导致应用ID(包名)也不能相同。
  • 使用系统签名的需要在清单文件中设置 android:sharedUserId="android.uid.system",不使用系统签名的则不设置。
  • 4个apk的版本号可能不一样,所以需要分别设置版本信息。
  • 有一款号型是只支持32位CPU的,对应只能使用32位的so,其它的使用64位so。

最开始我是使用变量来表示各种版本和配置,但是每打包一个版本时,就需要修改变量,比如把flag设置为1,对应的配置使用为型号1的配置,然后还要经常修改清单文件,这很麻烦,所以,这时候flavor就派上了用场,可以节省许多宝贵时间。

为4个签名文件设置对应的配置(下面的配置均使用Groovy语言):

android {signingConfigs {/** 型号1,使用系统签名 */normal {keyAlias 'aaa'keyPassword 'aaa'storeFile file('aaa.keystore')storePassword 'aaa'}/** 型号2,使用系统签名 */head {keyAlias 'bbb'keyPassword 'bbb'storeFile file('bbb.keystore')storePassword 'bbb'}/** 型号3,使用系统签名 */hand {keyAlias 'ccc'keyPassword 'ccc'storeFile file('ccc.jks')storePassword 'ccc'}/** 型号4,使用普通签名 */hik {keyAlias 'ddd'keyPassword 'ddd'storeFile file('ddd.jks')storePassword 'ddd'}}
}

然后根据需求配置flavor

android {flavorDimensions "version"productFlavors {normal {dimension "version"versionCode 202508180versionName "1.1.0"// 应用id没指定,则和原来的保持一样signingConfig signingConfigs.normal// 使用32位sondk.abiFilters "armeabi-v7a"// 指定清单文件中的sharedUserIdmanifestPlaceholders = [sharedUid: "android.uid.system"]}ylxHead {dimension "version"versionCode 202508080versionName "1.0.0"applicationIdSuffix ".head" // 修改应用ID,在原来包名基础上添加.headsigningConfig signingConfigs.headndk.abiFilters "arm64-v8a"// 指定清单文件中的sharedUserIdmanifestPlaceholders = [sharedUid: "android.uid.system"]}hand {dimension "version"versionCode 202508110versionName "1.0.0"applicationIdSuffix ".hand" // 修改应用ID,在原来包名基础上添加.handsigningConfig signingConfigs.handndk.abiFilters "arm64-v8a"// 指定清单文件中的sharedUserIdmanifestPlaceholders = [sharedUid: "android.uid.system"]}hik {dimension "version"versionCode 202508180versionName "1.0.0"applicationIdSuffix ".hik" // 修改应用ID,在原来包名基础上添加.hiksigningConfig signingConfigs.hikndk.abiFilters "arm64-v8a"// 指定清单文件中的sharedUserId,设置为空即为普通应用,不使用系统签名的manifestPlaceholders = [sharedUid: ""] }}
}

从这里可以看到,通过flavor,我们可以很方便的给每个变体设置不一样的版本号、应用ID、签名、so、sharedUserId等,flavor支持的配置远不止这些,如果你还有更多配置需要,自行问AI即可。

这里第一个flavor我们没有配置应用ID,则它和默认的保持一样,比如:

android {defaultConfig {applicationId "com.example.hello"}
}

其它的flavor则添加了后缀,比如:applicationIdSuffix ".hik",则它实际使用的应用ID为:com.example.hello.hik。按道理每个flavor都添加后缀比较好看一点,为什么第一个我没添加,这是因为在做这一款型号的开发的时候,我不知道它有这么多型号,所以当时就使用了com.example.hello包名,且已经上线了,后来来了几款型号说也要适配,所以此时这个包名已经是不能修改的了。

还有这里指定的manifestPlaceholders = [sharedUid: "android.uid.system"],它会自动注入清单文件,清单文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:sharedUserId="${sharedUid}">

这里需要注意的是,flavor中指定的签名配置只对release版本生效,对于系统签名,即使是debug版本,我们也希望使用系统签名,因为有些api,必须使用系统签名才能调用的,如果debug版本使用了Android Studio自带的debug.keystore,则会抛出异常,所以我们可以配置不使用自带的debug.keystore,如下:

android {buildTypes {debug {// 注:这里的签名配置会覆盖productFlavors中设置的签名配置,所以要想使用productFlavors中配置的签名,则这里不能配置签名// debug签名,即使我们不配置signingConfig,但它默认其实是配置了使用Android默认的debug.keystore签名的,所以要想debug的变体// 也使用productFlavors中配置的签名,则需要在这里手动把signingConfig设置为null,这样构造debug变体时才会使用productFlavors中的签名。minifyEnabled falsesigningConfig null // 禁用默认签名proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}

flavor配置好之后,开发就简单了许多,比如当我需要开发hik版本时,我就在构建变体中选择hik版本即可,然后调试的时候就直接点运行按钮,则hik的Debug版本就会运行到设备上,如下:

在这里插入图片描述
当需要打包某个版本时,直接使用gradle命令,我们可以先在gradle面板中执行tasks命令来查看当前项目都有哪些命令,如下:
在这里插入图片描述
如上图,在右上角选择我们的app模块(不选择其实也没问题,选择了就更好一些,表明只看app模块的可用任务),然后在输入框中输入tasks然后回车,结果如下:
在这里插入图片描述
Build tasks分组下,assemble开头的命令则为打包apk的命令:

命令构建范围输出数量典型用途
assemble所有风味 × 所有构建类型8个APK全设备全版本打包(CI/CD)
assembleDebug所有风味 × Debug4个APK所有设备的调试测试版本
assembleRelease所有风味 × Release4个APK所有设备的正式发布版本
assembleHeadhead风味 × 所有构建类型2个APK特定设备的调试+正式版本

其实tasks任务并没有完全打印所有的assemble命令,比如我就想打包一个hik风味的release版本,则可以用:assembleHikRelease,如果只要debug,则为assembleHikDebug

总结就是:assemble可单独使用,也可加风味,也可加构建类型,也可都加,在输入命令时,这太长了又容易输错,所以可以使用缩写,比如我要打包风味为normalrelease版本,完整命令为:assembleNormalRelease,缩写为aNR,对于HeadHandHik,它们都是H开头,所以可以再加多第二个字母来区别,比如要打包Hikrelease版本,则可以用:aHiR

不知道是不是我的Android插件版本不对,我执行assemble命令生成的apk位置在app/build/intermediates/apk目录下,截图如下:
在这里插入图片描述
执法assemble命令来打包所有版本时,也是可以用缩写的,截图如下:
在这里插入图片描述
生成所有debug版本:gradle aD,这与androidDependencies冲突了,则改用:gradle asD,反正不用记,先执行,冲突了会报错,然后再改了再执行即可,效果如下:
在这里插入图片描述
生成所有release版本:gradle aR,效果如下:
在这里插入图片描述
生成hik风味的debug与release版本:gradle aHi,效果如下:
在这里插入图片描述
生成hik风味的release版本:gradle aHiR,效果如下:
在这里插入图片描述
生成hik风味的debug版本:gradle aHiD,效果如下:
在这里插入图片描述
有时候在代码中,还需要根据变体做特殊处理,比如我的某个变体使用普通签名,则它不能调用那些需要系统签名的API,在代码中判断当前是哪个变体也很简单,我们是给应用ID添加的后缀,则判断后缀即可,如下:

class MyApplication : Application() {companion object {var isNormal = falsevar isHead = falsevar isHand = falsevar isHik = false}fun onCreate() {when  {packageName.endsWith(".bj")   -> isNormal = truepackageName.endsWith(".head") -> isHead = truepackageName.endsWith(".hand") -> isHand = truepackageName.endsWith(".hik")  -> isHik = true}}
}

flavor的一个经典应用就是同一个项目提供免费版本和付费版本,也可以理解为基础版本和高级版本,高级版本需要收费。由于近年来kotlin语言做为build.gradle.kts语言越来越流行了,所以下面使用kotlin语言进行示例演示:

android {flavorDimensions += "version"productFlavors {create("free") {dimension = "version"applicationId = "cn.android666.audiorecorder.free"versionCode = 1versionName = "1.0-free"}create("paid") {dimension = "version"applicationId = "cn.android666.audiorecorder.paid"versionCode = 1versionName = "1.0-paid"}}}

在flavor配置中,还可以为Debug和Release分别设置服务器IP、端口等,这样通过切换变体就能实现服务器的切换,无需要手动修改。假设免费版和收费版使用的服务器IP和端口都是一样的,但是debug版本和release版本不一样,其实这种情况就只和构建类型相关,和flavor不相关了,所以在构建类型中定义即可,如下:

android {buildTypes {debug {// Debug版本使用公司内部服务器buildConfigField("String", "SERVER_IP", "\"192.168.10.100\"")buildConfigField("int", "SERVER_PORT", "3000")}release {// Release版本使用生产环境服务器buildConfigField("String", "SERVER_IP", "\"47.98.123.156\"")buildConfigField("int", "SERVER_PORT", "80")}}buildFeatures {buildConfig = true}}

假设情况有变了,debug版本和release版本的服务器ip端口是一样的,只是免费版本和付费版不相同,这就跟构建类型不相关了,而是跟flavor相关了,所以就不要在构建类型中配置ip和端口了,而应该以在flavor中配置,如下:

android {flavorDimensions += "version"productFlavors {create("free") {dimension = "version"applicationId = "cn.android666.audiorecorder.free"versionCode = 1versionName = "1.0-free"// 免费版服务器配置buildConfigField("String", "SERVER_IP", "\"47.102.56.122\"")buildConfigField("int", "SERVER_PORT", "3000")}create("paid") {dimension = "version"applicationId = "cn.android666.audiorecorder.paid"versionCode = 1versionName = "1.0-paid"// 付费版服务器配置buildConfigField("String", "SERVER_IP", "\"47.102.56.123\"")buildConfigField("int", "SERVER_PORT", "8080")}}buildFeatures {buildConfig = true}
}

假设情况又有变了,对于免费版本和付费版本,它们分别使用不同的服务器,且它们的debug版本和release版本也是使用不同的服务器,此时不但和构建类型相关,还和和flavor相关,这种情况属于flavor和构建类型相交差的情形,声明在构建类型配置中不合适,声明在flavor配置中也不合适,这需要动态设置,示例如下:

android {buildTypes {debug {isMinifyEnabled = false}release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")}}flavorDimensions += "version"productFlavors {create("free") {dimension = "version"applicationId = "cn.android666.audiorecorder.free"versionCode = 1versionName = "1.0-free"}create("paid") {dimension = "version"applicationId = "cn.android666.audiorecorder.paid"versionCode = 1versionName = "1.0-paid"}}applicationVariants.all {val variant = thisvar serverIp = "\"192.168.1.100\""  // 默认服务器var serverPort = "8080"             // 默认端口// 根据变体名称配置不同的服务器IP和端口when (variant.name) {"freeDebug" -> {serverIp = "\"192.168.192.128\""     // 免费版调试服务器serverPort = "3000"                 // 免费版调试端口}"freeRelease" -> {serverIp = "\"47.98.123.156\""      // 免费版生产服务器serverPort = "80"                   // 免费版生产端口}"paidDebug" -> {serverIp = "\"192.168.192.100\""     // 付费版调试服务器serverPort = "4000"                 // 付费版调试端口}"paidRelease" -> {serverIp = "\"47.102.56.123\""      // 付费版生产服务器serverPort = "8080"                 // 付费版生产端口}}variant.buildConfigField("String", "SERVER_IP", serverIp)variant.buildConfigField("int", "SERVER_PORT", serverPort)}buildFeatures {buildConfig = true}
}

在代码中访问服务器IP和端口:

Log.i("TAG", "Server IP: ${BuildConfig.SERVER_IP}, Port: ${BuildConfig.SERVER_PORT}")

运行不同的变体就能得到不同的服务器IP和端口,无需每次都手动修改代码,这样大大节省了宝贵时间。

这里需要注意的是,我们在build.gradle.kts中指定的int类型时不要设置为Int,如下:

variant.buildConfigField("Int", "SERVER_PORT", serverPort)

这样生成的BuildConfig.java代码如下:

public final class BuildConfig {public static final boolean DEBUG = Boolean.parseBoolean("true");public static final String APPLICATION_ID = "cn.android666.audiorecorder.free";public static final String BUILD_TYPE = "debug";public static final String FLAVOR = "free";public static final int VERSION_CODE = 1;public static final String VERSION_NAME = "1.0-free";// Field from the variant APIpublic static final String SERVER_IP = "192.168.192.128";// Field from the variant APIpublic static final Int SERVER_PORT = 3000;
}

虽然语法上是错的,但是它还是生成了,这是Java代码,不是Kotlin,Java中是没有Int类型的,只有小写的int,有时候不注意,用kotlin习惯了,一下子转不过来,明明Int生成了,但是为什么使用的时候报错,如下:
在这里插入图片描述
报错原因就是Java中没有Int类型只有int类型。

http://www.dtcms.com/a/336899.html

相关文章:

  • 项目实战——矿物识别系统(利用机器学习从化学元素数据中识别矿物,从数据到分类模型)
  • 咨询进阶——解读咨询顾问技能模型
  • NL2SQL 技术深度解析与项目实践
  • Jmeter对图片验证码的处理
  • single cell ATAC(5)使用ArchR聚类
  • CentOS 7.9 部署 filebrowser 文件管理系统
  • 深入解析 Qwen3 GSPO:一种稳定高效的大语言模型强化学习算法
  • 运维命令基础
  • 算法魅力-BFS解决多源最短路
  • PPT生成视频的AI大模型应用技巧
  • 基于51单片机霍尔测速仪表测转速调速系统设计
  • Java 大视界 -- Java 大数据在智能安防视频监控系统中的视频内容理解与智能预警升级(401)
  • Java封装
  • Orange的运维学习日记--45.Ansible进阶之文件部署
  • Rust 入门 生命周期-next2 (十九)
  • Kubernetes配置管理全攻略:ConfigMap与Secret详解
  • [机器学习]10-基于ID3决策树算法的西瓜数据集分类
  • Apache RocketMQ,构建云原生统一消息引擎
  • 如何用github记录mit6s081-2020-labs学习过程
  • SQL注入防御
  • MacOS 安全机制与“文件已损坏”排查完整指南
  • 【前端】使用Vue3过程中遇到加载无效设置点击方法提示不存在的情况,原来是少加了一个属性
  • 动态规划:入门思考篇
  • SQL详细语法教程(五)事务和视图
  • zsh 使用笔记 命令行智能提示 bash智能
  • mac查看nginx安装位置 mac nginx启动、重启、关闭
  • 我的第一个开源项目:从0到1,我在GitHub写下的成长印记
  • OpenCV Python——Numpy基本操作(Numpy 矩阵操作、Numpy 矩阵的检索与赋值、Numpy 操作ROI)
  • 母猪姿态转换行为识别:计算机视觉与行为识别模型调优指南
  • 使用 ipconfig /all 获取电脑 IP 地址