如何去设计一个好用的SDK,自己做了很久的SDK,实际上并没有过多去思考过这个问题。最近对起总结一下,在回答这个问题之前,我们可以自己思考一下:对于接入者,怎样才算是一个好的SDK?对我自己而言,如果我要去接入一个SDK的话,可能会去考虑分别针对几个阶段:

接入前: 有清晰的文档能告诉我这个SDK 的环境要求,比如 minSdkVersion compileSdkVersion 这些硬性配置,明确列出所有直接 / 间接依赖的第三方库及版本,以及包大小、申请权限等。

接入中:接入需要足够简单,在 dependencies 中一行搞定;如果有初始化操作也尽量简洁,一行搞定;对应 SDK 调用 ,提供一个单例类或者某个接口,在这个类/接口里面能够看到最核心的 Api;使用的接口对各种异常处理有足够多的错误提示信息;针对输出日志能够有配置开关等。

接入后: SDK 由于功能需要更新,需提供详尽的 CHANGELOG,接入者只用更新版本号;

上面是站在接入者的角度去考虑的,作为SDK的开发者则应该遵循以下的规则。

先前原则

核心原则:以接入方体验为中心

在开发一个SDK的时候,把接入者当作“小白”,不寄希望于他能点进SDK内部,去了解内部的实现;把接入者视作“懒人”,他不想做太多事,他赖得看你代码里面的各种注释,只想看一份接入文档就搞定所有事情。

最小化原则:只暴露必要的内容

对外仅暴露核心入口类 + 必要数据类 + 统一结果类,内部工具类、中间逻辑、私有方法全部标记为private(Java)/internal(Kotlin),杜绝接入方依赖内部逻辑。

稳定性原则:避免崩溃,容错兜底

不要将你的SDK视为一个可以随时更新的应用程序,而应将其看作一旦上线就无法实时更新的后端服务。避免崩溃是第一前提,但不可能万无一失,会由于设备兼容性问题,没法在上线将所有情况全考虑到,那么需要SDK能够有兜底操作,比如某个开关能够关闭掉该功能,或者针对特定机型Android系统等进行屏蔽(需服务端配合)。

开发阶段

详细的文档

接入需要清晰、详细的文档,了解如何充分发挥 SDK 的能力。一份全面的文档需要包含:

  • SDK开发环境要求

  • SDK安装和配置的详细流程

  • 核心 Api 使用的代码示例

  • 有一个可进行索引的目录

安装和初始化

使用Maven Publish Plugin将 SDK 发布到 Maven 仓库,配置groupId/artifactId/version,确保接入方仅需一行implementation即可引入;

避免在 AndroidManifest.xml 中配置冗余内容,如需一些动态配置,提供Gradle 插件自动注入这些能力,无需接入方手动修改。

初始化的时候一行搞定,比如:XxxSdk.Builder().appKey("your_app_key").debugMode(true).build()

极简 API 设计

设计良好的 API 确保接入者能够轻松集成并使用你的 SDK 功能,避免不必要的复杂性。用 Kochava SDK 工程总监 Nathan Darst 的话说:

The developer wants to solve a problem, and the API should facilitate solving this problem quickly and intuitively with as few lines of code as possible. A good API prioritizes and focuses on the most common use cases; it is not muddied up with unnecessary, ambiguous, redundant, or rarely used functionality likely to confuse the user.

开发者的核心诉求是解决实际问题,而 API 的设计初衷,应是助力开发者用最少的代码、最直观的方式,快速解决问题。一个优质的 API,会优先聚焦并打磨最常用的核心使用场景;不会掺杂无关、模糊、冗余或极少用到的功能 —— 这类功能只会让使用者产生困惑。

更多的代码准则应参考谷歌官方:Android API 准则

轻量级 SDK

尽量减少外部依赖,尤其是核心功能方面。如果非得用第三方库(Gson、okhttp等),就把它们隔离出来,或者更好的是,让接入者选择排除或替换这些工具,或者参考koin,采用模块化架构允许仅集成所需的组件。

错误处理、调试、测试

当出现错误时,清晰且可作的响应帮助接入者快速识别并修复问题,减少挫败感,节省集成和故障排除的时间。尽可能将详细的错误返回或者在日志里面输出出来,不要只展示“未知错误”、”-1”、“errros”这些表达不明确的错误。

除了明确的错误信息提示外,还需要提供一些详细详细日志,且可配置。以视频渲染SDK为例,由于有多个线程参与,还有 GPU 参与,断点调试几乎不太可能,如果在每一帧都打印日志的话,又会得到大量冗余的信息,所以这里就需要日志输出可配置化。

为了更好地测试你的库,建议使用 依赖注入,这里以一份网络请求做对比->使用依赖注入和不使用依赖注入的两种实现对比

依赖注入通过解耦被测试类与具体依赖,实现依赖的灵活替换与精准模拟,让单元测试更可控、快速且用例间相互独立。

前后的兼容性

当 SDK 更新破坏现有 API 实现时,接入者会面临不必要的返工,通过保持向后兼容性,使接入者能够无缝采用新的 SDK 版本,同时不影响他们的工作流程。实现这一目标的一种广泛采用的策略是语义版本控制(SemVer), 它采用三部分版本控制方案,清晰传达变更的范围和影响:

以 4.3.2 为例,4 是主版本号,其变更代表 SDK 出现了向后不兼容的破坏性修改,需适配调整才能使用;3 是次版本号,其变更为新增兼容式功能 / 特性,无需改动原有代码即可升级;2 是补丁版本号,其变更仅为兼容式 bug 修复,是最无风险的小版本升级。

其他

这里主要是一些琐碎点,或者能提供一些在SDK 开发过程中的帮助

多利用语言特性

目前我遇到的利用 kotlin 特性最好的开源库是 koin,使用了大量的高阶函数 & Lambda 表达式,比如:高阶函数 / Lambda实现简洁的 DSL 模块定义(module { … }),委托属性实现by inject() 延迟获取依赖,协程支持协程作用域、挂起函数创建依赖等

演示代码片段:koin使用到 kotlin 的一些特性

善于使用脚本

脚本可以帮助开发者实现很多重复工作,包括不限于:Android SDK 一键编译打包(AAR/JAR)、多版本编译(测试不同 Kotlin/AGP 版本)、自动更新 SDK 版本号、生成版本日志(结合 Git 提交记录)、测试自动化等,这里面有很多可以使用 Gradle脚本,有的也可使用 Shell 脚本。

其他持续补充……

总结

本人结合个人Android SDK开发经验,好用的SDK需围绕接入方体验、稳定性设计:接入前有清晰文档,接入中极简便捷,接入后无缝升级。开发者需遵循接入方体验为中心、最小化暴露、稳定性兜底三大原则,开发中做好文档、安装初始化、API设计等核心工作,善用Kotlin特性与自动化脚本。本文为个人经验总结,因SDK开发场景多样,不适用于所有情况,仅供学习参考。

参考

Building great SDKs

SDK Development; The Good, The Bad, The Ugly

SDK design best practices

Mobile SDK Development Guidelines

Android SDK开发艺术探索(一)开篇与设计

Building Better SDKs