组件化下如何优雅进行本地调试,即aar依赖与module依赖动态切换

5,352 阅读7分钟

在组件化开发的时候,会面临的一个问题就是组件的调试。

即组件和主工程不在同一个工程目录下,主工程对组件的依赖,是通过依赖组件发布的aar包的方式引入。当组件开发了新的功能,就会发布一个新的 aar 包,并在主工程引入。

但如果这种方式带来的问题就是,如果组件有bug或者需要调试的时候,就会带来不便。具体来说就是,如果改一个问题,就发布一个组件的 aar 包,然后在主工程验证问题是否被修复成功。 这样就会频繁的发布 aar 包,而且调试的效率也低,那有没有办法在开发阶段,直接引用 组件module,而不使用组件的 aar 包呢?答案当然是有的,下面就来介绍一下具体的实现方案。

准备工作:

假设主工程路径:/Users/mei/WorkSpace/AndroidProjects/TestModuleDep

组件工程路径:/Users/mei/WorkSpace/AndroidProjects/module-user/user

组件的maven路径:com.mei.module:user:1.0.0

在主工程的 build.gradle 文件中,引入 user 组件:

dependencies {
    implementation 'com.mei.module:user:1.0.0'
}

一、组件module(源码)依赖方式


从上面的介绍可以知道,主工程 依赖 user 组件,是通过依赖 user 组件的 aar 包的方式进行的。如果想本地调试的话,可以改成依赖组件module 的方式。具体操作如下:

1、include 组件 module

在主工程的 settings.gradle 文件中,把 组件module 加入编译,即 include 组件module。组件跟主工程不在同一个工程目录下,所以,组件module 的 include 方式也有所不同:

include ":user"
project(":user").projectDir = file("/Users/mei/WorkSpace/AndroidProjects/module-user/user")

如上代码所示,在 主工程的 settings.gradle 文件中,增加上述代码,就可以把 组件module 以 include 的方式,加入到主工程中。

2、依赖组件module

在主工程的 app module 中,依赖组件module,即在 app 的 build.gradle 文件中,依赖组件module,同时,把组件的 aar 包依赖给注释掉:

dependencies {
    // implementation 'com.mei.module:user:1.0.0'
    implementation project(':user') // 以 组件module 的方式依赖组件
}

经过上面两步操作,就可以直接依赖组件的源码,而不是组件的 aar 包了,调试起来就非常方便了,不用每次改动都发布一个aar 包。

通过上面的方式,是可以源码依赖的问题,但每次需要调试的时候都这样去改的话,就显得非常的麻烦,还需要把 aar 依赖的方式给注释掉,万一不小心提交了代码,就有可能导致远程无法打包的问题。

那有没有什么方式是在 开发的时候使用 module 依赖,而不改变app 的gradle 文件呢?下面就介绍一种 aar依赖 与 源码依赖快速切换的方式。

二、aar依赖 与 源码依赖 快速切换


在主工程的 build.gradle 文件中,增加如下代码:

allprojects {// 所有组件都添加
    configurations.all {
        resolutionStrategy {
            dependencySubstitution {
            		// module,groupId:artifactId
            		// project,组件module 名称
                substitute module( "com.mei.module:user") with project(':user')
            }
        }
    }
}

上述代码的意思是,以 module 代替 aar 包,则在编译工程的时候,只会把 module 的代码加入编译,而 aar 包的代码不会加入编译。

当然,也可以只在 app 的build.gradle 文件中,加入上述代码,这个时候就可以不用调用 allprojects 方法了,如:

configurations.all {
  resolutionStrategy {
      dependencySubstitution {
          // module,groupId:artifactId
          // project,组件module 名称
          substitute module( "com.mei.module:user") with project(':user') // 注释1 
      }
  }
}

如果不想使用 module 依赖,就可以 把 注释1 的代码,给注释掉,就可以接着使用 aar依赖了。

三、组件依赖封装


经过 二、aar依赖 与 源码依赖 快速切换 的操作之后,我们可以不用 注释掉组件的 aar 依赖,就可以实现 aar依赖 与 module依赖的 快速切换,但还是需要去修改主工程的 settings.gradle 文件和 build.gradle 文件,还是会存在误提交的问题。

如果我把 所有的操作,都放到一个 gradle 文件中,只在主工程的 settings.gradle 文件中,引入这个 gradle 文件,就可以实现上面所有的操作,并且在找不到这个文件的时候,也不会导致编译流程失败,不就可以解决误提交的问题吗?

下面就来看看具体是如何封装的。

1、在 module 工程中,增加 module 依赖文件

在 module 工程中,增加 user_dependency.gradle 文件(完整路径:/Users/mei/WorkSpace/AndroidProjects/module-user/user_dependency.gradle),在这个文件中,实现 组件module 的 引入和 与aar依赖的动态切换功能。注意,这个文件可以不用加入到 git 管理。

1-1、组件module 依赖

include ":user"
project(":user").projectDir = file("/Users/mei/WorkSpace/AndroidProjects/module-user/user")

1-2、aar 依赖 与 组件module 依赖的动态切换

user_dependency.gradle 文件中,增加如下监听:

gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {

    @Override
    void beforeEvaluate(Project projectObj) {
        try {
          	// 增加扩展属性
            if (!projectObj.rootProject.extensions.hasProperty("kotlin_version")) {
                projectObj.rootProject.extensions.add("kotlin_version", "1.4.32")
            }
        } catch (Exception e) {
            e.printStackTrace()
        }
      	// 应用组件工程的全局配置文件
        projectObj.rootProject.apply from: "/Users/mei/WorkSpace/AndroidProjects/module-user/mavenConfig.gradle"

        println "beforeEvaluate project.configurations=${projectObj.configurations}"
    }

    @Override
    void afterEvaluate(Project projectObj, ProjectState state) {
        println "project name is $projectObj"
        println "afterEvaluate project.configurations=${projectObj.configurations}"
        if (projectObj.name != "app") {
            return
        }
      	// 切换 aar依赖 与 组件module 依赖
        projectObj.configurations.all { Configuration ->
            resolutionStrategy {
                dependencySubstitution {
                    substitute module( "com.mei.module:user") with project(':user')
                }
            }
        }
    }
})
  1. 给 gradle 对象,增加 ProjectEvaluationListener 监听事件

  2. 在 ProjectEvaluationListener 的 beforeEvaluate 方法中,增加一些组件需要依赖的额外配置,如果没有,可以不用添加。

  3. 在 ProjectEvaluationListener 的 afterEvaluate 方法中,给指定的工程对象,增加配置信息,如:给 app 工程,增加动态切换 aar依赖 和 module 依赖的 配置。

    注意:

    • 要把 afterEvaluate 的 Project 参数重新命名,否则在 dependencySubstitution 闭包中,调用 project() 方法会报错。
    • 只能在 ProjectEvaluationListener 的 afterEvaluate 方法 给工程增加配置信息,因为在 afterEvaluate 方法中,工程的configurations 配置对象不为空,而在 beforeEvaluate 方法中,工程的 configurations 对象是一个空对象,从而也无法 增加配置信息。

1-3、完整的 user_dependency.gradle 代码如下:

// 1. include 组件 module ,注意,使用绝对路径
include ":user"
project(":user").projectDir = file("/Users/mei/WorkSpace/AndroidProjects/module-user/user")

// 2. aar依赖 与 module依赖 动态切换
gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {

    @Override
    void beforeEvaluate(Project projectObj) {
        try {
          	// 增加扩展属性
            if (!projectObj.rootProject.extensions.hasProperty("kotlin_version")) {
                projectObj.rootProject.extensions.add("kotlin_version", "1.4.32")
            }
        } catch (Exception e) {
            e.printStackTrace()
        }
      	// 应用组件工程的全局配置文件
        projectObj.rootProject.apply from: "/Users/mei/WorkSpace/AndroidProjects/module-user/mavenConfig.gradle"

        println "beforeEvaluate project.configurations=${projectObj.configurations}"
    }

    @Override
    void afterEvaluate(Project projectObj, ProjectState state) {
        println "project name is $projectObj"
        println "afterEvaluate project.configurations=${projectObj.configurations}"
        if (projectObj.name != "app") {
            return
        }
      	// 切换 aar依赖 与 组件module 依赖
        projectObj.configurations.all { Configuration ->
            resolutionStrategy {
                dependencySubstitution {
                    substitute module( "com.mei.module:user") with project(':user')
                }
            }
        }
    }
})

2、在主工程的 settings.gradle 文件中,应用依赖文件

在主工程的 settings.gradle 文件中,应用 user_dependency.gradle 文件:

include ":app"

try {
    apply from:"/Users/mei/WorkSpace/AndroidProjects/module-user/user_dependency.gradle"
} catch (Exception e) {
    e.printStackTrace()
}

通过绝对路径的方式,应用 user_dependency.gradle 文件。这里增加 try catch 的目的是,即使找不到 user_dependency.gradle 文件的时候,也不会影响整体的编译流程。即 哪怕引用不到 组件module 还可以使用 aar 依赖。

这样,只需要修改主工程的 settings.gradle 文件,就可以实现 组件 aar依赖 与 组件module 的动态切换,而且 settings.gradle 的修改也可以提交,不会影响线上的的打包流程。

当debug的 时候,如果想用 aar依赖的方式,可以把 应用 user_dependency.gradle 文件的代码注释掉就可以了。

当然,也可以在 user_dependency.gradle 文件中,增加一个 开关,表示是否使用 module 依赖,从而控制 module 与 aar包 的动态切换操作。

如:

.... 

def useModule = true

// 2. aar依赖 与 module依赖 动态切换
gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {

    @Override
    void beforeEvaluate(Project projectObj) {
     	.... 
    }

    @Override
    void afterEvaluate(Project projectObj, ProjectState state) {
        if (projectObj.name != "app") {
            return
        }
      	// 切换 aar依赖 与 组件module 依赖
        projectObj.configurations.all { Configuration ->
            resolutionStrategy {
                dependencySubstitution {
                		if(useModule){ // 只有开关打开的时候,才使用 module 依赖
                				substitute module( "com.mei.module:user") with project(':user')
                		}
                }
            }
        }
    }
})

四、通用方案


1、用一个json 文件,保存 module 依赖的配置信息

在主工程新建一个 module_dependency.json 文件,内容格式如下:

[
  {
    "useModule": true,
    "module_name": "user",
    "module_dir": "/Users/mei/WorkSpace/AndroidProjects/module-user/user",
    "module_group": "com.mei.module:user"
  },
  {
    "useModule": true,
    "module_name": "xxx",
    "module_dir": "xxx",
    "module_group": "xxx"
  },
]

字段说明:

  • useModule:是否使用 module 依赖,true:module依赖,false:aar依赖
  • module_name:module 名称
  • module_dir:module 的绝对路径
  • module_group:groupdId:artifactId 的组合

2、解析 json 配置文件,并 根据配置信息,决定是否依赖module

在主工程目录下,新建一个 module_dependency.gradle 文件,用于解析 json 配置文件,并 根据配置信息,决定是否依赖module,代码如下:

import groovy.json.JsonSlurper

// 应用该文件的时候,执行该方法
includeAndSwitchModuleDep()

def includeAndSwitchModuleDep() {
    try {
				// module依赖的配置信息文件路径,json格式
        def moduleDepConfigDir = "/Users/akulaku/WorkSpace/xxx/module_dependency.json"
        // 解析json配置
        def json = file(moduleDepConfigDir).getText()
        def jsonSlurper = new JsonSlurper()
        def objList = jsonSlurper.parseText(json)
        println "objList=$objList"
        // 遍历配置信息列表,找出 useModule==true 的模块,动态include
        objList.forEach {
            if (it.useModule) {
                include ":${it.module_name}"
                project(":${it.module_name}").projectDir = file(it.module_dir)
            }
        }
        // aar依赖 与 module依赖 动态切换
        switchToModuleDependency(objList)
    } catch (Exception e) {
        e.printStackTrace()
    }
}

/**
 * 切换到 module 依赖
 * @param moduleDepList
 * @return
 */
def switchToModuleDependency(List<Object> moduleDepList) {
    gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {

        @Override
        void beforeEvaluate(Project projectObj) {
            ....
        }

        @Override
        void afterEvaluate(Project projectObj, ProjectState state) {
            println "project name is $projectObj"
            println "afterEvaluate project.configurations=${projectObj.configurations}"
            if (projectObj.name != "app") {
                return
            }
            switchModuleDep(projectObj, moduleDepList)
        }
    })
}

/**
 * aar依赖 与 module依赖 动态切换
 * @param projectObj project 对象
 * @param moduleDepList module依赖配置信息列表
 * @return
 */
def switchModuleDep(Project projectObj, List<Object> moduleDepList) {
    println "project.rootProject.configurations=${projectObj.rootProject.configurations}"
    println "project.configurations=${projectObj.configurations}"
    projectObj.configurations.all { Configuration ->
        resolutionStrategy {
            dependencySubstitution {
                moduleDepList.forEach {
                    if (it.useModule) {
                        substitute module(it.module_group) with project(":${it.module_name}")
                    }
                }
            }
        }
    }
}

主要功能为:

  1. 解析 module 依赖的 配置信息,并保存到列表中
  2. 把 module 通过 include 的方式,加入到主工程中
  3. 动态切换 aar依赖 和 module 依赖
  4. 加上异常捕获,避免文件找不到或者其他异常导致编译流程失败

3、在主工程的 settings.gradle 文件中,应用 module_dependency.gradle 文件

try {
    apply from: "module_dependency.gradle"
} catch (Exception e) {
    e.printStackTrace()
}

确保编译流程正常。

这样就可以很方便的把 组件module 加入到主工程中进行联调了,只需要修改 json 文件中的 useModule 字段,就可以实现 aar依赖 与 module 依赖的 动态切换。

如果有新的模块,也只需要在json 文件中,增加一个json 配置项就可以了。