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的约定式结构反而更省心。

我的建议:新项目直接用Gradle + Kotlin DSL。Kotlin DSL有类型安全、IDE补全好,写起来比Groovy舒服太多。

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,因为用户不需要知道内部实现
}
注意:模块间依赖不要形成循环依赖。我曾经在一个项目里,core依赖了api,api又依赖了core,结果构建时直接报「Circular dependency」错误。排查了整整一下午才找到问题。

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>
核心原则:混淆只对内部实现生效,对外API必须保留。否则用户调用你的SDK时,会直接抛出ClassNotFoundException。

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 知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

SDK构建工具链知识体系 构建工具链 Gradle / Maven 构建脚本 · 依赖管理 多模块构建 core · api · internal 混淆与压缩 R8 · ProGuard · 资源压缩 构建变体管理 debug · release · flavor Groovy/Kotlin DSL Maven XML 按功能拆分 依赖层级管理 保留API规则 资源瘦身 构建类型 产品风味 目标:高效 · 安全 · 灵活 · 可维护

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配置、多模块结构、混淆规则、变体管理搞扎实,后面每次发布都会很顺畅。

可可精品资料库