在我的开发中,有这样的需求,有一个项目,需要适配不同的执法仪设备,这些执法仪都是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"]}head {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(".hello")   -> 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.pswp.cn/pingmian/93599.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93599.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/93599.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用DeepSeek解析长pdf的文本

直接使用python工具解析pdf文件&#xff0c;可能因为格式兼容问题&#xff0c;导致解析出的文本几乎不可读。 这里尝试使用Deepseek解析pdf文件&#xff0c;这里仅考虑文本&#xff0c;不考虑其他要素。 1 解析第一步 将pdf作为附件上传到deepseekchat界面&#xff0c;输入如…

W3C CSS 活动

W3C CSS 活动 引言 CSS(层叠样式表)是现代网页设计中不可或缺的技术之一。W3C(万维网联盟)作为全球互联网标准制定的主要组织,定期举办各类CSS相关的活动,旨在促进CSS技术的发展与普及。本文将详细介绍W3C CSS活动,包括活动内容、参与方式以及活动意义。 活动内容 1…

React Native 与 UniApp 对比

React Native 优点: 由 Facebook 开发维护&#xff0c;社区生态强大 使用 JavaScript/TypeScript 开发&#xff0c;学习曲线相对平缓 真正的原生渲染&#xff0c;性能接近原生应用 支持热重载&#xff0c;开发效率高 可访问所有原生平台 API 大型应用案例丰富(如 Faceboo…

Dijkstra和多层图 0

众所周知&#xff0c;Dijkstra经常拿来解决不带负权和环的单元最短路。我们先来看一下他的实现过程 (由于朴素版用的不多&#xff0c;我们直接上堆优化) 模板 #include<bits/stdc.h> #define mf(x,y) make_pair(x,y)//x距离&#xff0c;y节点 using namespace std; …

【驱动】RK3576:桌面操作系统基本概念

1、桌面操作系统 我们常说的Ubuntu、Debian、麒麟、统信等都是总包工头; 他们把linux内核、根文件系统(遵循 Linux 标准文件系统层次结构FHS)、包管理(软件、库)、桌面环境(GNOME、Xfce等)、初始化系统(Systemd)、各种服务与守护进程、安全组件等整合成一个完整的桌面…

sfc_os!SfcQueueValidationRequest函数分析之sfc_os!IsFileInQueue

第一部分&#xff1a;1: kd> kc# 00 sfc_os!SfcQueueValidationRequest 01 sfc_os!SfcWatchProtectedDirectoriesWorkerThread 02 kernel32!BaseThreadStart1: kd> dvRegVal 0x01129164ChangeType 5vrd 0x012bfef0Status 0n1988337684vrdexisting 0x012bffdc//// if…

100202Title和Input组件_编辑器-react-仿低代码平台项目

文章目录1 开发两个问卷组件1.1 Title组件1.2 Input组件1.3 画布静态展示TItle和Input2 Ajax获取问卷数据&#xff0c;并存储到Redux store2.1 API接口2.2 组件列表存储到Redux store统一管理2.3 重构useLoadQuestionData3 在画布显示问卷列表&#xff0c;点击可选中3.1 Redux获…

设置计划任务自动备份mysql

windows系统下1.创建mysql自动备份脚本mysqlback.bat需将此脚本存放在mysql的bin文件夹下。确保此脚本执行成功了在进行第2步做计划任务。echo off REM 定义备份目录backup_dir、备份的文件名filename set "backup_dirD:\mysqlback" set "filenamemysqlback_%da…

飞机起落架轮轴深孔中间段电解扩孔内轮廓检测 - 激光频率梳 3D 轮廓检测

摘要&#xff1a;飞机起落架轮轴深孔中间段电解扩孔内轮廓检测存在精度要求高、结构复杂等挑战。本文针对电解扩孔特殊工艺特征&#xff0c;探讨激光频率梳 3D 轮廓检测技术的应用&#xff0c;分析其检测原理、技术优势及在轮轴深孔检测中的实践&#xff0c;为电解扩孔内轮廓高…

【软考中级网络工程师】知识点之入侵防御系统:筑牢网络安全防线

目录一、入侵防御系统基础概念1.1 定义与作用1.2 与其他安全设备的关系二、入侵防御系统工作原理剖析2.1 数据包捕获与预处理2.2 深度包检测&#xff08;DPI&#xff09;技术2.3 威胁特征匹配2.4 行为分析与机器学习辅助检测2.5 威胁处理与响应机制三、入侵防御系统功能全面解析…

Python爬虫实战:研究scrapfly-scrapers库,构建电商/新闻/社交媒体数据采集系统

1. 引言 1.1 研究背景与意义 在大数据与人工智能技术深度渗透各行业的背景下,数据已成为企业决策、学术研究、产品创新的核心驱动力。互联网作为全球最大的信息载体,蕴含海量结构化与非结构化数据(如电商商品信息、新闻资讯、社交媒体动态等),其价值挖掘依赖高效的数据采…

Python爬虫反爬检测失效问题的代理池轮换与请求头伪装实战方案

Python爬虫反爬检测失效问题的代理池轮换与请求头伪装实战方案 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是…

【原理】C#构造函数可以标记为Static吗

【从UnityURP开始探索游戏渲染】专栏-直达 实例构造函数&#xff08;Instance Constructor&#xff09;不能标记为static但C#提供了一种特殊的‌ 静态构造函数&#xff08;Static Constructor&#xff09;专门用于初始化静态成员。下面依次介绍他们&#xff1a; 1. ‌实例构造…

数据结构--树(3)

数据结构基础&#xff08;13&#xff09; 文章目录数据结构基础&#xff08;13&#xff09;--树树的存储结构树的存储方式1&#xff1a;双亲表示法&#xff08;顺序存储&#xff09;树的存储方式2&#xff1a;孩子表示法树的存储方式3&#xff1a;孩子兄弟表示法树转二叉树森林…

sys.stdin读取键盘输入【持续更新~】

背景sys.stdin主要用来读取键盘的一行或者多行输入&#xff0c;读取后表达形式为字符串。下文主要探讨sys.stdin.readline()的使用&#xff0c;sys.stdin.read()参考&#xff1a;sys.stdin.readline()是逐行读取&#xff0c;通常会配合.strip()清除首尾的换行符/空格sys.stdin.…

近阈值技术引领者:STM32U3系列的能效与安全革新

引言 当电池供电设备已深度融入生活的每一个角落&#xff0c;功耗控制与续航能力俨然成为制约技术演进的核心瓶颈。在此背景下&#xff0c;超低功耗新系列STM32U3凭借前沿的近阈值设计理念&#xff0c;为受功耗瓶颈限制的设备提供了突破性解决方案&#xff0c;也为能耗管理开启…

Vue3 中的 provide 和 inject 详解:实现跨组件通信

一、provide 和 inject 概述在 Vue3 中&#xff0c;provide 和 inject 是一对用于实现跨层级组件通信的 API&#xff0c;它们解决了 props 需要逐层传递的繁琐问题。1.1 基本概念provide (提供)&#xff1a;在祖先组件中提供数据inject (注入)&#xff1a;在任意后代组件中注入…

Kafka 零拷贝(Zero-Copy)技术详解

文章目录1. 什么是零拷贝2. Kafka 如何实现零拷贝2.1 sendfile 系统调用2.2 mmap 内存映射3. 传统拷贝 vs 零拷贝3.1 传统文件传输流程3.2 零拷贝文件传输流程4. Kafka 零拷贝的具体实现4.1 消息消费时的零拷贝4.2 日志段文件的零拷贝5. 零拷贝带来的性能优势6. 零拷贝的适用场…

Vue 中 v-for 的使用及 Vue2 与 Vue3 的区别

v-for 基本用法v-for 是 Vue 中用于循环渲染列表的指令&#xff0c;基本语法如下&#xff1a;运行<!-- Vue2 和 Vue3 通用基本语法 --> <div v-for"(item, index) in items" :key"item.id">{{ index }} - {{ item.name }} </div>Vue2 和…

本地搭建dify+deepseek智能体

今天开始搭建智能体&#xff0c;学习一下&#xff0c;也是公司转型所需。(Windows下的docker安装给我差点干破防了&#xff0c;安装了一周docker才成功。我真就要放弃的时候&#xff0c;又意外成功了/(ㄒoㄒ)/~~)0、准备阶段 配置Windows10的基本配置。 按下键盘Windows键&…