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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/146851.html

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

相关文章

【Java 进阶篇】JDBC ResultSet 遍历结果集详解

在Java数据库编程中&#xff0c;经常需要执行SQL查询并处理查询结果。ResultSet&#xff08;结果集&#xff09;是Java JDBC中用于表示查询结果的关键类之一。通过遍历ResultSet&#xff0c;我们可以访问和操作从数据库中检索的数据。本文将详细介绍如何使用JDBC来遍历ResultSe…

手把手教你做个智能加湿器(一)

一、前言 目前常见的加湿器类电子产品一般是由PCBA和外壳组成&#xff0c;我们将从PCB设计&#xff0c;然后编写软件&#xff0c;接着设计外壳&#xff0c;设计出一个完整的产品出来。 需要使用到软件&#xff1a; Altium Designer 17 SolidWorks 2019 Keil 4 二…

Error: node: unknown or unsupported macOS version: :dunno 错误解决

一、原因 今天安装 brew install node报错了&#xff0c;错误信息如下&#xff1a; 二、解决方案 1&#xff09;查找homebrew-cask安装位置 echo $(brew --repo homebrew/homebrew-cask) // 输出 /opt/homebrew/Library/Taps/homebrew/homebrew-cask2&#xff09;使用 gi…

selenium下载安装 -- 使用谷歌驱动碰到的问题

安装教程参考: http://c.biancheng.net/python_spider/selenium.html 1. 谷歌浏览器和谷歌驱动版本要对应(但是最新版本谷歌对应的驱动是没有的,因此要下载谷歌历史其他版本): 谷歌浏览器历史版本下载: https://www.chromedownloads.net/chrome64win/谷歌浏览器驱动下载: http:…

计算机网络分层结构

一、OSI参考模型(法定标准) 1.由国际标准化组织(ISO)提出的开放系统互连(OSI)参考模型 2.OSI七层结构&#xff1a; 3.通信过程&#xff1a; 4.各层功能 应用层-能和用户交互产生网络流量(需要联网)的程序&#xff0c;常见协议有文件传输(FTP)、电子邮件(SMTP)、万维网(HTTP)…

SpringCloud(一)Eureka、Nacos、Feign、Gateway

文章目录 概述微服务技术对比 Eureka服务远程调用服务提供者和消费者Eureka注册中心搭建注册中心服务注册服务发现Ribbon负载均衡负载均衡策略饥饿加载 NacosNacos与Eureka对比Nacos服务注册Nacos服务分集群存储NacosRule负载均衡服务实例权重设置环境隔离 Nacos配置管理配置热…

算法精品讲解(2)——DP问题入门(适合零基础者,一看就会)

目录 前言 DP问题它是什么&#xff08;了解&#xff09; 从中学的例题谈起 再来说一下&#xff0c;DP问题的核心思想&#xff08;理解&#xff09; DP问题的解决方法 先说方法论&#xff1a; 再说具体的例子 例一&#xff1a; 例二&#xff1a; 例三&#xff1a; DP和…

使用ExLlamaV2在消费级GPU上运行Llama2 70B

Llama 2模型中最大也是最好的模型有700亿个参数。一个fp16参数的大小为2字节。加载Llama 270b需要140 GB内存(700亿* 2字节)。 只要我们的内存够大&#xff0c;我们就可以在CPU上运行上运行Llama 2 70B。但是CPU的推理速度非常的慢&#xff0c;虽然能够运行&#xff0c;速度我…

postgresql-管理数据表

postgresql-管理数据表 创建表数据类型字段约束表级约束模式搜索路径 修改表添加字段删除字段添加约束删除约束修改字段默认值修改字段数据类型重命名字段重命名表 删除表 创建表 在 PostgreSQL 中&#xff0c;使用 CREATE TABLE 语句创建一个新表&#xff1a; CREATE TABLE …

量化交易全流程(四)

本节目录 数据准备&#xff08;数据源与数据库&#xff09; CTA策略 数据源&#xff1a; 在进行量化分析的时候&#xff0c;最基础的工作是数据准备&#xff0c;即收集数据、清理数据、建立数据库。下面先讨论收集数据的来源&#xff0c;数据来源可分为两大类&#xff1a;免…

nodejs开发环境搭建

Nodejs是一个开源的、跨平台JavaScript运行时环境&#xff0c;其使用V8引擎对JavaScript脚本执行解释&#xff0c;在前后端分离的应用架构设计中&#xff0c;其既能支持web页面服务应用的开发、也能支持后端接口服务应用的开发&#xff0c;类似于Java语言的J2EE运行时环境&…

Django基础入门操作 (Django-01)

一 背景介绍 Django是一个开源的 Web应用框架&#xff0c;由Python写成。采用了MTV的框架模式&#xff0c;它最初是被用来做CMS&#xff08;内容管理系统&#xff09;软件。 官方中文文档&#xff1a;Django 文档 | Django 文档 | Django 应用&#xff1a;做内容管理系统(新…

Swing程序设计(4)JLabel标签和导入图片

文章目录 前言一、JLabel标签 1.介绍2.实例展示二、JLabel中绘图和导入图片 1.自定义绘图2.导入图片总结 前言 本文介绍了Swing程序中JLabel标签的使用&#xff0c;以及在标签中导入图片和自定义图标的方法。 一、JLabel标签的使用 1.介绍 JLabel标签&#xff1a;在Swing程序中…

【生物信息学】计算图网络中节点的中心性指标:聚集系数、介数中心性、度中心性

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 3. IDE 三、实验内容 0. 导入必要的工具 1. 生成邻接矩阵simulate_G: 2. 计算节点的聚集系数 CC(G): 3.计算节点的介数中心性 BC(G) 4. 计算节点的度中心性 DC(G) 5. 综合centrality(G) 6. 代…

基于web的画作展示系统/作品展示平台

摘 要 网络的广泛应用给生活带来了十分的便利。所以把画作展示系统与现在网络相结合&#xff0c;利用JSP技术建设画作展示系统&#xff0c;实现画作展示系统的信息化。则对于进一步提高画作展示系统的发展&#xff0c;丰富画作展示系统经验能起到不少的促进作用。 画作展示系统…

NLP中token总结

Token 可以被理解为文本中的最小单位。在英文中&#xff0c;一个 token 可以是一个单词&#xff0c;也可以是一个标点符号。在中文中&#xff0c;通常以字或词作为 token。ChatGPT 将输入文本拆分成一个个 token&#xff0c;使模型能够对其进行处理和理解 在自然语言处理&#…

【QT】自定义组件ui类添加到主ui界面方法

1.添加自定义组件到项目中 add new选择如下 写好类方法&#xff0c;确定即可 2.将新创建的ui类加入到主ui界面 选中新创建ui类的父类空块&#xff0c;右键选择提升为 选择并添加新创建的类

asp.net core mvc 文件上传,下载,预览

//文件上传用到了IformFile接口 1.1文件上传视图 <form action"/stu/upload" method"post" enctype"multipart/form-data"><input type"file" name"img" /><input type"submit" value"上传&…

【通意千问】大模型GitHub开源工程学习笔记(2)--使用Qwen进行推理的示例代码解析,及transformers的库使用

使用Transformers来使用模型 如希望使用Qwen-chat进行推理,所需要写的只是如下所示的数行代码。请确保你使用的是最新代码,并指定正确的模型名称和路径,如Qwen/Qwen-7B-Chat和Qwen/Qwen-14B-Chat 这里给出了一段代码 from transformers import AutoModelForCausalLM, Aut…

多目标平衡优化器黏菌算法(MOEOSMA)求解CEC2020多模式多目标优化

多目标平衡优化器黏菌算法&#xff08;MOEOSMA&#xff09;比现有的多目标黏菌算法具有更好的优化性能。在MOEOSMA中&#xff0c;动态系数用于调整勘探和开采趋势。采用精英存档机制来促进算法的收敛性。使用拥挤距离法来保持Pareto前沿的分布。采用平衡池策略模拟黏菌的协同觅…