美文网首页Android开发经验谈Android开发
Android组件化的绝配Composite Build

Android组件化的绝配Composite Build

作者: liwshuo | 来源:发表于2019-06-15 11:36 被阅读11次

    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。

    • MainApp通过Maven的方式依赖ComponentA、ComponentB和Common,打包生成主App
    • ComponentA内部有app Module作为组件的壳,ComponentALibrary包含A的主要业务,ComponentAService包含对外的接口,其他组件如果需要与ComponentA进行通信的话,可以依赖ComponentAService。
    • ComponentB同A
    • ComponentA通过Maven方式依赖了ComponentBService和ComponentB进行通信,依赖了Common
    • ComponentB通过Maven方式依赖了ComponentAService和ComponentA进行通信,依赖了Common
    • 在需要对代码进行调试的时候,可以通过配置Composite Build的方式,将Maven的依赖替换为Project依赖的方式。
    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

    相关文章

      网友评论

        本文标题:Android组件化的绝配Composite Build

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