免费做ppt网站,网站建设经费预算策划书,买域名自己做网站,seo网站优化知识前言#xff1a;
我们开发过程中#xff0c;经常会面对针对不同的渠道#xff0c;要产生差异性代码和资源的场景。目前谷歌其实为我们提供了一套渠道包的方案#xff0c;这里简单描述一下。
比如我主模块依赖module1和module2。如果主模块中声明了2个渠道A和B#xff0c…前言
我们开发过程中经常会面对针对不同的渠道要产生差异性代码和资源的场景。目前谷歌其实为我们提供了一套渠道包的方案这里简单描述一下。
比如我主模块依赖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 version1.0 encodingutf-8 ?!-- 渠道依赖配置表 --
flavors-configmodule-flavors namemodule-flavorsmodule-flavor module-namemodule-mapflavor namemarket1 /flavor namemarket2 //module-flavormodule-flavor module-namemodule-adapterflavor namemarket1 /flavor namemarket2 //module-flavor/module-flavorsproject-flavors nameproject-flavorsflavor namechannelAflavor-item namemodule-map flavor-namemarket1 no-usetrue /flavor-item namemodule-adapter flavor-namemarket1 //flavorflavor namechannelBflavor-item namemodule-map flavor-namemarket1 /flavor-item namemodule-adapter flavor-namemarket2 //flavor/project-flavors
/flavors-config
所以整个需求需要实现以下几块功能点
1.在XML中声明对应的子模块的渠道以及主模块/子模块的对应关系
2.子模块的build.gradle引入配置使用XML中配置的子模块渠道进行productFlavors的动态生成
3.主模块中根据xml的配置生成对应的主模块渠道以及主模块渠道依赖的子模块渠道。
4.某些极端场景下的处理。比如主模块渠道甲依赖12两个模块而主模块渠道乙依赖123三个渠道这种不对称关系的兼容处理。
下面就来分几章对这几块功能点一一讲解。 二.子模块根据配置动态生成渠道
第一章中已经列出来了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.gradleandroid {...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 channelPROJECT_FLAVOR.each { flavorName, configList -productFlavors.create(flavorName) {dimension channelmatchingFallbacks configList.collect { subList -return subList.take(2).collect { it.replace(module-, ) }.join(-)}}}
}
其实上面的代码就是让sync完成后动态生成类似下面这样的配置
android{flavorDimensions channelproductFlavors {channelA {dimension channelmatchingFallbacks [map_market1, adapter_market1]}channelB {dimension channelmatchingFallbacks [map_market1, adapter_market2]}}
}这样主模块的channelA就会被指定使用map的map_market1渠道以及adapter的adapter_market1渠道。channelB同理也是一样。 说到人也会有人会提为什么不使用configuration进行配置。比如
implementation project(path: :module_map, configuration: market1)
这个我也尝试过GPT和百度后都有这样的方案说明但是实际上跑出来我发现根本没有把对应模块module_map中的渠道代码打进去尝试了一天发现这个方案是行不通的。 四.不对称场景的处理
如果主模块渠道甲依赖12两个模块而主模块渠道乙依赖123三个渠道这种不对称关系的如何处理
在我看来虽然渠道甲并不依赖模块3但是如果把模块3一并打入也并不影响逻辑。我只要把对应的路由类中的路由代码干掉即可。
所以最简单的方案我可以在编译的时候动态去配置生成不同的BuildConfig这样我就可以根据BuildConfig中不同的配置来进行对应的处理了。
比如我在xml中添加no-use选项代表不使用。
project-flavors nameproject-flavorsflavor namechannelAflavor-item namemodule-map flavor-namemarket1 no-usetrue /flavor-item namemodule-adapter flavor-namemarket1 //flavorflavor namechannelBflavor-item namemodule-map flavor-namemarket1 /flavor-item namemodule-adapter flavor-namemarket2 //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