笔者编写本博客时的历史运行环境:

  • 历史运行环境 2:

    • Android Studio Meerkat Feature Drop | 2024.3.2

    • Android SDK 35(安卓 15)

    • Gradle 8.11.1

    • JDK 21.0.6

  • 历史运行环境 1:

    • Android Studio Flamingo | 2022.2.1

    • Android SDK 29(安卓 10)

    • Gradle 8.0.1

    • JDK 17

  Android 的 Gradle 项目与一般的 Gradle 项目是不同的,因此对将 Gradle 模块打包发布到本地 Maven 仓库来说,对普通 Gradle 项目适用的方法,对 Android 项目是不适用的。

  因为普通 Gradle 项目打包生成的是 JAR 包,而 Android 项目打包生成的是 AAR 包。不过说到底,AAR 包和 JAR 包一样,也就是一种压缩包。只不过,AAR 包是在 JAR 包的基础之上的二次压缩。具体来说,AAR 包是将源码编译生成的 JAR 包再和安卓的清单文件放在一起又制成了一个新的压缩包。

  不过,也就是这点区别导致了它们关于发布到 Maven 仓库的流程有些不同。

具体流程

  1. 首先,需要先安装 Maven,否则 Gradle 将无法感知本地 Maven 仓库的位置。关于这方面的内容,可见笔者的另一篇博客:

    Maven 的下载安装教程:
    https://blog.csdn.net/wangpaiblog/article/details/112689500

  2. 这里以安卓打包发布到本地 Maven 仓库的流程为例,并假设读者使用的是一种 Gradle 多模块项目。这里演示的是将其中的一个模块打包发布到本地的 Maven 仓库。

  3. 如果自己的 Gradle 项目使用了多模块,且多模块项目的其中一个模块依赖了本项目的其它模块,则本 Gradle 多模块项目的所有被依赖的子模块都要编写如下代码。下面代码的添加位置为各个被依赖的子模块的 build.gradle

    // 定义一些在其它模块可以通过代码 project(模块名).ext.变量名 来访问的变量
    ext {
        moduleGroupId = 'Maven 的 groupId 坐标'
        moduleArtifactId = 'Maven 的 artifactId 坐标'
        moduleVersion = 'Maven 的 version 坐标'
    }
    

    其中,读者需要将上述代码中的 moduleGroupIdmoduleArtifactIdmoduleVersion 改成实际的值。

  4. 在需要打包发布到本地 Maven 仓库的那个模块的 build.gradle 中,添加如下代码。


    【踩坑提醒】

    • 安卓 Gradle 项目与普通用于运行在 Java 服务器的 Gradle 项目是不同。因此打包代码也是不同的。
    • 只有 idcom.android.library 的模块才支持打包到 Maven 仓库。如果 idcom.android.application 应用启动模块,是不支持打包到 Maven 仓库的。但是解决方案也很简单,直接分离出一个打包的模块即可。
    • Gradle 项目是支持多模块的,而多模块项目的其中一个模块如果依赖本项目的其它模块,使用的代码是 implementation project(':模块名')。此代码中是不含完整的 Maven 坐标的,因此无法像普通依赖那样直接读取。解决方法是先读取模块名,然后通过读取事先设置的 ext 变量才能读取到 Maven 坐标。

    plugins {
        id 'com.android.library'
        id 'maven-publish'
    }
    
    // 打包源代码
    task sourceJar(type: Jar) {
        from android.sourceSets.main.java.srcDirs
        archiveClassifier = "sources"
    }
    
    // 定义一些在其它模块可以通过代码 project(模块名).ext.变量名 来访问的变量
    ext {
        moduleGroupId = 'Maven 的 groupId 坐标'
        moduleArtifactId = 'Maven 的 artifactId 坐标'
        moduleVersion = 'Maven 的 version 坐标'
    }
    
    publishing {
        publications {
            maven(MavenPublication) {
                // 获取本模块定义的 ext 变量,然后用其初始化 Maven 模板的坐标
                groupId = project.ext.moduleGroupId
                artifactId = project.ext.moduleArtifactId
                version = project.ext.moduleVersion
    
                // 上传 AAR 包
                afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
                // 向 Maven 仓库中上传源码
                artifact sourceJar
    
                // 将本 Gradle 模块的依赖转换到 pom.xml 文件中
                pom.withXml {
                    def dependenciesNode = asNode().appendNode('dependencies')
                    configurations.implementation.allDependencies.each { dep ->
                        // 依赖本项目其它模块,使用的是 implementation project(':模块名'),
                        // 此代码中是不含完整的 Maven 坐标的,因此无法像普通依赖那样直接读取。
                        // 必须先读取模块名,然后通过读取事先设置的 ext 变量才能读取到 Maven 坐标
                        if (dep instanceof ProjectDependency) {
                            def depNode = dependenciesNode.appendNode('dependency')
                            // 读取本项目其它模块的模块名
                            def moduleName = dep.name
                            // 读取目标模块的 ext 变量组
                            def moduleExt = project(":${moduleName}").ext
    
                            def moduleGroupId = moduleExt.moduleGroupId
                            def moduleArtifactId = moduleExt.moduleArtifactId
                            def moduleVersion = moduleExt.moduleVersion
                            depNode.appendNode('groupId', moduleGroupId)
                            depNode.appendNode('artifactId', moduleArtifactId)
                            depNode.appendNode('version', moduleVersion)
                            println "源自本项目的依赖: ${moduleGroupId}:${moduleArtifactId}:${moduleVersion}"
                        } else if (dep.group != null && dep.name != null) {
                            // dep 会包含通过 implementation、api 引入的依赖,
                            // 但不会包含通过 annotationProcessor、testImplementation、androidTestImplementation 引入的依赖
                            def moduleGroupId = dep.group
                            def moduleArtifactId = dep.name
                            def moduleVersion = dep.version
                            // 有一些依赖是不需要输出到 pom.xml 的。如 lombok
                            if (moduleArtifactId == 'lombok') {
                                println "不需要输出的依赖: ${moduleGroupId}:${moduleArtifactId}:${moduleVersion}"
                            } else {
                                def depNode = dependenciesNode.appendNode('dependency')
                                depNode.appendNode('groupId', moduleGroupId)
                                depNode.appendNode('artifactId', moduleArtifactId)
                                depNode.appendNode('version', moduleVersion)
                                println "非源自本项目的依赖: ${moduleGroupId}:${moduleArtifactId}:${moduleVersion}"
                            }
                        }
                    }
                }
            }
        }
    }
    

    其中,读者需要将上述代码中的 moduleGroupIdmoduleArtifactIdmoduleVersion 改成实际的值。


    【提示】

      有人可能会使用如下额外的配置:

    publishing {
        // ...省略其它内容...
        repositories {
            maven {
                url = "http://my.org/repo"
            }
        }
    }
    

      此配置是将 Gradle 模块发送到 Maven 网络仓库上时才需要设置的。对本文来说,这是不必要的。


  5. 在 Android Studio 上的 Gradle 面板中点击 publishToMavenLocal 即可完成发布。

    在这里插入图片描述


    【踩坑提醒】

      有的读者可能会遇到 Gradle 面板没有 publishToMavenLocal 这一选项的问题。关于这方面的内容,可见笔者的另一篇博客:

      解决 Android Studio 的 Gradle 面板上只有关于测试的 task 的问题:
    https://blog.csdn.net/wangpaiblog/article/details/132124402


  6. 发布到本地 Maven 仓库成功之后,Gradle 会在 Maven 仓库在相应位置创建三个文件:

    • artifactId-version.aar

    • artifactId-version.pom

    • artifactId-version-sources.jar

  7. 然后,在 Gradle 项目中的根模块中的 settings.gradle 添加如下代码来引入本地 Maven 仓库。这样 Gradle 在解析依赖时就会额外去本地 Maven 仓库中寻找。

    dependencyResolutionManagement {
        repositories {
            mavenLocal()
        }
    }
    
  8. 现在,在 Gradle 项目中就可以像其它依赖一样使用 implementation 来引用刚发布到本地 Maven 仓库的依赖了。

附录

导入本地其它 Gradle 项目的一些错误解决方案

  Gradle 与 Maven 本是不同的技术。将 Gradle 项目转换成 Maven 包并安装到本地 Maven 仓库是为了方便其它 Gradle 项目可以使用它。实际上,Gradle 为此提供了原生解决方案。但经过笔者的反复踩坑,最终确认这些解决方案是一些错误的解决方案。

错误的解决方案 1

  在主模块的 settings.gradle 中使用 includeBuild 来直接导入其它 Gradle 项目。

  此方案只适用于简单的 Gradle 项目,因为 includeBuild 非常不智能。对于多模块的 Gradle 项目,如果子模块之间依赖,编译会直接报错。因为它无法帮助导入的子模块分辨导入之后的主模块。

错误的解决方案 2

  在主模块的 settings.gradle 中使用 include(...)project(...).projectDir = new File(...) 来导入其它 Gradle 模块。

  同样,此方案只适用于简单的 Gradle 项目,因为上述代码也非常不智能。对于多模块的 Gradle 项目,如果子模块之间依赖,编译也会直接报错。因为它无法帮助导入的子模块分辨其依赖的其它子模块。

更多推荐