感谢肥肥鱼的投稿,本文是Open软件开发小组的发布的第一篇投稿文章,欢迎朋友们踊跃投稿,我们努力给大家提供一个展现自我的平台,也旨在为大家提供优质的文章。
悄悄话:本文是原作者6年开发经验,作者经过数次修改,历经四月,完成SDK开发的第一部分,从SDK的分类、SDK的设计、API的设计、版本管理等几个方面展开论述。
作者 介绍
肥肥鱼
Android SDK 开发,6年开发经验,目前致力于室内定位技术的研发。
前言
男怕入错行,女怕嫁错郎。
肥肥工作六年有余,其中四年的时间致力于 SDK 开发。细细想来,唏嘘不已,感触颇多。
SDK 开发是一份不被理解而又枯燥无趣的工作。听说,每个 SDK 开发者上辈子都是折翼的天使。所以,程序猿与程序媛们,如果你身边有 SDK 开发者,请爱护他们。
这些年来,肥肥都假设,使用我的 SDK 的开发者都是一群肥肥打不过又跑不赢的暴力狂,最关键的是还知道我住哪里。
六年来一路荆棘,一路坎坷,一事无成。
扬雄亦慕仲尼之教者,以著书立言为事,得自易哉。
提示:文中链接需要点击文章末尾处阅读原文才能点击。
1
SDK 分类
目前来说,并没有统一的、官方的文档定义 Android 应用开发中常见的 Library、API、SDK 以及 Framework 这些概念。我们基于其字面意思以及日常使用习惯做如下的解释:
一般来说,SDK 是 Framework、API 以及 Library 的集合。Framework 定义了 SDK 整体的可重用设计,规定了 SDK各功能模块的职责以及依赖关系。SDK 中功能模块的具体实现则是 Library 的主要职责。各模块之间的通信以及 SDK 所能提供的服务则通过 API 体现出来。
通常情况下,SDK 在应用程序中是作为特定功能提供者的角色出现的。例如推送功能的 SDK、统计功能的 SDK、广告功能的SDK、性能监测功能的 SDK 以及分享功能的 SDK 等等。
2
SDK 设计
前文中说到 SDK 是作为应用程序中特定功能的提供者而存在的。通常情况下,SDK 是作为第三方服务而被引入到应用程序中的,SDK 的品质能够影响到应用程序的品质。
1
易用性
肥肥认为,好的 SDK 产品应该是易于使用的。我们想要创造一种简单的模式,让 SDK 的使用者在他们的应用中方便的使用SDK,那么这种模式应该是不需要侵入太多的代码或者不需要繁琐的集成工作的。
如果一个 API 的调用方式正好是开发者所预期的方式,那么我们认为该 API 的调用方式是易用的表现。多数情况下,API的品质直接决定了 SDK 的品质。SDK 的易用性体现在 API 的易用性上,那么,好的 API 设计也就显得尤为重要。
通常情况下,API 难以被误用 也是易用性的一种,这样可以有效地避免一些错误的发生。比如,对参数的校验、对边界的严格检查以及详细的说明文档,都将使得开发者在使用 SDK 的时候,能够有效地避免一些错误的。
2
稳定性
从 SDK 使用者角度来说,在 SDK 使用过程中,我们假设 SDK 本身是可靠的,不会影响到程序本身的稳定性。那么,从 SDK 设计者的角度来说,SDK 作为第三方服务,其稳定性是尤为重要的。
这种稳定性体现在如下四个方面:
3
灵活性
通常情况下,SDK 开发者并不能像应用开发者那样拥有更多的选择权。我们不能选择设备,系统版本,甚至是目标客户。相应的,我们需要最大化支持设备,提供高度灵活的 API 设计,以满足不同客户群的需要。
可以让开发者选择不同的依赖管理器或者构建工具来集成 SDK,是灵活性的一大体现。面对形形色色的应用程序开发团队,我们也要尽可能的去迎合这些团队所使用的开发环境,提供一些主要的开发工具插件的支持,包括 Gradle 、Maven 以及 Ant 等。
灵活性设计的关键是了解你的 SDK 用户的需求,然后做出需要支持的最低系统版本的决定。我们很希望我们的 SDK 能够支持尽可能多的系统设备,对于这一点,降低支持最低操作系统版本是很有必要尽力去做的。
但从另一方面来看,兼容低版本也是要付出代价的。并没有什么直接的法则能够告诉我们如何才能在繁琐度和更好的兼容性上权衡。支持旧的操作系统版本,通常意味着不能使用操作系统的新特性,同时还要面对一些旧版本存在的问题。除此之外,我们还要花费更多精力去测试代码的正确性以及兼容性。
4
最小资源开销
相对于 PC 来说,移动设备的硬件资源显得尤为珍贵。SDK 应尽可能的降低如下几种系统资源开销:
对于 SDK 来说,如果没有完全的必要性(比如使用 SharedPreference),选择 SD 卡目录存储数据是一种不错的选择。
这样做的好处在于,一方面可以减少/data/data/package_name/和/storage/sdcard0/Android/data/package_name/目录的存储压力,另一方面则方便多个应用程序间共享文件。
当然,无论使用何种存储目录,为 SDK 创建独立的文件夹是非常有必要的(比如 SDK 使用/data/data/package_name/sdk_cache目录),这也是为了方便将 SDK 文件与应用程序文件区分开来。
相较于目前动辄16G 起步的存储空间来说,应用程序的使用者对于电量和网络流量的消耗显得尤为敏感。究其原因,可能是电量和网络流量是应用程序使用者能够直接接触到的一些指标。在即便是网络流量白菜价的年达,也会有很大一部分用户因为抠门亦或是运营商等原因,仍旧使用着每月几十兆流量的套餐。而对于电量的敏感,可能就是因为现代人都有的 低电量恐惧症 这样时髦的毛病了。
内存以及 CPU 的过度使用,一方面带来了过度的电量开销,另一方面则可能造成应用程序卡顿甚至 ANR 等问题。
这些问题都能够或直接或间接的影响到应用程序使用者对一款应用程序的评价。
5
主线程
众所周知,Android 系统中主线程又被称为 UI 线程,理想情况下,主线程只负责向 UI 组件分发事件(触屏事件、渲染事件等)。
系统并不会为每个组件创建单独线程,在同一个进程中的 UI 组件都会在 UI 线程中实例化,系统对每一个组件的调用都从UI 线程分发出去。那么由此引发的问题就是,响应系统回调的方法(组件生命周期、触屏事件等)都是在 UI 线程中执行的。
如果所有的工作都是在 UI 线程中执行,特别是做一些耗时的操作(Http 请求、数据库查询以及文件读写等),都会阻塞UI 线程,导致事件的分发停止。 从用户的角度来看,是应用程序卡顿甚至卡住了。更为糟糕的情况是,如果 UI 线程阻塞的时间过长(UI 线程中大约5秒,BroadcastReceiver 中大概10秒),系统就会弹出 ANR(Application Not Response)对话框。
从另一个方面来说,Android 的 UI 组件并非是线程安全的,也就意味着不能从非 UI 线程操作 UI 组件。所以,SDK的线程模型有四条重要的设计原则:
6
最小权限原则
Android 应用程序权限机制限制应用程序访问特定的资源,如照相机、网络、存储系统以及查询用户数据以及以及某些 API 的调用。
一般来说,系统会在应用程序安装过程中提醒用户该应用程序所申请的权限,如果所申请的权限过高(Root 权限)则会在应用程序申请该权限时弹出窗口进行通知。
而自 Android 6.0 开始则使用了全新的权限控制系统,除了以上权限控制的机制之外,还会在应用程序访问特定系统功能时(比如使用蓝牙模块),也会通过弹出窗口的形式的进行通知。
相对应的,在 SDK 开发过程中,我们应该为 Android 6.0 及以上版本单独做权限方面的适配工作。
那么,作为第三方服务的 SDK 一定要遵循的一个原则就是:最小权限原则。最小权限原则指的是,SDK 尽可能不要申请非必要的权限,尽可能的不要给使用 SDK 的应用程序带来额外的权限申请。
举例来说,如果 SDK 中并没有使用到拨打电话的功能,但是却要求应用程序开发者在AndroidManifest.xml文件中声明android.permission.CALL_PHONE权限,那么就是违反了最小权限原则。
违反最小权限原则并不会对 SDK 本身的业务带来任何影响,但是这会使得应用程序向系统申请不必要的权限而造成的额外的权限开支。由此带来的后果是用户对于应用程序的不良印象。
7
严格的生命周期把控
SDK 作为服务的提供者,定义清晰且严谨的生命周期模型显得尤为重要。一种简单的做法就是 SDK 的生命周期托管给当前Activity 的生命周期管理。由此带来的好处就是,SDK 可以在恰当的时机做恰当的事情。比如我们可以在 onCreate() 的生命周期中完成一些初始化的工作,而在onDestroy()的生命周期中完成对象的销毁工作以及在应用程序的onPause()状态暂定一些后台的操作以节省资源。
3
API设计
本文中,我们假定 API 设计只涉及如下两方面:
之所以将这两方面拆分出来,是因为肥肥觉得这是两种不同的 API 设计理念。首先是面向的用户群体不同,SDK API 面向的是 SDK 使用者,也就是商业用户,而模块 API 则是面向 SDK 团队中的其他开发人员。其次,SDK API 是由具体的使用场景而决定的,而模块 API 则是由具体的功能而决定的。
从公司的角度来说,API 的通用商业价值是可以进行评估的。从数据的角度来看,API 应该算是公司资产的一种,因为设计优良的 API 实现了数据的可访问性、准确性、可应用性以及安全性。每一个公共 API 都在某些程度上提供了特定数据的可访问性,而设计优良的公共 API 则很大程度上保证了数据的准确性以及安全性。对于每一个开发人员来说,只要参与到编程的过程中,那么你就是一名 API 的设计者——因为好的代码即是模块,每一个模块就是一个 API(虽然这并不适用于 SDK API的开发)。
与 SDK 内部模块 API 的设计相比,SDK API 的设计难度要更大一些。 我们下文中的讨论围绕 SDK API 的设计展开,当然其也适用于模块 API 的设计。
好的 API 设计来自于迭代过程。
在开始设计你的 API 之前,你应该先了解设计这个 API 的目的,这也就意味着我们要设计出一种接口,让它的使用方式符合API 本身的设计目的。作为 SDK 开发者,我们对 API 设计所做的任何一个决策都会影响到 SDK 产品的质量。在我们能够做出一个正确的决策之前,很可能会先做出一个错误的决策,并从中吸取教训。实际上,在经历了多次的错误决策之后才可能接近正确的决策。
这正是 API 设计中迭代的意义。在实际的操作过程中,我们所面临的一项挑战在于,在某个 API 发布之后,再进行变更的成本变得非常高昂,并伴随着非常大的风险。
我们力求在 API 变更的成本变得高昂之前,就消除易用性与设计方面的问题。这需要强有力的对于产品需求的把控、全面的测试以及深厚的 API 设计功力来保证。
设计良好的 API 应该具备如下几个特点:
1
单一职责原则
单一职责原则说的是在类或方法的设计中,应该保证有且仅有一个引起类或方法变化的原因。通俗来说就是一个类或方法只负责一项职责。如果有两个比较接近的功能,但是使用一个接口实现有点繁琐,那么就应该使用两个接口。不要为了减少接口的数量而生硬的把两个接口合并为一个。
2
参数尽可能少
接口调用中应尽可能少的要求调用中传递参数。如果 SDK 能自己获取的参数就不需要让开发者传递。
在同一个接口中使用大量的相同类型的参数也是不推荐的。如果无法避免,建议将参数封装成对象。
3
参数合法性校验
参数合法性校验应该是接口要做的第一件事情。所有的参数必须校验其合法性,并视具体业务对不合法参数进行处理。一般情况下,除了必要参数,对于其他参数可以使用默认值或者区间值(超过最大、最小值使用最大、最小值)的方式来确保业务的正常流程。如果必要参数不合法,可以考虑使用抛出运行时异常的方式通知开发者。
4
优美的降解
开发者经常容易不耐烦,所以对于一些错误或异常,应该尽可能早的抛出。比如一些能够在编译期间就能抛出,终归好于在运行期间抛出。也就是说,SDK 开发者应该尽可能早的把一些可以预期的异常抛出,以便让开发者尽快处理这些异常。
5
实现不要影响 API
正式发布的 SDK 的接口应该是稳定的,这其中包括其参数类型、返回值类型、异常类型。
我们假设正式发布的 SDK 中的任何一个接口,都有机会被调用。那么,这样也就要求我们在后续的版本迭代中保证接口的参数类型、返回值类型以及异常类型是统一的。
如果需要变更接口功能,建议增加新的接口而不是改变现有接口。
4
版本管理
在 SDK 的升级、维护策略中,版本管理是一个非常重要的组成部分:
版本号的命名及管理并没有统一的标准,不同的团队往往使用不同的命名风格。但是无论使用哪种版本命名风格,给出详尽的版本变更记录是一种不错的选择。
1
SDK 迭代版本
按照软件版本的发布阶段来看,一款成熟稳定的 SDK 产品的版本迭代往往会经历如下阶段:
2
SDK 版本号命名
一个比较合理的版本号命名规范由如下四部分组成:
V1_0_2_201511171733_beta
3
SDK 版本号修改原则
4
API 版本管理
API 的版本受到 SDK 版本迭代状态的约束,但是不受 SDK 版本号修改原则的限制。
只有处于 release(或 rc ) 状态的 API 才能是对外提供服务的,否则该 API 应该是对应用程序开发人员不可见的。换句话说就是,坚决不发布处于 alpha 和 beta 状态的 API。
API 一旦对外发布,其内部实现以及方法签名原则上处于不可变更状态:
5
Http接口版本管理
SDK 一旦发布,你将无法强制要求应用程序开发者跟随你的 SDK 版本迭代而更新他们的代码。在一定周期内,将会有多个SDK 版本在提供服务,除了建议开发者升级 SDK 之外,服务端将不得不为多个 SDK 版本提供支持。
从另一方面来看,随着需求的变更,API 会相应的增加或声明废弃。与之相对应的,Http 接口往往也会发生相应的变化。
6
文档以及Demo版本管理
一种比较合理的做法是,文档以及 SDK 对应的 Demo 受 SDK 版本的管理。更为简便的做法就是文档以及 Demo 采用 SDK 版本号进行统一管理。普遍的做法是,即便是 SDK 接口的轻微改变,也要及时的体现在对应的文档上,并更新对应的 Demo。在 SDK 上线初期,其迭代频率相对较高,那么就会出现多个版本 SDK 共存的情况。合理的文档、SDK 以及 Demo 间的版本关系,也就显得尤为重要。
5
参考文章
结语
SDK 开发是一个很大的范畴,相较于应用程序的开发,有相似之处,也有不同之处。从面相的客户全体来说,应用程序开发者面向的是普通用户,而 SDK 开发者则面向应用程序开发人员。从服务的角度来说,应用程序开发人员在设计应用的时候,往往要考虑性能、兼容性、用户体验、渠道以及版本迭代。而 SDK 开发人员不仅要全面考虑上面这些因素,还要近乎于苛刻的将性能、兼容性提升到极致。对于某项需求的验证,应用程序开发人员会选择在部分灰度版本中进行验证,而 SDK 开发人员则没有这样的幸运,只能依赖对业务的高度抽象进行验证。当然,目前普遍的做法是基于自己的 SDK 开发相应的应用程序,一方面能够进行一些需求的验证,另一方面,自己成为自己的客户,也未尝不是一件坏事。
肥肥不才,文章前后修改数次,历经四月,终于写完《Android SDK 开发》的第一部分。这期间肥肥仔细拜读了 参考文献 中各位前辈的文章,受益颇多。肥肥在文章的有些章节内容中,直接参考了一些前辈的观点,甚至存在一些直接复制的行为。在此向各位前辈致以最高的敬意,并为肥肥的剽窃行为作出道歉。
剩余的内容将会围绕 SDK 的测试、安全性、业务配置以及数据运营展开讨论。
小贴士
本文由原作者肥肥鱼独家授权Open软件开发小组发布,著作权归原作者所有。如需转载请联系原作者申请授权。