美文网首页
Android自动构建平台(Air Force)的功能与实现

Android自动构建平台(Air Force)的功能与实现

作者: typ0520 | 来源:发表于2022-05-04 13:22 被阅读0次

    背景

    在组件管理和发布流程中,开发工具链之间处于信息孤岛状态,各个节点较为分散配置文件有很多,繁琐且易出错。正式包发布流程复杂,需要和各个业务线来回确认很多次信息,沟通成本高、容易出错。AirForce在这一背景下提上日程,大目标是承载组件化相关业务、提高组件管理和发布的稳定性和效率。原文链接https://mp.weixin.qq.com/s/6RnUP-Hv1g6dpwBosmSRQw

    组件化架构

    首先介绍下得物Android工程的组件化架构,以便理解下文中的内容,我们App包含的组件概念为:APP壳工程、常规业务组件层、基础业务组件层、基础功能组件层,其他辅助模块和脚本。

    • APP壳工程 App壳工程可理解为一个main组件,依赖了很多的子组件,它只有一些配置文件。没有任何代码逻辑,根据需要选择不同的业务组件,build打包组成不同的App
    • 常规业务组件 常规业务组件就是不同的业务模块组件,按照功能化划分的业务组件,例如用户中心模块,社区模块, 首页模块等。每个业务组件都是一个小的APP,理想状态下可以单独编译,单独打包成APK在手机上运行。
    • 基础业务组件 基础业务组件是对常规业务组件对抽象和封装,提供给所有的业务方使用。例如公共组件中封装的BaseActivity,分享组件,业务埋点组件等。可以供上层的业务组件访问数据,Manager信息共享流转。
    • 基础功能组件 基础功能组件都是最基础的组件功能,不包含任何业务逻辑,可以说这些组件都是一些通用的工具类。例如日志记录组件,文件上传组件,网络服务组件,数据处理组件等;
    • 其他辅助模块和脚本 其他辅助模块可以理解为通用模块,即所有模块都需要依赖的功能,例如route组件,bridge组件。 辅助脚本可以定义各模块间的依赖关系,包含对组件库版本的管理以及第三方库的版本管理。

    组件之间必须遵循以下规则:

    • 只有上层的组件才能依赖下层组件,不能反向依赖,否则可能会出现循环依赖的情况;
    • 同一层之间的组件不能相互依赖,这也是为了组件之间的彻底解耦;

    AirForce平台承载组件化相关业务,主要包括基线管理、组件管理、业务线管理、发布管理这四大核心模块。组件管理模块是整个组件化进程中的基石;基线管理是组件化工程支持多版本并行开发的组件管理解决方案,可以认为是一组业务组件和基础组件的集合;发布管理承载APP最终出包的流程管理,包括灰度包、正式包。下面让我们来一一了解这几个核心模块吧。

    基线管理

    开发中经常会出现多个版本并行开发的情况,为了有效的隔离工作区,抽象出基线的概念,基线持有一个组件版本集合,切换基线相当于切换开发版本。根据可见范围可以把基线分为公共和私有两种类型,顾名思义公共基线是协作开发时大家一起用的版本,如果是临时做测试可以自己fork出来私有基线以防污染公共开发区。

    状态变更

    根据开发阶段不同的场景,把基线划分为三个状态开发中、预发布、已发布

    • 开发中

    这个阶段没有限制,开发人员可以自由的添加、删除、修改组件版本等操作

    • 预发布

    到这个阶段,不允许添加SNAPSHOT的组件,如果当前有SNAPSHOT类型组件一个小时通知一次,直到清零

    • 已发布

    只有SNAPSHOT类型组件清零才允许流转到此状态,到这个阶段不允许修改任何组件信息

    冲突检测

    开发阶段组件变更单提交的较为频繁,很容易出现在未感知组件列表发生变化的情况下又提交了一个组件变更单,为了解决这个问题引入了traceId的机制,每次合入一个组件变更单时使用。UUID.randomUUID()随机生成一个字符串作为所属基线的traceId,前端页面每次提交组件变更单时把拉取基线组件列表时返回的基线traceId带上来,这样就能判断出来本次变更是否有冲突。

    经过一段时间的观察,发现这个机制虽然可以避免数据一致性问题,但是冲突概率很高,原因是第一次进入基线详情页面时才会拉取一次基线组件列表和traceId,大家都习惯一直开着这个页面等有需要时直接在当前页面上操作,这个时候离第一次打开页面已经过去很久了,组件列表可能已经被其它开发人员修改过了。经过和业务方的沟通其实每个开发人员经常改的组件就四五个左右,基线组件列表有几百个组件,显然traceId方案力度太大,需要寻找一个更细化的比对方案。

    这个时候想起了mysql的机制,traceId方案可以想象成数据库的表锁,更为细化的有行锁,模仿这个思想为每条基线组件数据加一个修改编号mod_number,每次这条记录有变动时+1,前端页面修改组件配置时把组件mod_number一并带过来,后台服务根据传过来的mod_number和当前组件的mod_number做对比就可以得知这个组件信息是否被修改过、添加组件时只判断这个组件不能存在、删除组件时判断这个组件要存在,新的机制在保证稳定的前提下大大减低了提交时的冲突概率。

    基线对比

    之前对比两条基线组件变化情况使用手工对比JSON的方式,不太方便且容易遗漏,所以提供了此功能。两条基线对比结果分为新增组件、组件变更、删除组件三种类型,对比的过程就是两个列表取差集。首先把基线1的组件列表和基线2的组件列表分别转换为以ID为key的HashMap,然后分别把两个HashMap的KeySet拿出来创建新的HashSet。

    • 新增组件 = KeySet2 - KeySet1
    • 删除组件 = KeySet1- KeySet2
    • 组件的变更内容是两个Set取交集,然后在做进一步比对

    组件管理

    发布插件执行完成publishMavenPublicationToMavenRepository任务后,把此次发布组件相关信息上报到AirForce后端服务,上传的信息大致如下:

    • git仓库地址
    • 发布人邮箱
    • Maven groupId
    • Maven artifactId
    • 版本号
    • gradle工程文件夹名
    • 插件类型
    • git分支
    • pom url
    • 发布插件版本
    • pom文件sha1
    • Source jar的sha1
    • 包sha1
    • Git comitId
    • ...... 后端拿到组件发布信息后,保存git仓库、组件、组件版本信息,从gitlab同步本次发布commit信息, 最后发送飞书通知。

    发布管理

    目前客户端的发版阶段,依赖发版人与社区、交易、直播、增长、基础等业务线核对版本再统一打包,这一模式随着业务线的增加出现了发版时间长、沟通成本高、容易出错的问题。提测阶段,打包产物可用性不高,偶尔出现代码错误导致的运行时异常,打包阻塞的问题也时有发生。为了解决上述问题将发版模式改为班车发版。

    由各业务线决定是否跟车,以及跟车的可发布版本,如果此次发布不跟车平台自动选择业务线上一个可用版本;发版人只维护App版本号、发版日期等基本信息。开发和测试阶段,增加集成组件的卡口检查,避免代码问题影响持续集成的稳定性,后期还将完善测试、审核流程。

    整体流程

    业务线发布

    业务线Owner从候选版本中选择一个作为发布版本, 平台会做必要检查. 如果不发布, 默认使用之前发布版本.

    发版人打包

    发版人预先创建发布计划,明确App版本信息,基线和计划发版日期,平台通知业务线Owner。打包构建的时候平台自动收集业务组件和三方组件,业务组件的版本来自业务线Owner的发布历史,三方组件来自基线。

    打包流程

    在没有AirForce平台之前,打包都是在jenkins上打包的,业务线的Owner都要手动在jenkins上选择要打包的业务线的组件,而每个打包的产物和上传的产物也是不能动态配置的,打正式包和打测试包也没有区分开,每个打包的产物也要去oss后台上去看,可以说对于开发人员来说无疑增加了开发的时间,和对于文档需求的成本。

    因此AirForce平台在把打包这任务构建这块变得更为的简洁,开发人员甚至不用关心oss后台和jenkins,直接在AirForce平台上操作就可以了。

    开发时的构建流程

    在编译插件中,实现了通过参数配置的下发来判断是AirForce发布还是本地打包,从而实现无缝的可切换的过程,在此之外,在AirForce平台中,我们在开发时基于每个基线构建的时候也有动态的配置classpath的组件和动态的配置implementation的组件,从而实现完全的动态可配的特性。

    发布计划构建流程

    客户端发布计划,主要兼容了AirForce发布和本地打包,原本的Baseline的参数不变,在AirForce发布计划的情况下新增了 AirForce.Baseline,所以我们在build.gradle加载Classpath前统一做了拦截,保持了其他插件读取Baseline参数的统一。

    而在构建详情中,原本我们从读取本地配置的渠道包,上传固定的产物到oss后台,而现在,我们把所有的逻辑统一收敛到了AirForce的平台,包括了动态的获取上传的产物,动态的配置渠道包的配置,从而让AirForce串起了整个流程,包括了获取和配置app的组件,获取和配置app的渠道包,获取和配置app上传的产物等,大大减少了各个业务线的发版和配置难度。

    打包时卡口检测流程

    由于得物app采用的是壳工程+aar的形式,故修改或者重构底层的模块就显得要异常的小心,尤其是重构底层的模块。 举个例子,如果重构du_common 那就需要把其关联修改的类从aar的依赖变成源码依赖的模式,此举会导致编译过慢,而且如果此module被上层依赖的module过多,就很有可能会导致开发人员漏了修改某一个上层的aar,从而导致此重构的底层module发上去,有可能造成运行期的异常。 解决方案: 因此,为了解决此痛点,我们在打整包时执行了检测的task,通过在每个aar中生成每个info信息,然后调用compare.jar,此jar包能让下列aar中的异常情况都能被检测到,从而避免运行期异常,可以检测的异常情况如下:

    • 某一个模块的里的接口或者抽象类里的方法名字变更了或者是参数有变更,调用此task能检测出被影响的组件列表。
    • 某一个模块里的类重命名或者被删除了,调用此task能检测出被影响的组件。
    • 某一个模块里的方法名字或者方法参数发生变化了,调用此task能检测出被影响的组件。 实现图如下:

    可以看到这里主要是获取被检测的业务组件的信息: 自己的类信息(shelfClassInfo)

    • 包括了类的签名
    • 类的访问权限
    • 方法的访问权限
    • 其父类信息
    • 父类所在的module名字
    • 接口信息
    • 接口所在的module名字
    • 方法的描述符 继承类的信息(inheritClassInfo)
    • 类名
    • 类的访问权限 实现接口的信息(inheritClassInfo)
    • 类名
    • 类的访问权限 调用其他module的类的信息(libsJarInfo)
    • 方法名字
    • 调用者的方法
    • 方法描述符
    • 调用者的类 然后把这些信息写到一个json文件中,通过封装的compare的jar包做统一的调用,从而分析出哪些类调用其他组件出现方法名缺失或者方法参数发生改变,要重写的方法未重写等,最后把比对的结果上传上去,然后去做统一的通知 所以我们编译插件中,也添加了关于整包的runtime的crash的预检测的task,从而在包打出来之前提前发现此包会出现的runtime的异常,从而提前通知开发人员,从而减少运行时异常给开发人员带来的block,当然,此开关也是在AirForce平台上可配置的。

    总结

    AirForce管理平台,串起了从开发到最终投放过程中组件管理和打包的主要流程。逐步收敛组件管理和打包相关的各个分散的点,把之前需要手工操作的点程序化、自动化,规范开发流程,降低手工操作出错概率,以及管理成本,通过关键节点的信息入库,做到行为可溯源,为提高研发效率助力。

    相关文章

      网友评论

          本文标题:Android自动构建平台(Air Force)的功能与实现

          本文链接:https://www.haomeiwen.com/subject/drmvsrtx.html