【Gradle KTS】一次发布 Android SDK 到 maven 仓库的经验总结

前言

近期接到需求,将此前一个项目的部分功能封装成 SDK 对外提供,我在 SDK 开发方面没有经验,借着这个需求,了解了 SDK 开发的一些技巧,也爬了很多的坑,梳理出这篇文章,把一些经验分享给大家。

如有不足或者谬误,还请各位同行大哥们多多指教。

jar VS aar

jar 包

jar(Java ARchive)java 归档文件,对于我们 Android 开发来说,通俗的理解就是这是一个单纯提供业务能力的 SDK,只有 class 文件与清单文件,不涉及任何的页面,不包含资源文件。

aar 包

aar(Android ARchive) 同理,是 Android 归档文件,也就是除了 class 文件和清单文件之外,还会包含一些 Android 页面,资源文件,SDK 本身就具备 UI 交互能力。

我这次接到的需求就是包含 UI 交互的封装,因此本文后续都将围绕 aar 展开内容。

构建并产出 aar

这一部分我不做详细阐述,因为类似的文章很容易检索到,我也会在文末贴一些相关的文章链接

创建 library module

我看有些文章,是把 application 项目改成 libaray ,我个人的情况还是提取封装部分功能,所以这里还是演示新建 libaray

1. File --> New --> New Module

2. 配置自己的项目参数

3. 顺带提一下 applicationlibrarygradle 配置区别

  1. 插件从 com.android.application 变成了 com.android.library

  2. library 模块不需要,也不应该配置 applicationId , targetSdkversionCode , versionName

产出 aar

产出的方式有很多,这里提供常见的 2 种

1. gradlew 命令产出(我喜欢用的)

shell 复制代码
./gradlew :SdkDemo:assembleRelease

# 如果你配置了 variant,不确定具体的产出命令,也可以先使用 tasks 命令查看所有命令,从中找到你需要的
./gradlew :SdkDemo:tasks

这里命令中的 SdkDemo 对应着我的 module 名称,具体到你的项目中,换成你的即可,下文同理。

2. AS 自带的 gradle 插件产出

最终 aar 包体都会产出在 SdkDemo/build/outputs/aar/ 路径下

发布 aar

通常情况下,此时直接产出 aar 包输出使用的话,大概率会报找不到类的异常,原因很简单:打包时没有包含使用的依赖,或者宿主项目中没有所需要的依赖。基于上述的原因也会有两种不同的解决方案:

将依赖打进 aar 包后再发布

我们平时使用的依赖,很多也是以 aar 包的形式从远端仓库,下载到我们本地,然后再参与遇到编译流程中去的。

那将依赖打进 aar 其实也就是将多个 aar 合并成一个,这种方式,Google 目前默认是不是支持的,需要使用第三方的 gradle 插件来实现

fat-aar-android

这个插件做的事情简单来说就是把多个 aar 包解包,然后合并诸如 Manifestres 资源R 文件assetslibsjni 等各类文件,其中合并 R文件 是最重要的一步。

1. 添加 fat-aar 插件

projectbuild.gradle.kts 添加 fat-aar 插件

kotlin 复制代码
buildscript {
    dependencies {
        ···
        classpath("com.github.kezong:fat-aar:1.3.8")
        // 本插件作者已停更,如果你和我一样使用 agp 8.0 及之后版本,可使用其他贡献者适配的版本
        // classpath("com.github.aasitnikov:fat-aar-android:ce932b38ef")
    }
}

SDKDemo module 的 build.gradle.kts(本节后文同) 添加 fat-aar 插件

kotlin 复制代码
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp")
    id("maven-publish")
}

2. Embed dependencies

embed 你所需要合并打入的依赖, 用法类似 implementation

kotlin 复制代码
dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to "*.jar")))

    // java dependency
    embed(project(path = ":lib-java", configuration = "default"))

    // aar dependency
    embed(project(path = ":lib-aar", configuration = "default"))

    // aar dependency
    embed(project(path = ":lib-aar2", configuration = "default"))

    // local full aar dependency, just build in flavor1
    flavor1Embed(project(path = ":lib-aar-local", configuration = "default"))

    // local full aar dependency, just build in debug
    debugEmbed(mapOf("name" to "lib-aar-local2", "ext" to "aar"))

    // remote jar dependency
    embed("com.google.guava:guava:20.0")

    // remote aar dependency
    embed("com.facebook.fresco:fresco:1.12.0")

    // don't want to embed in
    implementation("androidx.appcompat:appcompat:1.2.0")
}

3. 执行 assemble 命令

shell 复制代码
# assemble all 
./gradlew :SDKDemo:assemble

最终合并产物会覆盖原有aar,同时路径会打印在log信息中.

4. 多级传递

本地依赖

如果你想将本地所有相关的依赖项全部包含在最终产物中,你需要在你主library中对所有依赖都加上embed关键字

比如,mainLib依赖lib1,lib1依赖lib2,如果你想将所有依赖都打入最终产物,你必须在mainLib的build.gradle中对lib1以及lib2都加上embed关键字

远程依赖

如果你想将所有远程依赖在pom中声明的依赖项同时打入在最终产物里的话,你需要在build.gradle中将transitive值改为true,例如:

kotlin 复制代码
fataar {
    /**
     * If transitive is true, local jar module and remote library's dependencies will be embed.
     * If transitive is false, just embed first level dependency
     * Local aar project does not support transitive, always embed first level
     * Default value is false
     * @since 1.3.0
     */
    transitive = true
}

如果你将transitive的值改成了true,并且想忽略pom文件中的某一个依赖项,你可以添加exclude关键字,例如:

kotlin 复制代码
embed("com.facebook.fresco:fresco:1.11.0") {
    // exclude any group or module
    exclude(group = "com.facebook.soloader", module = "soloader")
    // exclude all dependencies
    isTransitive = false
}

接下来你需要做的可能是和你的乙方联调确认依赖是否有冲突问题,如果有并且对方不能修改,那我建议看看下面的 maven 发布方式

发布到 maven 平台托管依赖

相比于前一种将多个 aar 重组打成一个包,发布到 maven 平台具有:

  • 包体体积更小;
  • 版本管理更加灵活可靠可追溯;
  • 发布管理方便

等优点,缺点嘛,我觉得也是有的,配置上要更麻烦一点,尤其是当你跟我一样,项目全 kotlin 化的,ktsgradle 可是费了我不少时间,那下面就听我给你细说。

1. 添加 maven-publish 插件

SDKDemo module 的 build.gradle.kts(本节后文同) 添加 maven-publish 插件

kotlin 复制代码
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp")
    id("maven-publish")
}

2. 先打印一下 modulevariant 配置,并配置发布的 variant

build.gradle.kts 的 最外层 android 节点同级添加 afterEvaluate 并打印 variant 配置信息

kotlin 复制代码
afterEvaluate {

    publishing {

        publications {
            // 注意,这里的 release 是为发布任务定义的
            create<MavenPublication>("release") {
                components.forEach {
                    System.out.println(it.name)
                }
            }
        }
}

在确认了你的 variant 名称之后,继续配置 MavenPublication

kotlin 复制代码
afterEvaluate {

    publishing {

        publications {
            create<MavenPublication>("release") {
//              components.forEach {
//                  System.out.println(it.name)
//              }
                // 注意,这里的 Release 本发布任务对应的 variant,从上一步打印的 variant 中选择填写
                from(components.getByName("Release"))
                groupId = "com.randalldev.sdkdemo" // 唯一标识(通常为模块包名,也可以任意)
                artifactId = "sdkdemo" // 项目名称(通常为类库模块名称,也可以任意)
                version = "v1.0.0" // 版本号
            }
        }
}

3. 此时你就可以先尝试执行发布本地 maven

再次执行查看 gradle 任务命令,查找 maven 发布命令

shell 复制代码
./gradlew :SdkDemo:tasks

应该会看到 publishReleasePublicationToMavenLocal 的命令 执行一下,确认没有报错,然后继续后续的配置

4. 添加你的 maven 仓库及账户配置

kotlin 复制代码
afterEvaluate {

    publishing {
        ···

        repositories {
            maven {
                // 如果你的仓库地址是私服且没有配置 https 证书,那就需要添加这行
                isAllowInsecureProtocol = true
                // 这里的名字自定义即可,用来标识你的仓库,会在 gradle 命令体现
                name = "maven"
                url = uri("你的仓库地址")
                credentials {
                    username = "你的账户"
                    password = "你的密码"
                }
            }
        }
    }
}

5. 执行发布命令

再次执行查看 gradle 任务命令,查找 maven 发布命令

shell 复制代码
./gradlew :SdkDemo:tasks

应该会看到 publishReleasePublicationToMavenRepository 的命令 这一步完成,在前置步骤均顺利的情况下,就可以完成 modulemaven 发布了

6. 建议调整一下依赖的编译 scope

groovy 复制代码
publishing.publications.configureEach {
    pom.withXml {
        asNode().dependencies.'*'.findAll() {
            it.scope.text() == 'runtime' && project.configurations.implementation.allDependencies.find { dep ->
                println("replace dependency name from ${dep.name} to ${it.artifactId.text()}")
                dep.name == it.artifactId.text()
            }
        }.each {
            println("replace dependency scope from ${it.scope*.value} to compile")
            it.scope*.value = 'compile'
        }
    }
}

这部分代码,我目前还没能实现 kts 化,所以暂时需要单独创建一个 SDKDemo 目录下的 scope.gradleapply 进 的 maven 配置相关代码

kotlin 复制代码
afterEvaluate {

    publishing {

        publications {
        ···
        }

        apply(from = "scope.gradle")

        repositories {
            ···
        }
    }
}

看下执行发布命令后产生的影响

  • before
  • after

所谓 scopepom 管理文件中用于定义依赖关系的标签,它规定了依赖的作用域,即依赖在何时可用、以及在构建的不同阶段中是否被包含。而 pom 是用来描述项目构建的基本信息,可以简单理解为 pom 文件中管理着所有依赖及其作用域。

以下是 maven 中常见的依赖作用域:

作用域 编译 测试 运行 包含到 JAR/WAR 备注
compile 默认作用域,适用于所有阶段。
provided 编译和测试时需要,运行时由容器提供。
runtime 编译时不需要,但在运行时需要。
test 仅在测试阶段使用,不包含到最终构建。
system 与 provided 类似,但需要显式提供路径。
import - - - - 仅在 <dependencyManagement> 中使用。

我们 Android 项目以来添加,常使用 implementation,其对应着 runtime scope,通过将 scope 修改为 compile 可以避免一些依赖冲突问题,比如 duplicated class *******

7. 添加依赖即可使用了

将仓库地址和可访问的账户提供给使用者,其在项目的 settings.gradle.kts 中配置 maven 仓库

kotlin 复制代码
dependencyResolutionManagement {
    ···
    repositories {
        ···
        maven {
            // 非 https 地址需要添加
            allowInsecureProtocol(true)
            credentials {
                username = "乙方的账户"
                password = "乙方的密码"
            }
            url = uri("maven 仓库地址")
        }
    }
}

并在需要使用的 module 中添加依赖

kotlin 复制代码
implementation("com.randalldev.sdkdemo:sdkdemo:v1.0.0")

参考文章

Maven Publish Plugin

使用maven-publish插件发布Android工件(kts)

fat-aar实践及原理分享

相关推荐
曾经的三心草2 小时前
Mysql之约束与事件
android·数据库·mysql·事件·约束
guoruijun_2012_46 小时前
fastadmin多个表crud连表操作步骤
android·java·开发语言
Winston Wood6 小时前
一文了解Android中的AudioFlinger
android·音频
B.-8 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克8 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart
大耳猫13 小时前
主动测量View的宽高
android·ui
冰芒猓14 小时前
SpringMVC数据校验、数据格式化处理、国际化设置
开发语言·maven
帅次16 小时前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
枯骨成佛17 小时前
Android中Crash Debug技巧
android
旧故新长19 小时前
七牛云上传图片成功,但是无法访问显示{error : document not found}
java·maven