3. SDK构建工具链
聊到SDK发布,构建工具链是绕不开的硬骨头。说白了,你写的代码再漂亮,如果构建环节出了岔子,SDK根本到不了用户手里。我个人习惯把构建工具链比作「流水线」——从源码到最终产物的每一个环节,都得精密配合。
今天咱们就掰开揉碎,聊聊Gradle和Maven的配置、多模块怎么搭、产物怎么混淆压缩,还有构建变体怎么管。嗯,这里面的坑,我踩过不少。
3.1 Gradle vs Maven:选谁更顺手?
先解决一个老生常谈的问题:Gradle还是Maven?
我的答案是:Android SDK用Gradle,Java SDK看团队习惯。为什么?Android官方已经全面拥抱Gradle,你几乎没得选。但纯Java SDK,Maven的稳定性和约定优于配置的理念,依然有大批拥趸。
| 对比维度 | Gradle | Maven |
|---|---|---|
| 构建语言 | Groovy / Kotlin DSL | XML |
| 性能 | 增量编译、构建缓存,快 | 相对慢,但稳定 |
| 依赖管理 | 动态版本、强制依赖 | 严格版本控制 |
| 学习曲线 | 稍陡,DSL灵活 | 平缓,XML直观 |
| 多模块支持 | 原生优秀 | 通过parent pom实现 |
我在项目中遇到过团队从Maven迁移到Gradle的情况。原因很简单:多模块构建时,Gradle的并行任务执行效率高出一大截。但如果你团队全是Java老兵,Maven的约定式结构反而更省心。
3.2 多模块构建:SDK的「乐高」哲学
一个成熟的SDK,绝不是一个单模块项目。你想想看,如果把所有功能塞进一个module,每次改一行代码都要全量编译,那效率得多低?
多模块构建的核心思想是:按功能边界拆分,按依赖层级组织。
我习惯这样划分模块:
- core:基础能力,如网络请求、数据存储、日志
- api:对外暴露的接口层,用户直接调用的类
- internal:内部实现,不对外暴露
- annotation:注解定义,用于编译期处理
- processor:注解处理器,编译期生成代码
来看一个典型的Gradle多模块配置:
// settings.gradle.kts
rootProject.name = "my-sdk"
include(":core", ":api", ":internal", ":annotation", ":processor")
// 统一版本管理
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
每个模块的build.gradle.kts里,只声明自己需要的依赖:
// api/build.gradle.kts
dependencies {
implementation(project(":core")) // 依赖core模块
implementation(project(":annotation"))
// 不依赖internal,因为用户不需要知道内部实现
}
3.3 产物混淆与压缩:瘦身又安全
SDK发布前,有两件事必须做:混淆和压缩。
混淆的目的有两个:一是减小体积(缩短类名、方法名),二是增加逆向难度。压缩则是去掉无用的资源、代码。
对于Android SDK,我推荐使用R8(ProGuard的升级版)。配置示例如下:
// build.gradle.kts
android {
buildTypes {
release {
isMinifyEnabled = true // 开启混淆
isShrinkResources = true // 压缩资源
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
proguard-rules.pro里,需要保留对外暴露的API:
-keep class com.example.sdk.api.** { *; } // 保留所有API类
-keep class com.example.sdk.model.** { *; } // 保留数据模型
-keepclassmembers class * {
@com.example.sdk.annotation.Keep *; // 保留带Keep注解的成员
}
对于纯Java SDK,Maven配合ProGuard也能实现:
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>proguard</goal></goals>
</execution>
</executions>
</plugin>
3.4 构建变体管理:一套代码,多份产物
SDK经常需要发布多个版本:debug版、release版、付费版、免费版……如果每个版本都维护一套代码,那简直是灾难。
Gradle的构建变体(Build Variant)就是解决这个问题的。它由构建类型和产品风味组合而成。
举个例子:
android {
buildTypes {
debug {
isDebuggable = true
applicationIdSuffix = ".debug"
}
release {
isMinifyEnabled = true
proguardFiles(...)
}
}
flavorDimensions += "version"
productFlavors {
create("free") {
dimension = "version"
versionNameSuffix = "-free"
}
create("pro") {
dimension = "version"
versionNameSuffix = "-pro"
}
}
}
这样,Gradle会自动生成4个变体:
- freeDebug
- freeRelease
- proDebug
- proRelease
每个变体可以有自己的代码、资源、依赖。比如free版用广告SDK,pro版用高级功能SDK:
dependencies {
"freeImplementation"("com.example:ad-sdk:1.0.0")
"proImplementation"("com.example:premium-sdk:1.0.0")
}
我曾经接手过一个SDK项目,之前的人用if-else判断版本,代码里到处都是if (isFreeVersion)。后来我改成构建变体,代码干净了,构建也快了。你想想看,编译期就能确定的分支,何必留到运行时?
3.5 知识体系总览
下面这张图,帮你把本章的核心逻辑串起来:
3.6 避坑指南
最后,分享几个我踩过的坑:
- 混淆规则遗漏:我曾经漏掉了一个内部使用的反射调用类,结果用户反馈「调用SDK方法时崩溃」。排查后发现是混淆把反射类改名了。从那以后,我每次发布前都会用
-whyareyoukeeping检查混淆规则。 - 多模块版本不一致:多个模块引用了同一个第三方库的不同版本,导致运行时冲突。解决方案是在根项目的
build.gradle.kts里统一声明版本:val okhttpVersion by extra("4.10.0")。 - 构建变体资源覆盖:free版和pro版有同名资源文件,但内容不同。Gradle默认会以「高优先级变体」覆盖低优先级的。我的做法是:公共资源放main,差异资源放对应flavor目录。
./gradlew assembleRelease,然后用unzip -l查看产物里的类和方法,确认混淆是否生效。嗯,这一步虽然简单,但能避免90%的发布事故。
构建工具链这块,说白了就是「配置越细致,后期越省心」。多花点时间把Gradle/Maven配置、多模块结构、混淆规则、变体管理搞扎实,后面每次发布都会很顺畅。
可可精品资料库