前言
近期接到需求,将此前一个项目的部分功能封装成 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. 顺带提一下 application
和 library
的 gradle
配置区别
-
插件从
com.android.application
变成了com.android.library
-
library
模块不需要,也不应该配置applicationId
,targetSdk
,versionCode
,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
插件来实现
这个插件做的事情简单来说就是把多个 aar
包解包,然后合并诸如 Manifest
,res 资源
,R 文件
,assets
,libs
,jni
等各类文件,其中合并 R文件
是最重要的一步。
1. 添加 fat-aar
插件
在 project
的 build.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
化的,kts
的 gradle
可是费了我不少时间,那下面就听我给你细说。
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. 先打印一下 module
的 variant
配置,并配置发布的 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
的命令 这一步完成,在前置步骤均顺利的情况下,就可以完成 module
的 maven
发布了
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.gradle
并 apply
进 的 maven
配置相关代码
kotlin
afterEvaluate {
publishing {
publications {
···
}
apply(from = "scope.gradle")
repositories {
···
}
}
}
看下执行发布命令后产生的影响
- before
- after
所谓 scope
是 pom
管理文件中用于定义依赖关系的标签,它规定了依赖的作用域,即依赖在何时可用、以及在构建的不同阶段中是否被包含。而 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")