Android组件化 aar/源码调试切换方案

2,938 阅读6分钟

一、前言

一个Android项目在不断地迭代之后,项目代码会变多,业务会膨胀,难免会去做一些组件化,插件化这些改造。就那组件化来说,我们需要剥离出各个业务模块,以及基础库,拆成一个个的组件,然后慢慢地因为代码变多,编译会时间变得越来越长,从而影响开发调试的效率。然后我们可能会将组件源码以aar的形式引入,加快编译速度,然而我们在开发调试的时候,使用aar的方式不是很方便,所以需要一个aar和源码之间切换的方案,下面就简单说一下。

二、开始吧

1、没有Maven仓库?

公司客户端可怜,没有自己的Maven仓库,只有一台打包机,之前使用这台机器搭建了Android项目的构建平台,看空间还有富裕,那就继续压榨它吧,再搭个maven仓库吧,简单说下搭建流程吧,我使用docker的方式安装的,docker的使用方式在juejin.cn/post/721920…juejin.cn/post/722999…都有提到。

1、拉取maven的镜像

docker pull sonatype/nexus3

镜像有很多,可以通过 docker search nexus 搜一下。

2、创建容器

docker run -d -p 8081:8081--name nexus  --restart=always sonatype/nexus3 

然后浏览器输入: http://localhost:8081 即可打开了,启动有点慢,所以显示打不开的时候多刷新一下。

打开后默认的登录账号:admin ,密码需要去查看下

find / -name 'admin.password'

根据提示的路径查看

 cat xxx

3、创建总库

4、创建存储库

默认不然不能覆盖只能升版本,配置下可覆盖

获取最终的仓库地址,下面需要

5、gradle中配置publish

group "com.example.aar.resource.plugin"
version "1.0.1"

publishing { //当前项目可以发布到本地文件夹中
    repositories {
        maven {
			//maven仓库的地址 上图中有
            url = 'http://172.16.10.110:8081/repository/maven_test/'
            allowInsecureProtocol = true
            credentials {
                username 'admin'
                password 'qwertyu123'
            }
        }
    }

    publications {
        // Release 版本发布任务
        release(MavenPublication) {
            groupId group
            artifactId artifactId
            version version

            artifact("$buildDir/outputs/aar/child-release.aar")
        }

        // Debug 版本发布任务
        debug(MavenPublication) {
            groupId group
            artifactId artifactId
            version version

            artifact("$buildDir/outputs/aar/child-debug.aar")
        }
    }

}

在对应moudle的build.gradle中apply上述groovy即可 (apply from: './publish.gradle')

然后再命令行执行publish命令即可

// debug
./gradlew publishDebugPublicationToMavenRepository 
// release
./gradlew publishReleasePublicationToMavenRepository 
//一起
./gradlew publish

2、实现方式

配置文件:

{
  "libs": [
    {
      "projectName": "child",
      "packageName": "com.example.aar.resource.plugin",
      "use_aar": true,
      "version": "1.0.1"
    }
  ]
}

方式一 : 傻瓜式判断

//方式一
// 使用根目录的module_switch_config.json 来动态配置依赖库
dependencies {
    def switchConfig = getModuleSwitchConfig()
    def libs = switchConfig.libs
    println(switchConfig)
    libs.each { config ->
        println(config)
        if (config.use_aar) {
            println("使用aar")
            implementation("$config.packageName:$config.projectName:$config.version")
        } else {
            implementation project(":$config.projectName")
            println("使用本地")
        }
    }

}

方式二 使用substitute

// 方式二
gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    @Override
    void beforeEvaluate(Project projectObj) {
    }

    @Override
    void afterEvaluate(Project projectObj, ProjectState state) {
        projectObj.configurations.all { config ->
            config.resolutionStrategy.dependencySubstitution {
                def switchConfig = getModuleSwitchConfig()
                def libs = switchConfig.libs
                println(switchConfig)
                libs.each { lib ->
                    println(lib)
                    if (lib.use_aar) {
                        substitute project(":$lib.projectName")  using module("$lib.packageName:$lib.projectName:$lib.version")
                        println("使用aar")
                    } else {
                        substitute module("$lib.packageName:$lib.projectName:$lib.version") using project(":$lib.projectName")
                        println("使用本地")
                    }
                }
            }
        }
    }
})

在对应app的build.gradle中apply上述groovy即可 (apply from: './../switch_aar.gradle')

关于resolutionStrategy 和 dependencySubstitution


resolutionStrategy API 中常用的函数和配置选项:

  • failOnVersionConflict(): 当发生版本冲突时,Gradle 将会停止构建并抛出异常。

  • preferProjectModules(): 如果存在多个版本的同一库,Gradle 将会选择当前项目中的版本。

  • force(String... moduleIdentifiers): 强制使用指定的版本,忽略其他版本。可以传入一个或多个模块标识符,例如 'com.google.guava:guava:25.0'

  • latestVersion(): 使用最新版本的库。

  • highestVersion(): 使用最高版本的库。

  • lowestVersion(): 使用最低版本的库。

  • strictly(String... moduleIdentifiers): 强制使用指定版本,如果找不到则抛出异常。可以传入一个或多个模块标识符,例如 'com.google.guava:guava:25.0'

  • componentSelection(Action<? super ComponentSelection> action): 可以对每个依赖项进行个性化配置,比如排除某个依赖项或强制使用某个版本等。需要传入一个Action 对象,其中的参数 ComponentSelection 代表当前依赖项的组件选择信息,您可以在 Action 中修改这些信息来实现个性化配置。例如:

dependencies {
    // 全局配置依赖解决策略
    resolutionStrategy {
        failOnVersionConflict()
        preferProjectModules()
        force 'com.google.guava:guava:25.0'
    }
    // 单个依赖项指定特定版本
    implementation('com.google.guava:guava:28.2-jre') {
        force = true
    }
    // 对每个依赖项进行个性化配置
    resolutionStrategy.componentSelection {
        if (it.candidate.version == '1.0') {
            it.reject('conflict with other dependency')
        }
        if (it.candidate.groupId == 'com.google.guava' && it.candidate.moduleId == 'guava') {
            it.useVersion('25.0')
        }
    }
}

总之,resolutionStrategy API 提供了多种选项来指定 Gradle 如何解决构建过程中的依赖冲突,您可以根据实际情况选择合适的选项来保证项目的稳定性和正确性。例如,您可以使用 failOnVersionConflict() 来确保所有依赖项的版本都能够正确匹配,或者使用 preferProjectModules() 来优先使用当前项目中的依赖项。如果您需要强制使用特定版本,可以使用 force(),如果您需要使用最新版本或者最高/最低版本,可以使用 latestVersion()highestVersion() 或者 lowestVersion()。如果您需要对某个依赖项进行特定的配置,例如排除某个依赖项或者强制使用某个版本,可以使用 componentSelection() 来实现个性化配置。

另外,需要注意的是 resolutionStrategy 中的选项会影响整个项目中的依赖解决策略,因此您需要谨慎使用这些选项,确保它们能够符合您的实际需求。如果您需要对某个依赖项进行特定的配置,建议使用 componentSelection() 来实现个性化配置,这样可以更加灵活地控制依赖项的解决策略。

关于 dependencySubstitution


dependencySubstitution 是 Gradle 构建工具中的一个 API,它可以用于替换依赖项。通过 dependencySubstitution,您可以将一个依赖项替换为另一个依赖项或者一个本地文件。

下面是 dependencySubstitution API 中常用的函数:

  • substitute(moduleNotation: String, target: Dependency):将指定依赖项替换为目标依赖项,其中 moduleNotation 是要替换的依赖项的模块标识符,例如 'com.google.guava:guava:28.2-jre'target 是目标依赖项,可以是另一个标准依赖项,也可以是一个本地文件依赖项。
  • substitute(moduleNotation: String, dependencyNotation: String):将指定依赖项替换为目标依赖项,其中 moduleNotation 是要替换的依赖项的模块标识符,例如 'com.google.guava:guava:28.2-jre'dependencyNotation 是目标依赖项的依赖标记符,例如 'com.google.guava:guava:25.0'
  • substitute(moduleNotation: String, file: File):将指定依赖项替换为本地文件,其中 moduleNotation 是要替换的依赖项的模块标识符,例如 'com.google.guava:guava:28.2-jre'file 是本地文件对象。
  • substitute(moduleNotation: Pattern, target: Dependency):将符合指定模式的依赖项替换为目标依赖项,其中 moduleNotation 是一个正则表达式模式,例如 ~/com.google.guava:.*/target 是目标依赖项,可以是另一个标准依赖项,也可以是一个本地文件依赖项。
  • substitute(moduleNotation: Pattern, dependencyNotation: String):将符合指定模式的依赖项替换为目标依赖项,其中 moduleNotation 是一个正则表达式模式,例如 ~/com.google.guava:.*/dependencyNotation 是目标依赖项的依赖标记符,例如 'com.google.guava:guava:25.0'
  • substitute(moduleNotation: Pattern, file: File):将符合指定模式的依赖项替换为本地文件,其中 moduleNotation 是一个正则表达式模式,例如 ~/com.google.guava:.*/file 是本地文件对象。

你可以在 dependencies 块中使用 dependencySubstitution 来全局配置依赖项替换规则,例如:

dependencies {
    // 全局配置依赖项替换规则
    dependencySubstitution {
        substitute(module('com.google.guava:guava'), 'com.google.guava:guava:25.0')
        substitute(module('com.example:lib'), files('/path/to/local/lib.jar'))
    }
}

总之,dependencySubstitution 可以帮助您实现依赖项的替换,从而满足特定的需求。例如,您可以将某个依赖项替换为本地文件,以实现对某个库的修改或者调试。

三、最后

arr和源码切换的方式有很多,也可以不用substitute,自己定义规则,但最终离不来gradle提供的api,第一种需要在所有的有依赖配置的地方导入上述gradle文件,第二种导入一次就够了,最后安利个好玩链接github.com/xtekky/gpt4… 你懂的,里面有部署好的链接,也可以按照文档自己部署在本地就是有点丑