vivo官网App模块化开发方案-ModularDevTool
作者:vivo 互联网客户端团队- Wang Zhenyu
本文主要讲述了Android客户端模块化开发的痛点及解决方案,详细讲解了方案的实现思路和具体实现方法。
说明:本工具基于vivo互联网客户端团队内部开源的编译管理工具开发。
一、背景
现在客户端的业务越来越多,大部分客户端工程都采用模块化的开发模式,也就是根据业务分成多个模块进行开发,提高团队效率。例如我们vivo官网现在的整体架构如下图,分为13个模块,每个模块是一个独立代码仓。
(注:为什么这么分,可以参考之前的一篇文章《Android模块化开发实践》)
二、痛点
完全隔离的代码仓,使每个模块更独立,更易于代码管理,但也带来了一些问题。
1、开发阶段,子仓开发以及集成开发调试,操作麻烦、易出错、难跟踪回溯
1.1、当开发时涉及的模块较多时,需要手动一个一个拉代码,多个子仓的代码操作非常麻烦,并且需要打开多个AndroidStudio进行开发;
1.2、子仓集成到主仓开发调试,有两种方式,但是都有比较大的缺点:
(1)方式1,子仓通过maven依赖,这种方式需要不断的发布子仓的snapshot,主仓再更新snapshot,效率较低;
(2)方式2,子仓通过代码依赖,也就是需要在主仓的settings.gradle中,手动include拉到本地的子仓代码,然后在build.gradle中配置dependencies,配置繁琐,容易出错;
1.3、主仓对子仓的依赖,如果是部分maven依赖、部分代码依赖,容易出现代码冲突;
1.4、apk集成的子模块aar和代码,没有对应关系,排查问题时很难回溯。
2、版本发布阶段,流程繁琐,过多重复劳动,流程如下:
2.1、逐个修改子仓的版本,指定snapshot或release;
2.2、每个子仓需要提交修改版本号的代码到git;
2.3、每个子仓都要手动触发发布maven仓;
2.4、更新主仓对子仓依赖的版本;
2.5、构建Apk;
2.6、如果用持续集成系统CI,则每个子仓都需要配置一个项目,再逐个启动子仓的编译,等子仓全部编译完再启动主仓编译。
三、方案
针对上述问题,我们优化的思路也很明确了,就是以自动化的方式解决繁琐和重复的操作。最终开发了ModularDevTool,实现以下功能:
1、开发阶段
1.1、在主仓中,管理所有子仓代码(拉代码、切分支及其他git操作),管理子仓相关信息(代码仓路径、分支、版本等);
1.2、只需要打开一个AS工程,即可进行所有仓的代码开发;
1.3、对子仓的两种依赖方式(代码依赖和maven依赖)一键切换,支持混合依赖(即部分仓代码依赖,部分仓maven依赖);
1.4、编译时输出子模块的版本及对应commitid,便于回溯跟踪代码。
2、版本发布阶段
2.1、只需要在主仓修改子仓版本号,子仓无需修改,省去子仓代码修改和提交代码过程;
2.2、CI上只要配一个主仓项目,实现一键编译,包括子仓编译aar(按依赖关系顺序编译)、上传maven、编apk;
2.3、CI上支持3种编译模式:
-
OnlyApp:即只编译主仓代码生成apk(前提是子模块已发布maven);
-
publishSnapshot:即子仓编译上传snapshot版本,然后编译主仓生成apk;
-
publishRelease:即子仓编译上传release版本,然后编译主仓生成apk。
四、ModularDevTool概览
工具采用了shell脚本+gradle插件的方式实现的。
首先看下工程目录概览
1、submodules目录是用来存放子仓代码的,子仓代码就是正常的工程结构,submodules目录如下图:
2、repositories.xml文件是用来配置子仓信息的,包括模块名、代码仓、分支、版本等,具体内容如下:
<?xml version="1.0" encoding="utf-8" ?>
<repositories><!-- 一个repository表示一个仓库,一个仓库下可能会有多个module --><repository><!-- 仓库名称,可以随意定义,主要用于本地快速识别 --><name>lib模块</name><!-- 上传至maven时的groupid --><group>com.vivo.space.lib</group><!-- 配置仓库中的所有子模块,如果多个module就添加多个module标签 --><modules><module><!-- 上传至maven时的artifactid --><artifactid>vivospace_lib</artifactid><!-- 上传至maven时的版本号 --><version>5.9.8.0-SNAPSHOT</version><!-- 编译顺序优先级,越小优先级越高 --><priority>0</priority></module></modules><!-- 注意仓库地址中的个人ssh名称要使用$user占位符代替 --><repo>ssh://$user@smartgit:xxxx/VivoCode/xxxx_lib</repo><!-- 开发分支,脚本用来自动切换到该分支 --><devbranch>feature_5.9.0.0_xxx_dev</devbranch><!-- 打release包时必须强制指定commitId,保证取到指定代码 --><commitid>cbd4xxxxxx69d1</commitid></repository><!-- 多个仓库就添加多个repository -->...
</repositories>
3、vsub.sh脚本是工具各种功能的入口,比如:
./vsub.sh sync:拉取所有子模块代码,代码存放在主工程下的submodules目录中
./vsub.sh publish:一键编译所有子仓,并发布aar到maven
4、subbuild目录用来输出子仓的git提交记录,subError目录用来输出子仓编译异常时的log。
五、关键功能实现
ModularDevTool主要功能分为两类,一类是代码管理,用于批量处理git操作;第二类是项目构建,实现了动态配置子模块依赖、子模块发布等功能。
5.1 代码管理
vsub.sh脚本中封装了常用的git命令,用于批量处理子仓的git操作,实现逻辑相对简单,利用shell脚本将git命令封装起来。
比如 ./vsub.sh -pull的实现逻辑,首先是cd进入submodules目录(submodules目录存放了所有子仓代码),然后遍历进入子仓目录执行git pull --rebase命令,从而实现一个命令完成对所有子仓的相同git操作,实现逻辑如下:
<!-- ./vsub.sh -pull代码逻辑 -->cd submodulespath=$currPathfiles=$(ls $path)for fileName in $filesdoif [ ! -d $fileName ]thencontinueficd $fileNameecho -e "\033[33mEntering $fileName\033[0m"git pull --rebasecd ..done
5.2 项目构建
(1)Sync 功能
通过执行./vsub.sh sync命令将所有子模块的代码拉取到主工程的submodules目录中。
Sync命令有3个功能:
1)如果子仓代码未拉取,则拉取代码,并切换到repositories.xml中配置的devbranch;
2)如果子仓代码已拉取,则切换到repositories.xml中配置的devbranch;
3)考虑到在一些场景(比如jenkins构建),使用分支检出代码可能会存在异常,在sync命令后面加 -c 参数,则会使用repositories.xml中配置的commitid检出指定分支代码。
Sync流程如下:
(2)子模块依赖处理
在之前我们依赖不同子仓的代码时,需要手动修改settings.gradle导入子模块,然后修改build.gradle中的dependencies,如下图。
<!-- settings.gradle -->
include ':app',':module_name_1',':module_name_2',':module_name_3'...project(':module_name_1').projectDir = new File('E:/AndroidCode/module_name_1/code/')
project(':module_name_2').projectDir = new File('E:/AndroidCode/module_name_2/code/')
project(':module_name_3').projectDir = new File('E:/AndroidCode/module_name_3/code/')
...
<!-- build.gradle -->
dependencies {api fileTree(dir: 'libs', include: ['*.jar'])// 业务子模块 beginapi project (':module_name_1')api project (':module_name_2')api project (':module_name_3')// 业务子模块 end
}
...
团队中每个人代码的存放位置不同,在新版本拉完代码后都需要手动配置一番,比较繁琐。
基于sync功能已经把所有的子仓代码都拉到了submodules目录中,现在我们项目在构建时只需简单配置local.properties即可(local.properties配置如下图),确定哪些子模块是代码依赖,哪些子模块是maven依赖。
<!-- 其中key module_name_x表示子模块名,value 0表示maven依赖,1表示代码依赖,默认是maven依赖,也就是,如果不配置某些子模块则默认maven依赖 -->
module_name_1=0
module_name_2=0
module_name_3=1
module_name_4=1
module_name_5=1
module_name_6=1
子模块依赖处理的流程如下:
(3)publish功能
通过执行./vsub.sh publish命令实现一键编译所有子模块aar并上传maven。
publish命令主要有4个功能:
1)如果子仓代码未拉取,则自动拉取子仓代码;
2)如果是发布snapshot版本,则切换到devbranch分支最新代码,version中包含snapshot字符串的子模块,编译生成aar并上传maven;否则,则直接跳过,不会编译;
3)如果是发布release版本(即指定-a参数),则切换到commitid对应的代码,编译生成release版本的aar,并上传maven;
4)子仓的编译上传顺序根据配置的priority优先级来执行。
注:上述的devbranch、version、commitid、priority等都是repositories.xml中的配置项。
publish发布子模块的流程如下:
六、ModularDevTool接入
接入本方案的前提是项目采用多代码仓的方式进行模块化开发。具体接入步骤也比较简单。
第一步,主仓依赖gradle插件modular_dev_plugin;
(该插件包含settings、tools、base、publish四个子插件,其中settings、tools和base插件配合实现子仓代码管理、动态依赖处理,publish插件实现子仓的aar发布)
第二步,主仓的settings.gradle应用settings插件,主仓的app build.gradle中应用tools和base插件;
第三步,主仓根目录添加repositories.xml配置文件和vsub脚本;
第四步,子仓依赖modular_dev_plugin,并应用publish插件;
第五步,中间层的子仓(比如App→Shop→Lib,那Shop就是中间层子仓)对下一层子仓的依赖版本号改成占位符,项目构建时会自动替换成repositories.xml中的版本号。如下图:
dependencies {// 对lib仓的依赖,原来是依赖具体的版本号,现在改成“unified”占位符,项目构建时会自动替换成repositories.xml中的版本号api "com.vivo.space.lib:vivospace_lib:unified"
}
至此,ModularDevTool就接入完成了。
七、现在的开发流程
基于这个工具,现在我们官网的开发流程如下:
第一步是clone主App仓代码,checkout对应开发分支,并在AndroidStudio打开工程;
第二步是修改repositories.xml配置,需要进行开发的子仓,修改devbranch为对应开发分支,修改version为对应版本号;
第三步,通过./vsub.sh sync命令,检出所有子模块代码;
第四步,修改local.properties中子仓依赖的模式(maven依赖or代码依赖),修改完成后点击Sync一下,然后就可以正常进行代码开发了,开发体验与单工程多module模式完全一样。
八、总结
这个工具已经很成熟,在vivo钱包、vivo官网等项目已经使用多年,通过该工具,开发阶段,实现多业务模块集成式开发,解决代码仓分散管理和手动配置依赖等繁琐操作,发布阶段,实现多种编译模式以及一键编包能力,对于团队的开发效率有很大提升,支撑官网app项目3+业务线并行迭代,并且代码冲突降低50%以上。
相关文章:
vivo官网App模块化开发方案-ModularDevTool
作者:vivo 互联网客户端团队- Wang Zhenyu 本文主要讲述了Android客户端模块化开发的痛点及解决方案,详细讲解了方案的实现思路和具体实现方法。 说明:本工具基于vivo互联网客户端团队内部开源的编译管理工具开发。 一、背景 现在客户端的业…...
Python基础-数据类型之数字类型
变量中的变量值是用来存储事物状态的,事物的状态分成不同的种类(例如:人的姓名、年龄,身高、职位、工资等),因此变量值有多种不同的数据类型。 age 18 # 用整型记录年龄 salary 3.1 # 用浮点型记录…...
基于Web的6个完美3D图形WebGL库
现代前端、游戏和Web开发正是WebGL可以转化为数字杰作的东西。使用GPU绘制在浏览器屏幕上生成的矢量元素,WebGL创建交互式Web图形,从而获得用户体验。视觉元素的质量和复杂性使该工具在HTML或CSS等其他方法中脱颖而出。WebGL基础WebGL不是一个图形套件。…...
界面组件DevExpress Reporting v22.2 - 增强的Web报表组件UI
DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。DevExpress Reporting v22.2版本已正式发布&…...
初学vector
目录 string的收尾 拷贝构造的现代写法: 浅拷贝: 拷贝构造的现代写法: swap函数: 内置类型有拷贝构造和赋值重载吗? 完善拷贝构造的现代写法: 赋值重载的现代写法: 更精简的现代写法&…...
Windows10 安装wsl2、Ubuntu相关操作
Windows10 安装wsl2、Ubuntu相关操作 安装wsl2 查看本机windows版本: 键盘上按下winr,输入winver,查看系统版本。必须运行 windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 windows 11。满足版本要求后…...
SpringBoot简单使用MongoDB
MongoDB介绍 SpringBoot简单使用MongoDB 一、配置步骤 1、application.yml 2、pom 3、entity 4、mapper 二、案例代码使用 1、库 前期准备上一篇安装MongoDB地址http://t.csdn.cn/G4oYJ 跟关系型数据库概念对比 Mysql MongoDB Database(数据库) Datab…...
Oracle Data Guard 角色转换(Role Transitions)
查询视图V$DATABASE的DATABASE_ROLE列可以看到数据库当前的角色。 1.角色转换介绍 Oracle Data Guard让你可以使用SQL语句或者通过Oracle Data Guard broker界面来动态更改数据库的角色,Oracle Data Guard支持以下的角色转换: 1࿰…...
opencv的TrackBar控件
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...
关于基线长度对双天线GNSS测姿精度的影响
文章目录一、GNSS测姿原理1. 载波相位双差求解基线向量2. GNSS姿态角表示二、基线长度对GNSS测姿精度的影响三、GNSS定向产品精度描述实例四、参考文献在GNSS定向模块或者板卡的指标参数中,我们一般会看到航向的测量精度和基线的长度相关。在实际使用,用…...
口交换机睿易 RG-NBS1826GC 24 口
接口形态不将就,标配光纤接口传输性能不将就,标配千兆上联口和大缓存设计端口数量不将就,8/16/24 三种选择楼宇对讲交换机不将就,保证开锁指令品质服务不将就,监控专用交换机接口形态不将就,标配光纤接口非…...
如何在Excel中向下拉列表中添加条件
在Excel中向下拉列表中添加条件 创建矩阵型数据集创建下拉列表创建第一个下拉列表创建第二个下拉列表你可以使用Microsoft Excel下拉列表来显示一个简单的列表,尽管有时需要更多的控制。假设你的人员分散在四个地区:北部、南部、东部和西部。你希望按地区与人员合作,而不是与…...
自定义bean 加载到spring IOC容器中
自定义bean加载到spring容器中的两种方式: 1.在类上添加注解Controller、RestController(本质是Controller)、Service、Repository、Component2.使用Configuration和Bean 这篇文章主要介绍第二种方式原理(因为在实际使用中&#…...
[python入门㊻] - python装饰器和类的装饰器
目录 ❤ python装饰器介绍 ❤ 什么是装饰器 ❤ 装饰器的流程 ❤ 定义装饰器时通常会涉及以下3个函数 无参装饰器 有参装饰器 多重装饰器 ❤ 装饰器的用法(闭包) ❤ 装饰器语法糖 ❤ 时间计时器 ❤ 装饰器中wraps作用 不使用wraps装饰器 使用wraps装饰器解…...
企业级信息系统开发学习1.1 初识Spring——采用Spring配置文件管理Bean
文章目录一、Spring容器演示——采用Spring配置文件管理Bean(一)创建Maven项目(二)添加Spring依赖(三)创建杀龙任务类(四)创建勇敢骑士类(五)采用传统方式让勇…...
CSS盒子模型
盒子模型 CSS三大特性 继承性、层叠性、优先级 优先级比较 继承 < 通配符选择器 < 标签选择器 < 类选择器 < id选择器 < 行内样式 < !important 注意:!important不能提升继承的优先级,只要是继承优先级最低 复合选择器权重叠加计…...
Python基础学习笔记 —— 数据结构与算法
数据结构与算法1 数据结构基础1.1 数组1.2 链表1.3 队列1.4 栈1.5 二叉树2 排序算法2.1 冒泡排序2.2 快速排序2.3 (简单)选择排序2.4 堆排序2.5 (直接)插入排序3 查找3.1 二分查找1 数据结构基础 本章所需相关基础知识:…...
笔记本连接wifi,浏览器访问页面,显示访问被拒绝
打开chrome、edge浏览器访问第1个第2个页面正常,后面再打开页面显示异常。 但手机连接正常,笔记本连接异常,起初完全没有怀疑是wifi问题 以为用了vpn软件问题,认为中了病毒。杀毒,并没有中毒。 1、关闭vpn代理&#…...
36个物联网专业毕业论文选题推荐
物联网技术在智能家居系统中的应用研究物联网在智慧城市建设中的作用物联网在工业4.0中的实现与发展 物联网与智能物流系统的结合物联网与医疗健康领域的融合研究物联网与环境监测系统的集成物联网与农业生产的结合研究物联网技术对汽车行业的影响与发展物联网在智能安防领域的…...
Pytorch复习笔记--torch.nn.functional.interpolate()和cv2.resize()的使用与比较
1--前言 博主在处理图片尺度问题时,习惯使用 cv2.resize() 函数;但当图片数据需用显卡加速运算时,数据需要在 GPU 和 CPU 之间不断迁移,导致程序运行效率降低; Pytorch 提供了一个类似于 cv2.resize() 的采样函数&…...
ASP.NET Core MVC 项目 AOP之ActionFilterAttribute
目录 一:说明 二:实现ActionFilterAttribute父类 一:说明 ActionFilterAttribute比前两者简单方便,易于扩展,不易产生代码冗余。 ActionFilterAttribute过滤器执行顺序: 1:执行控制器中的构造函数,实例化控制器 2:执行ActionFilterAttribute.OnActionExecutionA…...
浅析EasyCVR安防视频能力在智慧小区建设场景中的应用及意义
一、行业需求 城市的发展创造了大量工作机会,人口的聚集也推动了居民住宅建设率的增长。人民生活旨在安居乐业,能否住得“踏实”是很多劳动工作者最关心的问题。但目前随着住宅小区规模的不断扩大、人口逐渐密集,在保证居住环境舒适整洁的同…...
Python的深、浅拷贝到底是怎么回事?一篇解决问题
嗨害大家好鸭!我是小熊猫~ 一、赋值 Python中, 对象的赋值都是进行对象引用(内存地址)传递, 赋值(), 就是创建了对象的一个新的引用, 修改其中任意一个变量都会影响到另一个 will …...
TCP协议十大特性
日升时奋斗,日落时自省 目录 1、确认应答 1.1、序号编辑 2、超时重传 3、连接管理 3.1、三次握手 3.2、四次挥手 4、滑动窗口 5、流量控制 6、拥塞控制 7、延时应答 8、捎带应答 9、面向字节流 10、异常情况 TCP协议: 特点:有…...
2.14作业【GPIIO控制LED】
设备树 myleds{ myled1 <&gpioe 10 0>; myled2 <&gpiof 10 0>; myled3 <&gpioe 8 0>; }; 驱动代码 #include<linux/init.h> #include<linux/module.h> #include<linux/of.h&…...
5min搞定linux环境Jenkins的安装
5min搞定linux环境Jenkins的安装 安装Jenkinsstep1: 使用wget 命令下载Jenkinsstep2、创建Jenkins日志目录并运行jekinsstep3、访问jenkins并解锁jenkins,安装插件以及创建管理员用户step4、到此,就完成了Finish、以上步骤中遇到的问题1、 jenkins启动不了2、jenkins无法访问…...
Cortex-M0存储器系统
目录1.概述2.存储器映射3.程序存储器、Boot Loader和存储器重映射4.数据存储器5.支持小端和大端数据类型数据对齐访问非法地址多寄存器加载和存储指令的使用6.存储器属性1.概述 Cortex-M0处理器具有32位系统总线接口,以及32位地址线(4GB的地址空间&…...
软件测试——测试用例之场景法
一、场景法的应用场合 场景法主要用于测试软件的业务流程和业务逻辑。场景法是基于软件业务的测试方法。在场景法中测试人员把自己当成最终用户,尽可能真实的模拟用户在使用此软件的操作情景: 重点模拟两类操作: 1)模拟用户正确…...
英文写作中的常用的衔接词
1. 增补 (Addition) in addition, furthermore, again, also, besides, moreover, whats more, similarly, next, finally 2.比较(Comparision) in the same way, similarly, equally, in comparison, just as 3. 对照 (Contrast) in contrast, on …...
新库上线 | CnOpenData中国地方政府债券信息数据
中国地方政府债券信息数据 一、数据简介 地方政府债券 指某一国家中有财政收入的地方政府地方公共机构发行的债券。地方政府债券一般用于交通、通讯、住宅、教育、医院和污水处理系统等地方性公共设施的建设。地方政府债券一般也是以当地政府的税收能力作为还本付息的担保。地…...
网罗天下做网站靠谱吗/seo交流qq群
控制语句 分支分流 循环语句判断语句 if...else...ifif 条件语句(比较 逻辑 成员运算符in)用法1:if 条件语句:子语句age 20if age > 18:#当条件是Ture时执行子语句print("恭喜你,你成年了")特殊:sif s:print("子语句执行了")这种情况等于判空操作,是空…...
网站建设要多少费用/网络推广免费网站
要想全面了解java开发工具,我们首先需要先了解一下java程序的开发过程,通过这个过程我们能够了解到java开发都需要用到那些工具。 首先我们先了解完整项目开发过程,如图所示: 从上图中我们能看到一个完整的java项目的开发包括很多…...
有限公司英文/整站优化seo
1.震动是系统的服务,首先需添加震动权限 <uses-permission android:name"android.permission.VIBRATE" /> 2.实现震动方法代码 public static void sendVibrater(Context mContext) { // 间隔震动Vibrator mVibrator (Vibrator) mContext.getSystemService(m…...
高校网站建设需求单/泉州网站建设优化
策略模式【Strategy Pattern】刘备要到江东娶老婆了,走之前诸葛亮给赵云(伴郎)三个锦囊妙计,说是按天机拆开解决棘手问题,嘿,还别说,真是解决了大问题,搞到最后是周瑜陪了夫人又折兵…...
平面设计资料网站/百度统计平台
python限定方法参数类型、返回值类型、变量类型等 typing模块的作用 自python3.5开始,PEP484为python引入了类型注解(type hints) 类型检查,防止运行时出现参数和返回值类型、变量类型不符合。作为开发文档附加说明,方便使用者调用时传入和…...
提供手机网站制作哪家好/襄阳seo
用单调栈维护斜率,使之斜率单调递增,左右各跑一遍,具体的可以看代码里的注释 #include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #include<cmath> #include <iomanip> #defi…...