在公司做了两年多的 SDK 开发,结合自己的所知所学,分享一些 SDK 开发的经验。
相信做 Android 开发的朋友,一定使用过第三方的 SDK,比如推送 SDK、分享 SDK 等。SDK 的全称是 Software Development Kit,翻译为“软件开发工具包”。SDK 通常是为辅助开发某类软件而编写的特定软件包、框架集合等。
SDK 可以分为系统 SDK 和应用 SDK。所谓系统 SDK 是为使用特定的软件框架、硬件平台等所开发的工具集合。而应用 SDK 则是基于系统 SDK 开发的独立于具体业务、拥有特定功能的工具集合。
SDK 的使用者主要是 B 端客户,最终交付产品是代码、示例和文档,客户接入 SDK 也是和 SDK 提供方交流的过程,对外沟通的成本比对内更高,遇到的问题也会更多。所以 SDK 开发对开发者的要求比对应用开发更高。能开发好 SDK 一定能开发好应用,但能开发好应用,未必能开发好 SDK。
SDK 的实现目标,概括来说:简洁、稳定、高效。
简洁
对于用户而言,一款好的产品应该是简洁易用的,不该让他们花费太长的时间学习。SDK 也当如此,它不该出现复杂繁琐的对接工作,使用者通过阅读代码和文档,花费很少的时间就能做好 SDK 的对接。
比如当开发者需要使用 SDK 的服务时,只需要在代码中新增一行即可。在项目中初始化 SDK 只要一行代码,开发者不用关心 GLContext,内部已做好处理,也不用关心同步或异步问题。
- public class FURenderer {
- // 定义
- public static void setup(Context context) {
- //...
- }
- }
- // 一行代码调用
- FURenderer.setup(context);
稳定
站在 SDK 使用者角度来看,我们期望第三方 SDK 的服务是稳定高效的,体现在提供稳定可靠的服务,同时运行时性能要高效。这就要求我们在设计实现 SDK 时要尽可能做到以下几点:
高效
无论是普通的应用开发还是 SDK 开发,都应该考虑到性能问题,SDK 设计者要着重考虑以下问题:
SDK 的架构实现决定了后续的维护难度,所以最好能够结合实际业务确定合适的方案。以项目中的模块化开发为例,讲讲架构设计的原则。
遵循面向对象开发的几大原则,目的是达到三个目标:可维护性、可重用性和可扩展性。具体来讲:
比如项目第三方 demo 的功能模块借鉴了 Java 集合框架的架构,分为契约接口、抽象类和具体实现三部分。
API 设计在任何开发中都非常重要,许多时候软件的质量好坏体现在 API 的设计上。在普通的应用开发中,API 只会在开发人员间流通,不会暴露给非本应用开发的其他人员。但是 SDK 作为一种服务,需要向开发者暴露一部分 API,这样才能使用 SDK 的服务。
下面列出一些应该重点关注的原则。
方法名表明其用途
好的方法名最直观表明它的功能,名字是自解释的,不需要额外的文档,这样做会减少不必要的沟通成本。对于开发者而言,还有什么比直接读代码更直观呢?《重构》一书中讲到,要像给自己孩子起名一样给每个变量命名,这个要求不算过分吧。
参数的合法性检验
如果程序运行时出现异常,会破坏使用者的体验,影响非常不好。我们采用“防御式编程”的思想,能够避免非法输入对系统的破坏性。
当合法性校验不通过时,针对方法权限不同分别对应不同不同的处理策略:
需要注意的是,如果检查的代价太大,那就需要综合考量。
方法只实现单一功能
一个方法应该具有单一的功能,尽可能做更少、更专的事情,这也是单一职责原则的体现。“阿里巴巴代码规约”规定一个方法最好不要超过 80 行,对庞大的方法要拆分成更小的。
另外注意,宁可提供小而美的方法也不要提供大而全的方法,大而全的方法往往经常发生变动,产生风险的可能性更高。因此不如提供更小的方法以便组合使用,小而美的方法更易做到代码复用。
访问权限控制
包括类方法的权限和变量的权限,能声明私有的不要公开,外部知道得越少越好。能声明静态的方法就用静态,静态方法天然线程安全,体现继承关系的用 protected 修饰,确保公开的方法和变量是安全可靠的。
避免过长参数
过长的参数会造成记忆上困难,还有调用传参容易出错,应当尽力避免。在无法避免过长参数的情况下,考虑其他的方法进行解决:
例如,项目里有个方法,参数非常多。
- int onDrawFrameSingleInput(byte[] img, int w, int h, int format, byte[] readBackImg, int readBackW, int readBackH);
重构后,把参数封装成对象,调用方法只用构造一个对象传入,避免大量参数带来不好的体验感。
- public class VideoFrame {
- private int width;
- private int height;
- private byte[] data;
- private byte[] readback;
- private int readbackWidth;
- private int readbackHeight;
- private int pixelFormat;
- // ...
- }
- int onDrawFrameSingleInput(VideoFrame videoFrame);
慎用方法重载
滥用重载容易让开发者感到疑惑,在需要重载方法的时候,可以使用不同方法名来代替。对于构造函数,可以通过静态工厂来代替重载。
Java 中提供的 ObjectOutputStream 类就是个很好的示范:它的 write 对于每个基本类型都有一个变形,比如写出字符、写出 boolean 等操作。设计者并没有使用重载将其设计成 write(Long l)、write(Boolean b),而是将其设计为 writeLong(l)、writeBoolean(b)。
例如,项目对外的处理方法全部是重载,只能根据参数区分,迷惑性非常大。修改为不同的方法名后,看到名字就知道要调用的方法,清楚了不少。
- // 重构前
- int onDrawFrame(byte[] img, int tex, int w, int h);
- int onDrawFrame(byte[] img, int w, int h);
- // 重构后
- int onDrawFrameDualInput(byte[] img, int tex, int w, int h);
- int onDrawFrameSingleInput(byte[] img, int w, int h, int format);
避免方法直接返回 null
对于需要返回数组或集合的方法,不要返回null。比如我们去买糕点店买面包,面包没了是一种正常状态,就不应该返回 null,而是返回长度为 0 的数组或集合。Java 提供了 Collections.emptyXXX() 表示空集合。
避免引入第三方库
GitHub有许多开源的第三方库,比如网络请求 OkHttp、图片加载 Glide 等,但在 SDK 开发中,遵循的基本的原则是:
引入第三方库可能带来下面几个问题:
保证兼容性
SDK 是不断迭代的,每次发布都会有新功能和 bug 修复。对于使用者来说,升级版本不该有太大的改动,一般直接替换库文件或者修改远程依赖库的版本号就够了。避免直接对公开接口的重命名,如果旧接口废弃,要通过 @Deprecated 关键字标明,并给出替代方案和废弃的时间。
减少入侵性
要保证较少的代码侵入主要在对外提供服务时,充分考虑开发者的使用场景来设计优良的 API。一套优良的 API 在定义时要满足绝大数开发者预期的方式——语义上要求通俗易懂,使用上要求简单可靠。具体的表现是,在正常情况下能够稳定可靠地运行,在异常情况下及时地反馈错误信息。
比如使用 Gradle 下载依赖库,AAR 包中有不必要的 bundle 资源,我们提供了打包 apk 时的构建配置,自由选择要打包的 bundle,减少了对宿主应用的侵入性。
- applicationVariants.all { variant ->
- variant.mergeAssetsProvider.configure {
- doLast {
- delete(fileTree(dir: outputDir,
- // 删除不必要的 bundle 文件
- includes: ['model/ai_face_processor_lite.bundle',
- 'model/ai_gesture.bundle',
- 'graphics/controller.bundle',
- 'graphics/tongue.bundle']))
- }
- }
- }
封装包
Android 平台通常使用 jar 和 aar 发布 SDK,区别是 jar 只包含代码,aar 可以包含代码、资源和动态库。一般而言 aar 是最合适的交付方式,把它上传到 maven 服务器,使用者就可以一行代码集成。对于需要灵活定制的客户,我们也会提供 SDK 的源码,弊端就是升级困难,要改动很多的代码。
对于代码混淆,公开接口和 native 使用的接口不要混,内部的实现细节可以混淆,以减少 SDK 包的大小。
接入文档
接入文档用来告诉 SDK 使用者,如何使用 SDK、详细步使用骤和可能发生的问题。文档内容包括:更新记录、基本信息、API 说明、集成步骤、FAQ等。好文档的标准就是清晰明了,通俗易懂。一个完全不懂 SDK 的开发者看着文档就能对接,对于经常遇到的问题要逐条列出,专业名词要有对应的解释。
Demo 示例
集成 Demo 通常是一个简单的 App,用来展示如何快速地接入 SDK。Demo 的源码托管到 GitHub,方便使用者参考,其版本变更策略和 SDK 版本的变化保持一致。尽管是个 Demo,它的开发原则也要与 SDK 一致,确保高质量的交付。
武汉晚报讯(记者王超然)自动驾驶出租车体验如何?车上有没有司机?会不会迷失...
1. 介绍 本文为以前做的项目总结,由于相关支付 SDK 迭代,原文已经不满足需求,...
今天我给大家分享20款小众宝藏APP,工作、生活全不误,每天5分钟让自己悄悄成长...
在电视剧《亮剑》片尾,有这么一个剧情,李云龙长期与田雨分居,张白鹿趁虚而入...
组织为什么要在机器学习治理上挣扎?当我们要为组织解决机器学习治理时,我们看到...
北京时间 3 月 5 日消息,使用人工智能的算法正在尝试以意想不到的技巧来解决问...
上周微信刚刚更新了8.0版本,带来新的表情、状态、音乐MV等玩法,直到现在关于它...
2020年是基于深度学习的自然语言处理(NLP)研究的繁忙年份。最大的噪音的英文由迄...
沉浸式技术是不仅激发了企业而且吸引了普通消费者的最新潮流之一。看到这些新生...
新晋世界首富、特斯拉CEO、科技大佬马斯克一度呼吁人们限制机器人,认为机器人在...