2. SDK 基本概念与架构:认识 SDK 的类型、依赖与接口设计
做 SDK 接入改造,第一步得把「SDK 到底是什么」搞透彻。很多新手同事一上来就照着文档拷代码,出了问题根本不知道怎么排查。说白了,SDK 就是别人帮你封装好的工具箱——你把工具箱拿来用,但得清楚里面有什么工具、工具之间怎么配合。
我自己经历过一个项目,接入支付 SDK 时少了两个传递依赖,结果跑测试用例一直报 ClassNotFoundException。嗯,从那以后我养成了习惯:接任何一个 SDK 前,先花半小时把它依赖树捋清楚。
2.1 SDK 到底是什么?
SDK(Software Development Kit)是一组预编译的代码、库、文档和工具的集合。你想想看,它帮你省掉了从零实现加密、网络请求、数据解析这些脏活的时间。一个典型的 SDK 包含这几块:
- 核心库——封装了业务逻辑,比如推送 SDK 里的长连接管理
- 接口定义——你调用的方法 / 类,也就是暴露给开发者的 API
- 依赖配置文件——标明它需要跑在什么环境下、依赖哪些第三方库
- 示例代码 + 文档——告诉你「我预期你怎么用我」
我记得有一次对接某云厂商的存储 SDK,他们给的文档里说「集成只需三行代码」,结果我写完后发现内存泄漏。回头一查,是他们依赖的 okhttp 版本和项目里的冲突了。所以你看,光看接口不够,必须理解依赖。
2.2 SDK 的常见分类
SDK 不是只有一种。实际工作中,我们按部署位置、职能、语言做区分。下面这张表格是我自己整理的分类法,你在选型时可以对照看:
| 分类维度 | 类型 | 典型例子 | 接入注意点 |
|---|---|---|---|
| 部署位置 | 客户端 SDK | 推送、埋点、登录 | 包体积、多线程安全性 |
| 服务端 SDK | 支付、短信、存储 | 连接池、重试策略、线程模型 | |
| 编程语言 | Java / Kotlin | Android SDK | ProGuard 规则、多版本兼容 |
| JavaScript / TypeScript | Web SDK | 跨域、异步加载、压缩混淆 | |
| 业务职能 | 工具类 SDK | 图片加载、网络请求 | 替换成本低,可考虑自研 |
| 业务 SDK | 支付、IM、推送 | 耦合度高,替换风险大 |
这里我想多说一句:客户端 SDK 和服务端 SDK 的思维方式完全不一样。客户端要关心包大小、用户隐私权限;服务端更关心吞吐量和 API 响应时间。你在改造时,不要用服务端的思维去写客户端代码,反过来也一样。
2.3 SDK 的依赖关系——避坑第一课
依赖是 SDK 接入里最常见的坑。你接一个 SDK,它自己又引了别的库。那些库还可能和你项目里的版本冲突。我这里把依赖分成三层:
- 直接依赖:你在代码里显式引入的 SDK(比如 compile 'com.example:push-sdk:2.0')。
- 传递依赖:SDK 内部引用的库(比如 Retrofit、OkHttp)。你项目里原本可能已经有一个 OkHttp 3.12,但 SDK 要 4.0,冲突就来了。
- 系统依赖:SDK 要求 Android API Level ≥ 21,或者需要在 iOS 上引入 Security.framework。这些底层依赖不满足,SDK 初始化就会崩。
我曾经在一个旧项目里接入新版地图 SDK,它最低要求 API 24,但目标用户还有不少 API 21 的设备。结果一上线就 Crash。所以后来我给自己定了个规则:接 SDK 前先看一眼 minSdkVersion 和 targetSdkVersion,不符合就要做 fallback 或者找替代品。
依赖管理的另一个麻烦是版本锁定。举个例子,你用了一个支持三方登录的 SDK,它依赖了 volley 1.2。恰巧你项目里的网络库也是 volley,但版本是 1.0。这时候 Gradle 通常会选择较高版本,但 API 不兼容又可能出问题。我给你个建议:把 SDK 的依赖包单独打包,做成 fat aar 或者 shaded jar,把传递依赖重新命名,这样就不会污染宿主的类路径。下面是一个用 Gradle Shadow 插件的简化配置:
// build.gradle
plugins {
id 'com.github.johnrengelman.shadow' version '7.1.2'
}
shadowJar {
relocate 'okhttp3', 'myapp.shaded.okhttp3'
relocate 'okio', 'myapp.shaded.okio'
}
这么做虽然增大了一点包体积,但解决了依赖地狱的问题,我觉得非常值。
2.4 接口设计原则——好的 SDK 让人「会心一笑」
接口暴露给开发者,就像建筑物的门把手。你想想看,一个反人类的设计会让你每天工作都很痛苦。我总结了几条自己评估 SDK 接口质量的标准:
- 最小暴露原则:好的 SDK 只暴露 3-5 个公有方法,所有配置往 Builder / Config 对象里塞。比如「init()」「send()」「registerCallback()」就够了。那些让你写十几行配置的 SDK,我一般直接弃用。
- 回调 vs 同步:网络请求和 I/O 操作一定要异步回调,不要让开发者自己建线程。SDK 内部应该管理好线程池,并提供超时、重试、取消等控制。
- 参数校验:SDK 内部要静默校验参数。传 null 或者非法值不能 throw Exception,而是用日志 warning + 默认行为。我见过一个登录 SDK 传错 直接抛空指针,把整个 App 都弄崩了——这种「不友好」的接口不要在生产使用。
- 版本兼容:接口签名不能随便改。如果需要升级,务必提供 @Deprecated 过渡期,让开发者有半年时间迁移。
下面是一个我比较推崇的 SDK 初始化接口风格:
// 好的设计:链式 Builder + 默认值
SdkClient client = SdkClient.newBuilder()
.appKey("YOUR_APP_KEY")
.debugMode(true)
.callbackExecutor(Executors.newFixedThreadPool(2))
.build();
client.start(); // 启动长连接
client.sendMessage(new TextMessage("hello"));
你看,调用者不用关心网络、重连、心跳,SDK 内部自己搞定。这就是「会心一笑」的接口。
最后,我用一张图来总结 SDK 的核心架构与分类。你把它当作本章的知识地图,后面改造过程中可以反复回来对照。
图上你可以看到,SDK 从核心库出发,按使用场景分成客户端、服务端和工具类三大阵营,每个阵营下又有具体的业务方向。无论哪种,最终都要落到「依赖管理」和「接口设计」这两个核心问题上。后面章节的改造方案,基本都是围绕这张图展开的。
本章要点速记:
- SDK = 核心库 + 接口 + 依赖 + 文档
- 按部署位置分客户端 / 服务端 SDK,接入思维不同
- 依赖冲突是最大隐患,shading / relocating 是好方法
- 接口设计遵循最小暴露、异步回调、参数校验、版本兼容