深入浅出:Android Gradle组件化管理配置脚本

1,347 阅读4分钟

本文需要对gradle、groovy的一些语法有一定的了解。本文只展示了配置管理的一种思路,不仅仅限于这么使用,希望能给大家启发

本文基于gradle-7.0.2-bin和 agp gradle:7.0.3进行开发

模块层级配置

配置声明

先来看配置的数据格式,在Components中,以集合存储key-value,既可以看成是集合,也可以看作json的Item object。其下还ext了两个脚本方法includeComponentsimportDependencies,分别应用于settiing.gradlebuild.gradle(:module)。对于数据格式,可根据需要,结合脚本主体一并修改,达到满足自己的使用需求

// component_config.gradle
ext {
    // 这个请替换成自己远程库的group name,如com.xx.xx
    COMPONENT_GROUP = 'xxx'
    Components = [
            [
                    // index(在Components中的位置,从0开始),以用于进行匹配
                    // 当然也可以不用这个进行匹配,选择index完全是为了从集合中取item便利
                    // 如有需要进行遍历匹配,可自行更改匹配item部分的脚本逻辑
                    index      : 0,
                    // 模块名称,需要和对应Module中进行同步声明,脚本使用时需要传入,用于找到自身Item
                    module     : 'corekit',
                    // 这个对于需要进行远程依赖时进行的path拼接,包含snapshot、version配置项
                    group      : COMPONENT_GROUP,
                    // 这个是SNAPSHOT拼接开关
                    snapshot   : true,
                    // 远程依赖配置版本号
                    version    : '1.0.0.34',
                    // 是否需要进行本地依赖,即会被include到setting中
                    localEnable: false,
                    // 如果本地依赖的模块,与编译的app不在同一个project,那就需要声明路径,如下方不声明则默认在project中
                    localPath  : 'Self_Demo\\corekit'
            ],
            [
                    index          : 1,
                    module         : 'common',
                    localEnable    : true,
                    // apiDependencies与dependencies区分,api与implementation两种依赖传递方式
                    apiDependencies: [0],
            ],
            [
                    index       : 2,
                    module      : 'login',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 3,
                    module      : 'home',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 4,
                    module      : 'security',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 5,
                    module      : 'topic',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 6,
                    module      : 'member',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 7,
                    module      : 'category',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 8,
                    module      : 'address',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 9,
                    module      : 'search',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 10,
                    module      : 'detail',
                    localEnable : true,
                    dependencies: [1],
            ],
            [
                    index       : 11,
                    module      : 'app',
                    dependencies: [2, 3, 4, 5, 6, 7, 8, 9, 10, 12],
                    // 主模块的需要设置includes标签,以进行include,同时也是主入口的标志
                    includes    : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12]
            ],
            [
                    index       : 12,
                    module      : 'barcode',
                    localEnable : true,
                    dependencies: [1]
            ],
    ]
    parentPath = rootProject.projectDir.parentFile.toPath()
    // 将脚本方法 对外暴露
    includeComponents = this.&includeComponents
    importDependencies = this.&importDependencies
}

脚本主体

findComponent用于根据moduleName进行匹配对应的Components中的Item项,以读取配置。

importDependencies用于在各自的build.gradle(:module)关联层级依赖

includeComponents用于在settiing.gradle进行module include声明,如果不需要include的话,localEnable字段为false即可,但会被以remote依赖进行import,需要在importDependencies进行区分注意

// component_config.gradle
// Closure闭包可以理解为Kotlin中的高阶函数,用于在遍历Item的子级依赖时进行相应操作
def findComponent(def moduleName, def closure) {
    def component = Components.find {
        // 根据project匹配处理Item
        return it.module == moduleName
    }
    if (component && (component.dependencies || component.apiDependencies)) {
        println("║ [${component.module}]---has dependencies:${if (component.dependencies) "normal{${component.dependencies}}" else ""} ${if (component.apiDependencies) "api{${component.apiDependencies}}" else ""}")
        // 这块其实可以合并,但比较多,就懒得改配置了,大家可以根据需要自行改造配置的数据结构
        component.dependencies.each { item ->
            // 根据index,获取子级依赖,当然也可以参照 Components.find 进行匹配获取
            def dependency = Components.get(item)
            // 0、1是依赖模式,0为implementation,1为api
            // 由于该方法闭包在两个主要脚本中均使用,所以一旦调整参数,均需修改
            closure(dependency, 0)
        }
        component.apiDependencies.each { item ->
            def dependency = Components.get(item)
            closure(dependency, 1)
        }
    }
    // 对于有需要自己做独立操作的,return出去
    return component
}
// DependencyHandler就是build.gradle(:module)中的dependencies的函数体对象,对于gradle中的配置项应均理解为函数调用
def importDependencies(def moduleName, DependencyHandler dependencyHandler) {
    println "╔════════════════Build Script 【${moduleName}】 Start════════════════╗"
    findComponent(moduleName) {
        dependency, mode ->
            // 取到对于module后进行依赖导入
            def path
            def des
            // 这里是local和remote的path拼接
            if (dependency.localEnable) {
                path = project(":${dependency.module}")
                des = "is localed,is imported ${dependency.localPath ?: "in project"}"
            } else {
                def remote = "${dependency.version}${dependency.snapshot ? '-SNAPSHOT' : ''}"
                path = ("${dependency.group}:${dependency.module}:$remote")
                des = "is remoted,version:$remote"
            }
            println "║  └————component:[${dependency.module}] $des"
            if (mode == 1) {
                // 区分依赖模式
                dependencyHandler.api path
            } else {
                dependencyHandler.implementation path
            }
    }
    println "╚════════════════Build Script 【${moduleName}】   End════════════════╝"
}
// 通过includes识别主模块,进行导入
def includeComponents() {
    def rootModule = Components.find {
        it.includes
    }
    if (rootModule) {
        println "╔════════════════Setting Script 【${rootModule.module}】 Start════════════════"
        // 对主module单独include
        include ":${rootModule.module}"
        rootModule.includes.each { index ->
            def dependency = Components.get(index)
            if (dependency.localEnable) {
                // localPath区分本地非项目中依赖,和项目中依赖
                if (dependency.localPath) {
                    def local = "${dependency.localPath}"
                    println("║  └————enable local dependency:[${dependency.module}],path:$local")
                    include ":${dependency.module}"
                    project(":${dependency.module}").projectDir = new File(parentPath.toFile(), local)
                } else {
                    println("║  └————enable local dependency:[${dependency.module}],path:in project")
                    include ":${dependency.module}"
                }
            }
        }
        println "╚════════════════Setting Script 【${rootModule.module}】   End════════════════"
    } else {
        println "can not find any root Module with 'includes' tag"
    }
}

脚本引入

需要处理的其实就三处,项目gradle、模块gradle和setting文件

// build.gradle(:project)
// 此处进行全局导入
// 这个是另一个三方依赖管理,具体看下面
apply from: rootProject.file('project_config.gradle')
apply from: rootProject.file('component_config.gradle')

// setting.gradle
// 这里需要单独导入
apply from: file('component_config.gradle')
// 传入主module名称
includeComponents()

// build.gradle(:module)
def MODULE_NAME = "category"
dependencies {
    importDependencies(MODULE_NAME, dependencies)
}

三方依赖管理

这个就非常简单,其实就是一些常量的声明,在使用时通过rootProject.ext进行引用即可,管理的目的是为了全局统一,避免一些不必要的依赖冲突,以及依赖升级时带来的频繁改动

// project_config.gradle
ext {
    //SDK版本
    compileSdkVersion = 30//用于编译的SDK版本
    buildToolsVersion = '30.0.3'// 用于Gradle编译项目的工具版本

    minSdkVersion = 21// 最低支持Android版本
    targetSdkVersion = 30// 目标版本

    versionCode = 1
    versionName = '1.0'
    //JDK版本
    sourceCompatibilityVersion = JavaVersion.VERSION_1_8
    targetCompatibilityVersion = JavaVersion.VERSION_1_8
    //依赖版本
    supportLibrariesVersion = '1.0.0'

    dep = [
            junit                  : 'junit:junit:4.13.1',
            // for application
            supportV7              : "androidx.appcompat:appcompat:1.2.0",
            supportDesign          : "com.google.android.material:material:1.2.1",
            supportCardview        : "androidx.cardview:cardview:$supportLibrariesVersion",
            supportConstraintLayout: 'androidx.constraintlayout:constraintlayout:1.1.3',
    ]
}

// build.gradle(:module)
compileSdkVersion rootProject.ext.compileSdkVersion

dependencies {
    // 如果有需要,一样可以将一组依赖作为一个集合,利用脚本进行遍历,将dependencies传入,进行操作
    // 如模块层级配置时的思路一致
    api dep.supportDesign
    api dep.supportV7
    api dep.supportCardview
    api dep.supportConstraintLayout
    testImplementation dep.junit
}

依赖发布

依赖打包上传maven,需要配置maven仓库,一般用Nexus管理。常量记录在gradle.properties中,在moudle中加入gradle task,对于需要使用远程依赖的project,需要进行maven引入声明

// build.gradle(:module)
task androidSourcesJar(type: Jar) {
    // 如果有Kotlin那么就需要打入dir : getSrcDirs
    if (project.hasProperty("kotlin")) {
        from android.sourceSets.main.java.getSrcDirs()
    } else if (project.hasProperty("android")) {
        from android.sourceSets.main.java.sourceFiles
    } else {
        from sourceSets.main.allSource
    }
    archiveClassifier.set('sources')
}

def versionCode="1.0.0.1"
def snapshotEnable=false

afterEvaluate {
    publishing {
        //发布的 jar 包配置
        publications {
            //release这个名称可以替换,会生成对应的publishXXXPublicationToMaven的两个Task
            release(MavenPublication) {
                //aar 文件
                from components.release
                // 上传source,这样使用方可以看到方法注释,使用上面的 androidSourcesJar Task
                artifact androidSourcesJar
                groupId = GROUP
                artifactId = MODULE
                version = "$versionCode${snapshotEnable?'-SNAPSHOT':''}"
            }
        }
        //仓库地址配置
        repositories {
            maven {
                // 凭证
                credentials {
                    username MAVEN_USER// 仓库发布用户名
                    password MAVEN_PWD // 仓库发布用户密码
                }
                // 地址 REPOSITORY_SNAPSHOT,REPOSITORY
                url MAVEN_URL
                //允许http
                allowInsecureProtocol true
            }
        }
    }
}

// gradle.properties
MAVEN_URL=http://xxxx……
MAVEN_USER=xxx
MAVEN_PWD=xxx

// build.gralde(:project)
buildscript {
    maven {
        // 凭证
        credentials {
            username MAVEN_USER// 仓库发布用户名
            password MAVEN_PWD // 仓库发布用户密码
        }
        // 地址
        url MAVEN_URL
        //允许http
        allowInsecureProtocol true
    }
}