gradle中主模块/子模块渠道对应关系通过配置实现
前言:
我们开发过程中,经常会面对针对不同的渠道,要产生差异性代码和资源的场景。目前谷歌其实为我们提供了一套渠道包的方案,这里简单描述一下。
比如我主模块依赖module1和module2。如果主模块中声明了2个渠道A和B,那么我们在module1和module2中,也可以选择创建对应的渠道A和B。这样当主模块选择A时,对应的子模块也会自动切换到渠道A。这时,主模块的渠道和子模块的渠道是一一对应的,如下图所示:
谷歌提供的这种配置,可以满足大多数的场景。但是如果我依赖的模块数量特别多时,就会产生一个新的问题。主模块和子模块的渠道并不是一一对应的。比如如下图所示,渠道甲和渠道乙都依赖模块2的渠道A,但是渠道甲依赖模块1的渠道A,而渠道乙依赖模块1的渠道B。这时候该怎么办么?本文的核心就是介绍如何解决这种复杂场景下的主模块/子模块渠道之间对应关系。
一.需求梳理
上图右中,其实还只是举一个简单的例子,作者所遇到的实际场景,要远比这个例子复杂的多。这种复杂的关系,直接写死在build.gradle中无疑是不明智的,我们应该写成一个配置文件的形式动态生成这种依赖。这样做既方便后续的维护,看起来也会更直观。
所以首先设计上,我把配置文件分成两部分:
1.子模块的渠道包声明。如下面xml中的module-flavors中所声明,有两个子模块。子模块module-map的渠道为market1和market2,子模块module-adapter的渠道为market1和market2(这里的marktet1和market2完全可以配置成不一致的)。
2.主模块依赖部分。如下面xml中的project-flavors中所声明。比如主模块的channelB渠道中,使用module-map的market1渠道和module-adapter的market2渠道。
<?xml version="1.0" encoding="utf-8" ?><!-- 渠道依赖配置表 -->
<flavors-config><module-flavors name="module-flavors"><module-flavor module-name="module-map"><flavor name="market1" /><flavor name="market2" /></module-flavor><module-flavor module-name="module-adapter"><flavor name="market1" /><flavor name="market2" /></module-flavor></module-flavors><project-flavors name="project-flavors"><flavor name="channelA"><flavor-item name="module-map" flavor-name="market1" no-use="true" /><flavor-item name="module-adapter" flavor-name="market1" /></flavor><flavor name="channelB"><flavor-item name="module-map" flavor-name="market1" /><flavor-item name="module-adapter" flavor-name="market2" /></flavor></project-flavors>
</flavors-config>
所以,整个需求需要实现以下几块功能点:
1.在XML中声明对应的子模块的渠道,以及主模块/子模块的对应关系;
2.子模块的build.gradle引入配置,使用XML中配置的子模块渠道进行productFlavors的动态生成;
3.主模块中根据xml的配置,生成对应的主模块渠道,以及主模块渠道依赖的子模块渠道。
4.某些极端场景下的处理。比如主模块渠道甲依赖1,2两个模块,而主模块渠道乙依赖1,2,3三个渠道,这种不对称关系的兼容处理。
下面,就来分几章,对这几块功能点一一讲解。
二.子模块根据配置动态生成渠道
第一章中已经列出来了xml了,所以这里就直接拿来用了。想实现子模块的渠道动态生成,我们拆分成两步:
首先,要把xml的配置,在Sync的过程中动态读取到内存中,生成对应的对象;
其次,根据对应的对象,动态生成对应的gradle配置。
2.1 读取XML中的配置
实现第一个功能点,我们可以先创建一个flavor_build.gradle文件,然后在其中声明一个Map类型的对象MODULE_FLAVOR,用来存放渠道对应关系。
ext {//以下属性通过plugin_of_flavor.xml配置def moduleFlavor = new HashMap()MODULE_FLAVOR = moduleFlavor
}
然后使用XmlParser加载配置文件,解析文件生成对应的对象并添加到MODULE_FLAVOR中。
def xmlParser = new XmlParser()
//读"渠道依赖配置表",并转换为Map
def xml = xmlParser.parse("${getRootDir().getAbsolutePath() + File.separator}plugins_of_flavor.xml")
xml.get("module-flavors").'module-flavor'.each { Node moduleNode ->def moduleName = moduleNode.attribute("module-name")def flavors = []moduleNode.value().each { Node pluginNode ->flavors.add(moduleName.replace("module-", "") + "-" + pluginNode.attribute("name"))}MODULE_FLAVOR.put(moduleName, flavors)
}
最终的效果应该和下面这样的代码类似:
moduleFlavor.put("module-adapter", ["adapter-market1", "adapter-market2"])
代表模块module-adapter中,有两个渠道:adapter-market1和adapter-market2。
2.2 子模块中动态生成渠道
切换到子模块的build.gradle,首先引入flavor_build.gradle,然后通过下面的代码自动生成对应的渠道。
apply from: '../flavor_build.gradle'android {...productFlavors {MODULE_FLAVOR.get(project.name).each {"${it as String}" {println("-------------> flavor: " + it)}}}
}
到此,第一个需求就已经实现了。
三.主模块渠道生成及和子模块的对应关系配置
仍然分为两步:
1.从XML中读取配置
2.在主模块的build.grdale中生成对应的配置项
3.1 从XML中读取配置
这个流程其实和2.1中差不多,只不过数据结构有一些区别。
ext {def projectFlavor = new HashMap()PROJECT_FLAVOR = projectFlavor
}
def xmlParser = new XmlParser()
def xml = xmlParser.parse("${getRootDir().getAbsolutePath() + File.separator}plugins_of_flavor.xml")xml.get("project-flavors")."flavor".each { Node flavorNode ->def flavorName = flavorNode.attribute("name")def flavors = []flavorNode.value().each { Node flavorItemNode ->def items = []items.add(flavorItemNode.attribute("name"))items.add(flavorItemNode.attribute("flavor-name"))flavors.add(items)}PROJECT_FLAVOR.put(flavorName, flavors)
}
最终的效果,其实和下面的代码一样:
PROJECT_FLAVOR.put("bux", [["module-map", "market1"], ["module-adapter", "market1"]])
3.2 主模块中生成对应配置项
首先是主模块的依赖关系声明,代表依赖module-adapter和module-map两个模块。
dependencies {implementation project(':demo-common')implementation project(':module-adapter')implementation project(':module-map')
}
然后在android的闭包中进行渠道的生成
android{flavorDimensions "channel"PROJECT_FLAVOR.each { flavorName, configList ->productFlavors.create(flavorName) {dimension "channel"matchingFallbacks = configList.collect { subList ->return subList.take(2).collect { it.replace("module-", "") }.join("-")}}}
}
其实上面的代码,就是让sync完成后动态生成类似下面这样的配置:
android{flavorDimensions "channel"productFlavors {channelA {dimension "channel"matchingFallbacks = ['map_market1', 'adapter_market1']}channelB {dimension "channel"matchingFallbacks = ['map_market1', 'adapter_market2']}}
}
这样,主模块的channelA就会被指定使用map的map_market1渠道以及adapter的adapter_market1渠道。channelB同理也是一样。
说到人,也会有人会提,为什么不使用configuration进行配置。比如
implementation project(path: ':module_map', configuration: 'market1')
这个我也尝试过,GPT和百度后都有这样的方案说明,但是实际上跑出来,我发现根本没有把对应模块module_map中的渠道代码打进去,尝试了一天发现这个方案是行不通的。
四.不对称场景的处理
如果主模块渠道甲依赖1,2两个模块,而主模块渠道乙依赖1,2,3三个渠道,这种不对称关系的如何处理?
在我看来,虽然渠道甲并不依赖模块3,但是如果把模块3一并打入也并不影响逻辑。我只要把对应的路由类中的路由代码干掉即可。
所以最简单的方案,我可以在编译的时候,动态去配置生成不同的BuildConfig,这样,我就可以根据BuildConfig中不同的配置来进行对应的处理了。
比如我在xml中添加no-use选项,代表不使用。
<project-flavors name="project-flavors"><flavor name="channelA"><flavor-item name="module-map" flavor-name="market1" no-use="true" /><flavor-item name="module-adapter" flavor-name="market1" /></flavor><flavor name="channelB"><flavor-item name="module-map" flavor-name="market1" /><flavor-item name="module-adapter" flavor-name="market2" /></flavor>
</project-flavors>
然后flavor.gradle中读取这个配置:
xml.get("project-flavors")."flavor".each { Node flavorNode ->def flavorName = flavorNode.attribute("name")def flavors = []flavorNode.value().each { Node flavorItemNode ->def items = []items.add(flavorItemNode.attribute("name"))items.add(flavorItemNode.attribute("flavor-name"))items.add(flavorItemNode.attribute("no-use"))flavors.add(items)}PROJECT_FLAVOR.put(flavorName, flavors)
}
随后,在主模块的build.gradle中生成对应的BuildConfig。
productFlavors.all { flavor ->def moduleList = PROJECT_FLAVOR[flavor.name]def sb = new StringBuilder("{")moduleList.each {//no-use为true时不生成对应的模块配置if (it[2] == 'true') {return}sb.append("\"").append(flavor.name).append("\"").append(",")}sb.append("}")buildConfigField("String[]", "PLUGIN_IMPL_ClASSES", sb.toString())
}
这样,如果是channelA渠道,其BuildConfig内容如下:
public static final String[] PLUGIN_IMPL_ClASSES = {"module-adapter",};
channelB渠道如下:
public static final String[] PLUGIN_IMPL_ClASSES = {"module-adapter","module-map",};
具体怎么使用,那就是路由类中的功能了,这里就不再赘述了。
五.参考资料
https://juejin.cn/post/6976508673027735588
相关文章:

gradle中主模块/子模块渠道对应关系通过配置实现
前言: 我们开发过程中,经常会面对针对不同的渠道,要产生差异性代码和资源的场景。目前谷歌其实为我们提供了一套渠道包的方案,这里简单描述一下。 比如我主模块依赖module1和module2。如果主模块中声明了2个渠道A和B,…...

28383-2012 卷筒料凹版印刷机 学习笔记
声明 本文是学习GB-T 28383-2012 卷筒料凹版印刷机. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了卷筒料凹版印刷机的型式、基本参数、要求、试验方法、检验规则、标志、包装、运输与 贮存。 本标准适用于机组式的卷筒料凹版…...

stable diffusion学习笔记【2023-10-2】
L1:界面 CFG Scale:提示词相关性 denoising:重绘幅度 L2:文生图 女性常用的负面词 nsfw,NSFW,(NSFW:2),legs apart, paintings, sketches, (worst quality:2), (low quality:2), (normal quality:2), lowres, normal quality, (…...

flink选择slot
flink选择slot 在这个类里修改 package org.apache.flink.runtime.resourcemanager.slotmanager.SlotManagerImpl; findMatchingSlot(resourceProfile):找到满足要求的slot(负责从哪个taskmanager中获取slot)对应上图第8,9&…...

世界前沿技术发展报告2023《世界信息技术发展报告》(六)网络与通信技术
(六)网络与通信技术 1. 概述2. 5G与光通讯2.1 美国研究人员利用电磁拓扑绝缘体使5G频谱带宽翻倍2.2 日本东京工业大学推出可接入5G网络的高频收发器2.3 美国得克萨斯农工大学通过波束管理改进5G毫米波通信2.4 联发科完成全球首次5G NTN卫星手机连线测试2…...

spark SQL 任务参数调优1
1.背景 要了解spark参数调优,首先需要清楚一部分背景资料Spark SQL的执行原理,方便理解各种参数对任务的具体影响。 一条SQL语句生成执行引擎可识别的程序,解析(Parser)、优化(Optimizer)、执行…...
算法练习2——移除元素
LeetCode 27 移除元素 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑…...

动态规划算法(2)--最大子段和与最长公共子序列
目录 一、最大子段和 1、什么是最大子段和 2、暴力枚举 3、分治法 4、动态规划 二、最长公共子序列 1、什么是最长公共子序列 2、暴力枚举法 3、动态规划法 4、完整代码 一、最大子段和 1、什么是最大子段和 子段和就是数组中任意连续的一段序列的和,而…...

CentOS上网卡不显示的问题
文章目录 1.问题描述 1.问题描述 ifconfig下看不到ens33网卡了。systemctl status network #查看网卡状态报下面的问题网上说的解决方式有以下三种: 第一种: 和 NetworkManager 服务有冲突,这个好解决,直接关闭 NetworkManger 服…...

localStorage实现历史记录搜索功能
📝个人主页:爱吃炫迈 💌系列专栏:JavaScript 🧑💻座右铭:道阻且长,行则将至💗 文章目录 为什么使用localStorage如何使用localStorage实现历史记录搜索功能(…...

计算机网络(一):概述
参考引用 计算机网络微课堂-湖科大教书匠计算机网络(第7版)-谢希仁 1. 计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成为一种重要的信息服务基础设施计算机网络已经像水、电、煤气这些基础设施一样,成为我们生活中不可或…...
visual code 下的node.js的hello world
我装好了visual code ,想运行一个node.js 玩玩。也就是运行一个hello world。 一:安装node.js : 我google 安装node.js 就引导我到下载页面:https://nodejs.org/en/download 有 Windows Installer (.msi) 还有Windows Binary (…...
MySQL——四、SQL语句(下篇)
MySQL 一、常见的SQL函数1、数学函数2、日期函数3、分组函数(聚合函数)4、流程控制函数 二、where条件查询和order by排序三、分组统计四、多表关联查询1、交叉连接CROSS2、内连接inner3、外连接:outer4、子查询 五、分页查询 一、常见的SQL函数 1、length(str):获…...

蓝桥杯每日一题2023.10.2
时间显示 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 输入为毫秒,故我们可以先将毫秒转化为秒,由于只需要输出时分,我们只需要将天数去除即可,可以在这里多训练一次天数判断 #include<bits/stdc.h> using namespace std…...

红外遥控器 数据格式,按下及松开判断
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。 同类产品的红…...
win32进程间通信方式(13种)
win32进程间通信 文件映射共享内存匿名管道命名管道远程过程调用(RPC)对象连接与嵌入(OLE)动态数据交换(DDE)剪贴板WM_COPYDATA消息邮件槽其它 文件映射 特点:本地间通信,不能用于网…...

基于Vue+ELement搭建动态树与数据表格实现分页模糊查询
🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚 🌟推荐给大家我的专栏《ELement》。🎯🎯 …...

多线程案例 - 单例模式
单例模式 ~~ 单例模式是常见的设计模式之一 什么是设计模式 你知道象棋,五子棋,围棋吗?如果,你想下好围棋,你就不得不了解一个东西,”棋谱”,设计模式好比围棋中的 “棋谱”. 在棋谱里面,大佬们,把一些常见的对局场景,都给推演出来了,照着棋谱来下棋,基本上棋力就不会差到哪…...

云原生Kubernetes:对外服务之 Ingress
目录 一、理论 1.Ingress 2.部署 nginx-ingress-controller(第一种方式) 3.部署 nginx-ingress-controller(第二种方式) 二、实验 1.部署 nginx-ingress-controller(第一种方式) 2.部署 nginx-ingress-controller(第二种方式) 三、问题 1.启动 nginx-ingress-controll…...

Java21 新特性
文章目录 1. 概述2. JDK21 安装与配置3. 新特性3.1 switch模式匹配3.2 字符串模板3.3 顺序集合3.4 记录模式(Record Patterns)3.5 未命名类和实例的main方法(预览版)3.6 虚拟线程 1. 概述 2023年9月19日 ,Oracle 发布了…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...

论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...