Android开发经验谈Android开发

Android组件化的绝配Composite Build

2019-06-15  本文已影响11人  liwshuo

1. 什么是Composite Build

Composite Build可以简单的认为是一个Build包含其他多个Build。这个是官方的说法,如果从Android开发的角度来看,可以理解为,一个Project可以包含其他Project。

正常情况下,使用Android Studio开发的时候,可以通过New Project的方式创建一个新的Project。一个Project内部会包含多个Module,不同的Project之间是没有联系的。不过如果使用了Composite Build,那么会在不同的Project之间建立联系。

这个联系是通过依赖替换来生成的,可以指定某一个依赖替换为其他Project中的Module。这样原本是implementation 'com.example.xx:1.0.0'的依赖方式,会替换为implementation project('xx')的依赖方式。这样就可以进行调试、查看代码、Refactor等操作。

2. 为什么使用Composite Build

在平时的开发中,在项目刚开始的时候,一般只会有一个主Module,有新需求、新功能都在这个主Module 中添加就好了。

但是随着越来越多的功能被添加进来,这个Module会变得越来越臃肿。为了更好地对项目进行管理,这时候会创建多个Module来分担主Module的压力。这时候主Module对其他Module之间的依赖是通过implementation project('')的方式来引用的。这种方式后面都称为project方式。

但是随着越来越多的Module被加入,编译速度越来越慢,而且可能某些Module需要被别的项目使用。为了提高编译速度、提高Library的复用性,这时会搭建一个Maven仓库,将Module发布到仓库上,然后通过implementation 'com.example.xx:1.0.0'的方式来引用Module。这种方式后面都成为maven方式。这样就会避免project方式每次编译过程中每个Module都需要重新编译的缺点,直接从Maven仓库获取已经发布的aar/jar,加快编译速度。

使用Maven方式确实可以提高编译速度,也提高了复用性。但是如果某一个已经发布的Library代码需要修改,这时候就麻烦了。

  1. 将对目标Library的依赖方式从maven方式改为project方式
  2. 修改代码,并进行调试验证
  3. 修改版本号,发布新版本到仓库
  4. 修改对目标Library有依赖关系的Library的版本号,发布它们的新版本到仓库
  5. 对4中修改了版本号的Library重复4的步骤,直到所有对目标Library有直接或者间接依赖关系的Library都发布了新的版本到仓库
    或者使用snapshot的方式替换上述的1,2步骤。
    可以看到,这是一个非常复杂的步骤,而且如果一旦疏忽,就可能导致工程中存在两个版本号的目标Library。

那么如何解决上面的问题呢?答案就是使用Composite Build。

3. 如何实现Composite Build

在使用Composite Build之前,我们的工程目录是如下所示的,只有MyApp这个工程下面有settings.gradle文件,这个文件中声明了app、lib1、lib2这三个Module。app依赖了lib1和lib2。

MyApp/
├── build.gradle 
├── settings.gradle 
├── app
│     ├── build.gradle
│     └── src
├── lib1
│     ├── build.gradle
│     └── src
└── lib2
      ├── build.gradle
      └── src

如果直接在这个结构上使用Composite构建是不可以的,因为Composite构建要求所有参与Composite构建的目录下面必须有settings.gradle文件。如果app这个Module通过Composite的方式依赖lib1和lib2,那么就需要做一些处理:

  1. 在MyApp同级的目录下,创建两个新的文件夹,lib1Project、lib2Project。在新文件夹中均放入build.gradle和settings.gradle两个文件,相当于创建了两个Project工程,然后将lib1和lib2分别放入这两个文件夹中。如果想省事,可以直接使用Android Studio创建两个新的Project。
  2. 修改lib1Project中的settings.gradle,在其中声明lib1这个Moule,lib2Project同样做法。
  3. 修改lib1Project和lib2Project的build.gradle文件,配置repositories和dependencies。这里需要注意,需要保证这两个文件中的dependencies和MyApp中的dependencies保持一致,顺序也必须一致。否则编译会出错,可以参考 https://developer.android.com/studio/known-issues
    Composite Builds条目。
  4. 移除MyApp目录下settings.gradle中对lib1和lib2的声明,并在settings.gradle中添加如下代码
includeBuild('../lib1Project')
includeBuild('../lib2Project')
  1. 在MyApp工程中,sync gradle,sync成功后,在Android Studio的project视图下可以看到MyApp同级的目录下出现了lib1Project和lib2Project,这时候就说明composite构建成功了。虽然此时app对lib1和lib2的依赖方式在其build.gradle文件中仍然是maven方式的,但是实际上此时已经变成了project的方式。
  2. 如果在5的步骤中,没有看到出现lib1Project和lib2Project,说明此时构建失败了,其中的一个可能的原因在于,发布lib1和lib2到maven的时候,修改了其默认的GROUP和ARTIFACTID。这个时候就需要在includeBuild的时候,手动指定要替换的library如下:
includeBuild('../lib1Project'){
    dependencySubstitution {
        substitute module('com.example.xx:lib1') with project (':lib1')
    }
}
includeBuild('../lib2Project'){
    dependencySubstitution {
        substitute module('com.example.xx:lib1') with project (':lib2')
    }
}

此时的工程目录如下

rootDir/
├── MyApp/
│   ├── build.gradle 
│   ├── settings.gradle 
│   ├── app
│       ├── build.gradle
│       └── src 
├── lib1Project/
│   ├── build.gradle 
│   ├── settings.gradle 
│   ├── lib1
│       ├── build.gradle
│       └── src 
├── lib2Project/
    ├── build.gradle 
    ├── settings.gradle 
    ├── lib2
        ├── build.gradle
        └── src 

但是在不需要调试和修改代码的时候。并不想使用Composite构建怎么办呢?
如果通过在gradle文件中增减字段作为变量来判断是否使用Composite构建的方式固然可以,但是可能存在不小心将修改后的字段提交,导致CI上打包使用了Composite构建。

这时候可以考虑在lib1Project/lib2Project目录下创建一个名为.composite-enable的隐藏文件,通过判断这个文件是否存在来决定是否使用Composite构建方式。为了避免不小心将这个文件上传到服务器,可以将该文件添加到gitignore中,这样该文件就只会在本地存在,而不会影响到CI打包。

为了更加方便地创建和删除文件,可以在gradle文件中添加Task来辅助处理。

创建文件

task enable Lib1Project CompositeBuild(){
    group = 'Tools'
    description = 'Enable Lib1Project composite build'
    doLast {
        if (file("../Lib1Project").exists()) {
            println("create .composite-enable file")
            new File("../Lib1Project/.composite-enable").createNewFile()
        }
    }
}

删除文件

task disable Lib1Project CompositeBuild(){
    group = 'Tools'
    description = 'Disable Lib1Project composite build'
    doLast {
        if (file("../Lib1Project").exists()) {
            File file = file("../Lib1Project/.composite-enable")
            if (file.exists()) {
                println("delete .composite-enable file")
                file.delete()
            }
        }
    }
}

4. Composite Build 和组件化

现在组件化非常火,那么组件化配合Composite构建将能摩擦出更大的火花,每一个组件可以放到一个单独的Project中,然后通过Composite构建的方式调试修改代码。可以避免在一个Project中存在过多Module的情况。

rootDir/
├── MainApp/
│   ├── build.gradle 
│   ├── settings.gradle 
│   ├── app
│       ├── build.gradle
│       └── src 
├── ComponentAProject/
│   ├── build.gradle 
│   ├── settings.gradle 
│   ├── app
│   │   ├── build.gradle
│   │   └── src 
│   ├── ComponentALibrary
│   │   ├── build.gradle
│   │   └── src 
│   ├── ComponentAService
│       ├── build.gradle
│       └── src 
├── ComponentBProject/
│   ├── build.gradle 
│   ├── settings.gradle 
│   ├── app
│   │   ├── build.gradle
│   │   └── src 
│   ├── ComponentBLibrary
│   │   ├── build.gradle
│   │   └── src 
│   ├── ComponentBService
│       ├── build.gradle
│       └── src 
├── Common/
    ├── build.gradle 
    ├── settings.gradle 
    ├── CommonLibrary
        ├── build.gradle
        └── src 

假如目前组件化项目中有两个组件:ComponentA和ComponentB,一个基础库Common,一个MainApp。

  1. Component对外的接口不需要下沉到一个非常低的层级,让所有的组件都来依赖,而是按需依赖。一旦发生了修改,不会所有的组件都会受影响,而只会影响对其有依赖的组件。
  2. 每个组件在自己的Project中有独立的APP Module,也方便其独立安装运行。
  3. 使用Composite Build方式,避免了在一个Project工程中,存在过多的Module,导致编译和运行非常缓慢;也避免了即使将这些Module发布到Maven上,导致后面调试维护非常麻烦的缺点。

这样每一个组件都在自己的Project中,方便进行代码管理和权限管理。

参考文章

Composite builds
Gradle之多项目与混合构建 – Android开发中文站
如何简单快速搭建 Android 大仓 - Yrom’s
Saying Goodbye to SNAPSHOTs with Gradle’s Composite Builds

上一篇下一篇

猜你喜欢

热点阅读