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() 的采样函数&…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...