半年小记(一) : 架构推进

转载请注明出处: https://blog.csdn.net/anyfive/article/details/87645007

18年中,由于各种原因,换了一份工作,加入了现在这家公司,负责终端的业务开发。这半年时间有一些印象比较深刻的点,趁着空档记录下来。

解耦单个模块

我们的项目采用的是多module的组织形式,SDK层与业务层在同个项目里,其中业务层又分为多个app模块。

在入职前,同事抽离了一个Component模块,希望复用多个产品中通用的代码,但随着业务的推移,这个模块很快就陷入了耦合地狱之中了,只是由于业务的进度压力,一直未正视问题。

后来在一次打包时,另一个产品的代码不断报错,导致打包不断失败。也就是这次事件,让我感受到了解耦的刻不容缓。于是第二天,花了一天的时间将代码分离,通过设置sourceSets,实现各部分的编译期隔离。之后,又陆陆续续加入了同级依赖(同module不同部分的依赖)等功能,实现起来其实很简单:

  1. module的build.gradle文件的末尾加入以下代码:

    // 引入各config
    applyComponents(new ArrayList<String>(), rootProject.ext.Components)
    
    /**
     * 递归依赖所有需要的模块
     * @param appliedComponents
     * @param childComponents
     * @return
     */
    ArrayList<String> applyComponents(ArrayList<String> appliedComponents, ArrayList<String> childComponents) {
        for (String it : childComponents) {
            if (appliedComponents.indexOf(it) >= 0) continue
            apply from: "${it}/pin.gradle"
            appliedComponents.add(it)
            if (ext.has("componentDep") && ext.componentDep != null) {
                appliedComponents = applyComponents(appliedComponents, ext.componentDep)
            }
            ext.componentDep = null
        }
        return appliedComponents
    }
    

    如代码所示,从rootProject.ext.Components中获得要依赖的模块,apply其模块下的pin.gradle文件。

  2. 在module的build.gradle的android中,加入sourceSets设置,清空默认的源码路径:

    android {
        sourceSets {
            main.java.srcDirs = []
            main.res.srcDirs = []
            main.assets.srcDirs = []
            main.renderscript.srcDirs = []
            main.jniLibs.srcDirs = []
            main.manifest.srcFile "AndroidManifest.xml"
        }
    }
    
  3. 在各子模块目录下添加pin.gradle,该文件用于声明源码路径和依赖及其他内容:

    // 源码目录
    def dir = "${projectDir}/ComponentA"
    android {
        sourceSets {
            main.java.srcDirs += "${dir}/java"
            main.res.srcDirs += "${dir}/res"
            main.assets.srcDirs += "${dir}/assets"
            main.renderscript.srcDirs += "${dir}/rs"
        }
    }
    
    // 依赖
    dependencies {
        implementation "com.android.support:appcompat-v7:28.0.0"
    }
    
    // 同级依赖
    ext.componentDep = ["ComponentB"]
    

    通过sourceSets指定该模块的源码路径,通过dependencies添加该模块需要的依赖。

  4. 在app或者根项目的build.gradle中声明需要依赖的模块:

    rootProject.ext.Components = ["ComponentA"]
    

这样,就达到了代码的编译期隔离效果,如图:

PinsSample

从图中可以看到: 由于在app中声明了ComponentA,因此Component模块中,ComponentA被编译了;ComponentA中,又声明了ComponentB的同级依赖,因此ComponentB也被编译了;而ComponentC未被声明依赖,因此,ComponentC目录下的代码都不会参与编译。

四层+pins架构

在业务稳定下来之后,为保证之后可以更加快速地响应需求、解决问题,便又开始动起了架构调整的心思。于是,便延续着Components模块的风格,将整个项目的代码分成了四层:

  1. app层,即实际的业务项目层,目前有三个app层,通过settings.gradle和gradle.properties,实现项目的快速切换;
  2. Components层,一些至少两个项目通用的部分,包含了较强的独特性;
  3. Commons层,与公司其他业务或平台相关的部分,如日志系统等;
  4. Base层,通用的、不与公司任何业务相关的部分,如相机、网络等;

其中,Components、Commons、Base三层均使用pins的方式进行拆分,每个模块都进行了封装,并拥有自己的接入文档,保证最快速度的接入。

另外,这三层允许向下依赖和同级依赖,不允许向上依赖。为保证编译顺序,实现向下依赖,另编写了Pins.gradle,以Commons的Pins.gradle文件为例:

// 初始化Commons数组
if (!rootProject.ext.has("Commons")) {
    rootProject.ext.Commons = []
}

// 获得所有module的名称
import java.util.function.BiConsumer
ArrayList<String> projectNames = new ArrayList<String>()
rootProject.childProjects.forEach(new BiConsumer<String, Project>() {
    @Override
    void accept(String s, Project project) {
        projectNames.add(s)
    }
})

// 如果Commons module参与了编译,则将其编译顺序挪到除Base之外最后的位置
allprojects {
    if (it == rootProject) return
    if (projectNames.contains("如果Commons") && it.name != "Base" && it.name != "Commons") {
        project(":Commons").evaluationDependsOn(":${it.name}")
        return
    }
}

然后,在根项目的build.gradle中,依次apply这三层的Pins.gradle:

apply from: "Components/Pins.gradle"
apply from: "Commons/Pins.gradle"
apply from: "Base/Pins.gradle"

这样一来,就可以保证编译顺序,顺利实现向下依赖,最终的架构如下图:
架构图

如此,便可通过配置依赖,尽量复用代码,快速完成新项目的开发;同时,出现问题时也更加容易定位和解决。

gradle脚本替代git-tag

我们项目的代码托管,使用submodule的形式,每个app是一个submodule,每层也是单独的submodule。每次发布版本时,都需要在release分支上打tag。架构调整后,由于底下三层逐渐趋于稳定,导致同一commit中,逐渐出现大量tag。

于是决定,不再打tag,而是通过在打包时,记录每个submodule的commitId用以代替tag,代码与之前写过 android 打造自己的gradle构建脚本(以集成Tinker为例)的类似,在此不再累赘。

演进思考

在项目的调整过程中,每一次模块的拆分,其实都有不少的工作量。每次抽离出一个模块,都需要对其进行封装,保证其可以快速地被接入、方便地扩展,如Camera模块,基本算是重写了一遍。但值得高兴的是,最后的结果达到了我们的预期,对后续的工作,带来了不小的帮助。

若是采用jenkins+maven的方式,跨层的依赖也会变得更加方便和规范;但我们希望,每一个参与项目的人,都可以对每一层的代码尽可能熟悉,且都可以方便地参与到模块的完善中来,因此,在架构调整到可以满足业务的需求和适应可预见的变化的这一步时,也就可以先放一放,把精力放到其他更有价值的事情上。

虽然本篇文章的代码并没有什么难度,也没有什么黑科技,但我觉得,这次经历是一次有意义的事,因此在此记录。

没有最好的架构,只有最适合业务需求的架构。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值