Gradle 运用在组件化中

前面几节我们学习了 Gradle 的任务,命令已经学会了自定义插件。那么下面我们就来学习以下如何将前面所学的 Gradle 知识运用在组件化架构中。我们现在的项目基本都是组件化的架构。但是我们真的了解组件化吗?我们通过这节学习,希望能够帮助到大家在组件化开发中有更高的效率。

1. 组件化与集成化

我们的项目最开始创建时是集成化模式的,但是由于我们一个团队,有很多人在同时开发一个项目,但是大家都负责各自的模块。这样在集成化的模式下,大家要编译跟大家不相关的别的模块相关的代码。所以就出现的组件化模式。在组件化模式下,各个模块可以独立运行。

集成化模式: 就是打包整个项目,编译出一个全业务功能的 apk 文件。各个子模块不能够独立运行,只能依赖于宿主 App。
组件化模式: 就是每个子模块都能够独立运行,不需要依赖宿主 APP 壳。而且每个模块都能够编译出 apk 文件。

2. Android 项目中组件化运用

我们下面来具体看下 Android 项目中我们怎么来实施组件化。我们知道我们发布市场肯定是要打一个全功能的 apk 包,也就是发布市场时是需要集成化的打包模式,而我们开发过程中是需要组件化模式的,所以我们需要一个开关来控制组件化和集成化打包模式。我们各个模块都会有编译工具版本,SDK 的版本,support 库的版本号等。我们可以将这些抽离出来,单独建立一个 Gradle 文件来配置这些全局变量。

2.1 config.gradle

我们创建一个单独的config.gradle文件,定义全局变量,如下所示:

ext {

    // 定义一个项目全局变量isRelease,用于动态切换:组件化模式 / 集成化模式
    // false: 组件化模式(子模块可以独立运行),true :集成化模式(打包整个项目apk,子模块不可独立运行)
    isRelease = false

    // 建立Map存储,对象名、key可以自定义
    androidId = [
            compileSdkVersion: 28,
            buildToolsVersion: "29.0.0",
            minSdkVersion    : 19,
            targetSdkVersion : 28,
            versionCode      : 1,
            versionName      : "1.0"
    ]

    appId = ["app"     : "com.bthvi.modular",
             "order"   : "com.bthvi.modular.order",
             "personal": "com.bthvi.modular.personal"]

  
    supportLibrary = "28.0.0"
    dependencies = [
            // ${supportLibrary}表示引用一个变量
            "appcompat"      : "com.android.support:appcompat-v7:${supportLibrary}",
            "recyclerview": "com.android.support:recyclerview-v7:${supportLibrary}",
            "constraint"     : "com.android.support.constraint:constraint-layout:1.1.3",
            "okhttp3"        : "com.squareup.okhttp3:okhttp:3.10.0",
            "retrofit"       : "com.squareup.retrofit2:retrofit:2.5.0",
            "fastjson"       : "com.alibaba:fastjson:1.2.58",
    ]
}

2.2 在 build.gradle 中引用 config.gradle

我们要引用我们上面定义的 config.gradle 文件,就需要在项目的根目录下的 build.gradle 中加入以下代码

apply from: "config.gradle"

2.3 在 module 中引用公共变量

前面我们在 config 定义了我们各个模块可能都会用到的依赖库,编译版本,sdk 版本版本号等一些公共变量。下面我们就需要将这些变量在 module 的 build.gradle 中引入。这里我创建了一个项目有 common,order,person 以及主模块 app 四个 module,下面我们以 order 为例。

def rootAndroidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies

android {
    compileSdkVersion rootAndroidId.compileSdkVersion
    buildToolsVersion rootAndroidId.buildToolsVersion
    defaultConfig {
        if (!isRelease) { // 如果是集成化模式,不能有applicationId
            applicationId appId.order // 组件化模式能独立运行才能有applicationId
        }
        minSdkVersion rootAndroidId.minSdkVersion
        targetSdkVersion rootAndroidId.targetSdkVersion
        versionCode rootAndroidId.versionCode
        versionName rootAndroidId.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        //当前项目的build.config文件里添加了一个boolean类型的变量 
        buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 循环引入第三方库
    support.each { k, v -> implementation v }
    implementation project(':common') // 公共基础库
}

Tips:这里我们在前面加入了buildConfigField,这个的作用是在当前模块的 build.config 中加入一个 isRelease 的布尔型变量。因为 src 代码中有可能需要用到跨模块交互,如果是组件化模块显然不能跨模块交互的。

2.4 在 module 的 build.gradle 中使用 isRelease 开关

我们引入定义的变量后,我们就需要在 module 中引入组件化开关,这里我们用 isRelease 表示,如果 isRelease 为 true,则表示当前为集成化模式,否则当前为组件化模式,各模块可相互独立。

Tips: 我们知道默认创建项目只有 app 模块才能运行,那么我们现在组件化中需要各个模块都能独立运行,那么我们就需要根据 isRelease 开关来控制了。能够独立运行取决于为 build.gradle 第一行引入的是com.android.library还是com.android.application,只有引入后者module才能独立运行。
根据上面的知识,我们应该在 module 中加入:

if (isRelease) { // 如果是生产发布版本时,各个模块都不能独立运行
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

2.5 配置资源路径

由于组件化和集成化模式中,module 的 AndroidManifest.xml 文件是不同的,组件化时,module 可以独立运行,AndroidManifest.xml 中需要配置 appliation、启动 activity 等。而集成化运行时只有主模块可以配置。所以这里我们就需要这么配置。

// 配置资源路径
    sourceSets {
        main {
            if (!isRelease) {
                // 如果是组件化模式,需要单独运行时
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                // 集成化模式,整个项目打包apk
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

2.6 配置组件化测试

我们在开发中可能都会遇到,自己的模块运行需要别的模块的数据,当没有集成别的模块的数据时,我们可以写一些自己的测试数据,或是资源文件等等。就是只有在组件化中能够用到,但是不需要出现在集成化打包后的生产包中的,我们可以单独创建一个文件夹,集合化时使用exclude不要让这个文件夹合并到项目中。具体如下所示:

    // 配置资源路径
    sourceSets {
        main {
            if (!isRelease) {
                // 如果是组件化模式,需要单独运行时
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                // 集成化模式,整个项目打包apk
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    // release 时 debug 目录下文件不需要合并到主工程
                    exclude '**/debug/**'
                }
            }
        }
    }

Tips: exclude 的妙用非常多,如果我们一些测试代码,测试数据,或是组件化单独的资源文件我们都可以放在 debug 文件下。编译的时候 exclude 会将这个模块所有的 debug 文件夹下的文件不会合并到整个项目中去。

2.7 完整的 module 的配置

前面我们的配置都是将 order 模块的 build.config,单独拆各个模块来讲的。可能大家有点乱。下面我们看下完整的配置应该是怎样的。

if (isRelease) { // 如果是发布版本时,各个模块都不能独立运行
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

def rootAndroidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies

android {
    compileSdkVersion rootAndroidId.compileSdkVersion
    buildToolsVersion rootAndroidId.buildToolsVersion
    defaultConfig {
        if (!isRelease) { // 如果是集成化模式,不能有 applicationId
            applicationId appId.order // 组件化模式能独立运行才能有 applicationId
        }
        minSdkVersion rootAndroidId.minSdkVersion
        targetSdkVersion rootAndroidId.targetSdkVersion
        versionCode rootAndroidId.versionCode
        versionName rootAndroidId.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    // 配置资源路径
    sourceSets {
        main {
            if (!isRelease) {
                // 如果是组件化模式,需要单独运行时
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                // 集成化模式,整个项目打包apk
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    // release 时 debug 目录下文件不需要合并到主工程
                    exclude '**/debug/**'
                }
            }
        }
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 循环引入第三方库
    support.each { k, v -> implementation v }
    implementation project(':common') // 公共基础库
}

3. 小结

本节,我们带大家学习了 Gradle 在 Android 项目组件化中的运用。主要知识点有以下几点:

  • 配置公共的依赖 jar 包,编译版本,构建版本,版本号等。
  • 用 buildConfigField 将组件化开关添加到 build.config 中,这样 Java 代码也就可以使用组件化开关。
  • 配置资源文件,可以将我们的测试资源、代码放在一个单独的目录下,使用 exclude,将这个目录下的文件不合并到项目。