作者: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=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的更多相关文章

  1. vivo官网APP全机型UI适配方案

    vivo 互联网客户端团队- Xu Jie 日益新增的机型,给开发人员带来了很多的适配工作.代码能不能统一.apk能不能统一.物料如何选取.样式怎么展示等等都是困扰开发人员的问题,本方案就是介绍不同机 ...

  2. Unity官网针对IOS开发有比较好的建议

    Unity官网针对IOS开发有比较好的建议,我总结了翻译如下,后面附上原文. 尽量控制定点数量(注意所谓顶点不是建模时的顶点,而是引擎渲染时的顶点.例如,模型一个顶点如果设置了2个法向,那么对引擎来说 ...

  3. vivo 官网资源包适配多场景的应用

    本文介绍了资源包的概念及使用场景,同时对资源包的几种使用方案进行对比.通过本文,大家可以快速掌握资源包的使用方法,解决单一配置满足多场景.多样式的问题. 一.业务背景 随着官网项目的业务深入发展,单纯 ...

  4. 官网app下载更换成微信公众号二维码 测试

    微信现在很火啊.公司官网原先提供的ios和andriod的app下载链接要求切换成微信公众号二维码.简单的替换,大家都说不需要测试直接上线.还是测了下. 1 验证所有与下载相关的信息都已去除. 包括下 ...

  5. Vue2.5开发去哪儿网App 首页开发

    主页划 5 个组件,即 header  icon  swiper recommend weekend 一. header区域开发 1. 安装 stylus npm install stylus --s ...

  6. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [3] 首页 APP 接口开发方案 ② 读取缓存方式

    以静态缓存为例. 修改 file.php line:11 去掉 path 参数(方便),加上缓存时间参数: public function cacheData($k,$v = '',$cacheTim ...

  7. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [2] 首页 APP 接口开发方案 ① 读取数据库方式

    方案一:读取数据库方式 从数据库读取信息→封装→生成接口数据 应用场景: 数据时效性比较高的系统 方案二:读取缓存方式 从数据库获取信息(第一次设置缓存或缓存失效时)→封装(第一次设置缓存或缓存失效时 ...

  8. MVC模块化开发方案

    核心: 主要利用MVC的区域功能,实现项目模块独立开发和调试. 目标: 各个模块以独立MVC应用程序存在,即模块可独立开发和调试. 动态注册各个模块路由. 一:新建解决方案目录结构 如图: 二:Eas ...

  9. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [4] 首页 APP 接口开发方案 ③ 定时读取缓存方式

    用于 linux 执行 crontab 命令生成缓存的文件 crop.php <?php //让crontab 定时执行的脚本程序 require_once 'db.php'; require_ ...

  10. Spring boot 官网学习笔记 - 开发第一个Spring boot web应用程序(使用mvn执行、使用jar执行)

    Creating the POM <?xml version="1.0" encoding="UTF-8"?> <project xmlns= ...

随机推荐

  1. codeforces补题计划

    11.15 Codeforces Round #833 (Div. 2) 知识点: D:高位和对低位无影响 E:笛卡尔树上dp 补题传送门

  2. Training: Encodings I

    原题链接:http://www.wechall.net/challenge/training/encodings1/index.php 根据题目信息貌似是让我们用这个JPK来解码,我们先点击JPK去下 ...

  3. ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。

    一:什么时候出现? 当我们用迭代器循环list的时候,在其中用list的方法新增/删除元素,就会出现这个错误. package com.sinitek.aml; import java.util.Ar ...

  4. DRF认证流程及源码分析

    认证 前言 用户验证用户是否合法登陆. 部分内容在DRF视图的使用及源码流程分析讲解,建议先看讲解视图的这篇文章. 使用流程 认证使用的方法流程如下: 自定义认证类,继承BaseAuthenticat ...

  5. linux mint 归档管理器报错Extraction not performd

    解决办法 后缀名的问题,后缀名与文件的真正类型不符合,至于到底是上面压缩类型,那只能靠尝试了,比如我这个是rar, 实际是zip,很坑,网上也没有这个问题的描述 其他 感觉 linux 对于文件类型方 ...

  6. C++编程笔记(通信)(win32平台)

    目录 一.初始化网络库 二.socket套接字 2.1服务端 2.2客户端 三.发送.接收数据 3.1发送 3.2接收数据 四.自定义的结构体 4.1 发送端 4.2接收端 IPV6版本套接字的创建 ...

  7. 【Java SE进阶】Day06 线程、同步

    一.线程 1.多线程原理 流程图 内存图解说明 创建线程的方式 继承Thread类 实现 Runnable接口 2.继承Thead类 3.实现Runnable接口 实现接口,重写run方法 最终均需要 ...

  8. TS编写发布订阅模式

    interface PubSubType { events: { [key: string]: { name: string, once: boolean, cb: Function }[] } on ...

  9. 边框 display属性 盒子模型 浮动 溢出 定位 z-index

    目录 边框 隐藏属性 钓鱼网站 display visibility 盒子模型 调整方式 浮动 溢出 圆形头像的制作 定位 z-index属性 边框 /*border-left-width: 5px; ...

  10. 【深入浅出Spring原理及实战】「源码原理实战」从底层角度去分析研究PropertySourcesPlaceholderConfigurer的原理及实战注入机制

    Spring提供配置解析功能 主要有一下xml文件占位符解析和Java的属性@Value的占位符解析配置这两种场景进行分析和实现解析,如下面两种案例. xml文件的占位符解析配置 <bean i ...