作者: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 submodules path=$currPath files=$(ls $path) for fileName in $files do if [ ! -d $fileName ] then continue fi cd $fileName echo -e "\033[33mEntering $fileName\033[0m" git pull --rebase cd .. 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']) // 业务子模块 begin api 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=0module_name_2=0module_name_3=1module_name_4=1module_name_5=1module_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%以上。