Floating Cat

Android SDK设计与开发(一)

字数统计: 5.9k阅读时长: 20 min
2016/12/10 Share

概念简介

什么是SDK

SDK是Software Development Kit的缩写,译为”软件开发工具包”,通常是为辅助开发某类软件而编写的特定软件包,框架集合等,SDK一般包含相关文档,范例和工具.

SDK可以分为系统SDK和应用SDK.所谓的系统SDK是为特定的软件包,软件框架,硬件平台,操作系统等简历应用时所使用的开发工具集合.而应用SDK则是基于系统SDK开发的独立于具体业务而具有特定功能的集合.比如在进行Android 应用开发时,我们使用Google提供的系统SDK(Android SDK),而我们经常使用的友盟SDK,极光SDK则是基于系统SDK开发的.

什么是Library

Library即我们所说的库,通常是一组或者几组类的集合,通常是应用中某些功能的具体实现或者对系统已有功能的增强或补充.对Android开发者而言,最常见的莫过于是Support Library,另外就是我们经常使用各种网络请求库(OkHttp,Volley),数据库操作,图片加载库(Glide,ImageLoader)等.

什么是Framework

Framework即我们所说的框架,通常是系统或者应用的骨架,很多时候,它表现为一组抽象的构建及构件实例间交互的方法.因此,可以认为,Framework规定了应用的体系结构,阐明了整体设计,写作构件之间的依赖关系以及控制流程.注意自处的Framework并不完全等同于你所熟知的Android Framework框架,可以认为Android Framework中体现了Framework的思想,并进行了实现.

什么是API

API是Application Programming Interface,又称为应用编程接口,是软件系统不同组成部分衔接的约定。更加通俗的说就API就是我们常见和编写的方法或函数.

小结

明确了上面提到的概念之后,现在就可以来描述这四者之间的关联:
SDK主要包含Framework,API及Library的三部分.Framework定义了SDK整体的可重用设计,规定了SDK各功能模块的职责以及依赖关系.其中个功能模块体现为Library.模块之间的内部通信及SDK外部通信(SDK对外提供服务的接口)则通过API进行.

另外完整的SDK还应该包含大量的示例和其他工具.比如在Android SDK的tools目录下提供了大量的辅助开发工具.

对我们而言,大部分情况下是为某种具体的业务需求开发对应的SDK,以便作为第三正提供给其他需求方使用.比如百度推送的SDK主要实现消息推送功能,需求方只需要集成百度推送的SDK便可以使自己应用具备推送功能.

到现在已经介绍了SDK的主要构成,接下来我们重点来介绍SDK的实现目标以及在SDK架构中的一些核心点.

浅谈SDK实现目标

上面介绍了开发中常见的概念,现在来谈谈SDK的实现目标.任何应用都应具备:简洁易用,稳定,高效,轻量,SDK作为一种特定应用当然也不例外.

简洁易用

按照”奥卡姆剃须刀”理论,一个好的产品对第三方使用者使用而言应该是简洁易用,不用改让使用者花费太长时间学习的.这对SDK同样适用—SDK不应该对宿主应用有过多的代码侵入,也不应该有复杂频繁的接入工作.比如当开发者需要使用SDK的服务时,只需要在缘由的代码中新增一行即可.常见的SDK初始化如下:

1
2
3
4
5
6
7
public class Ad{

@TargetApi(9)
public synchronized static void init(Context context, SdkParams params) {
//省略多行代码
}
}

当我们需要使用该SDK的服务时,通过一行代码便可启用Ad.init(this,params)

要保证较少的代码侵入主要在对外提供服务时充分考虑到使用者的使用场景来设计出优良的API.一个优良的API在定义的时候应该满足绝大数开发者所预期的方式—语义上要求通俗易懂,使用上要求简单可靠.

一个优良的API首先是简单可靠的.在正常使用的情况下体现为稳定可靠的执行,在异常情况下体现为及时的告知使用者使用错误.初次之外,遵循一致的明明规则,并是所有的API呈现出一致的风格对开发而言无疑是个好消息.

稳定

站在SDK使用者角度来看,我们期望第三方的SDK服务应该是稳定高效的,体现在提供稳定可靠的服务,在不影响宿主稳定性的前提下足够的高效,这就要求我们SDK设计者在设计并实现SDK时要尽可能的做到以下几点:

  • 对外提供稳定的API.SDK的API一旦确定,如无非常严重情况不可更改.作为提供服务方,发生API变更所带来的变更成本非常大.
  • 对外提供稳定的业务.在稳定的API后,必须要有稳定的业务来支撑.
  • SDK运行时的稳定,作为服务提供方,我们必须确保SDK自身运行的稳定,并且保证接入方不会因为我们的SDK产生不稳定的情况.
  • 版本稳定更新.和面向普通用户的应用相比,SDK版本的迭代是非常缓慢的.并且需要尽可能的对开发者屏蔽迭代过程,以免给开发者带来不必要的适配开销.

高效

无论是普通的应用开发还是SDK开发,都应该考虑到性能问题,SDK设计者应该着重考虑以下问题:

  • 更少的内存占用.在不使用多进程的情况下,SDK服务和宿主程序运行在同一进程中,这种情况下必须要求限制SDK内存的占用,不能因为说因为我们SDK占用太多的内存资源,导致应用的存活时间变短.
  • 更少的内存抖动.在占用更少内存的前提下,SDK设计者必须刻意的减少反复GC造成的内存抖动问题.
  • 更少的电量消耗.尽管很多时候无法对电量消耗做一个很好的权衡,但是仍然有一些可以参考的做法,比如减少使用耗电模块的时间.比如在使用定位服务时,不要求非常高的精度下优先使用网络定位而不是GPS定位.
  • 更少的流量消耗.

整体架构设计

SDK的架构实现决定了SDK后续的维护难度,因此有必要在此对SDK整体架构中的一些点做些简单的说明.

模块化开发

根据单一职责将系统拆分为不同的小模块,每个模块保持相对独立。模块之间通过协议或接口通信,以减少相互之间的依赖耦合.模块内部按照设计的几大原则进行实现,以保证模块本身可以灵活实现

对于现代开发而言,模块化是常用的手段,从宏观角度来看,模块是系统最小的组成单元.

组件化开发

组件开发同样是个老生常提的概念,但从我个人的感受来说,组件是对逻辑的封装,并具备单个可移植性.比如可以把日志记录做成一个组件,之后它可以被轻松在应用在不同的项目中.对于android 开发者而言,Android 提供的每个UI 控件同样也是组件,比如Button,TextView等.

在明确了组件这一概念之后,组件化开发也就不难理解:所谓的组件化就是将整个项目划分成多个模块,几个模块或者单个模块作为一个组件,开发过程中我们可以对每个组件进行并行开发,最后发布时通过依赖将组件合并成完整的应用.

那为什么要使用组件化呢?
随着android的逐渐成熟,现在的app业务越来越复杂,与此同时,android工程也变得日益庞大,代码行数十几万已经是常态,此时有几个问题便会凸显出来:

  1. 工程任何一点改动都会造成整个工程的重新编译.记忆最深的就是早期在没有进行组件化的时候,庞大的工程动辄需要十几分钟的编译时间,一杯茶的时间就出来了,很多时候,不得不眼巴巴的等着,尽管现在可以使用facebook出品的buck以及来自阿里的feeline来加速编译过程,单仍然不够.
  2. 整个工程中充斥的大量重复或者冗余的子模块,业务耦合度非常高,牵一发而动全身.这就造成了”老人不敢改,新人无法改”,因为谁也不能预知在做修改之后,会产生什么影响.
  3. 协作开发基本上是不可能的,天知道彼此在做什么.代码合并的的时候更是令人痛苦.
  4. 不方便测试.高度耦合的业务和模块导致无法下手进行测试,只能草草了事.

通过引入组件化,上面遇到的问题便可迎刃而解.在SDK当中,根据实际情况对其进行组件化,比如我们将分享功能组件化,可以轻松的支持多种渠道的分享,在需要更新分享功能时,可以对其进行单独的编译和测试.

通过组件化,我们也可以轻松的实现SDK的定制功能,通过编写编译脚本,我们可以决定哪些组件被依赖,最终合并到完整的应用当中.比如友盟中的提供的可定制分享组件(如下图)的原理就是如此.

mage-20180710224805

插件化开发

什么是插件化开发这里就不做介绍了,一方面插件化并不是个新概念,另外就是插件化到目前为止理论层次上已经非常成熟,不想15念开始研究的时候资料相对较少.

在SDK中为什么使用插件化呢?SDK不同于普通应用,不能频繁的进行更新,以免让开发者觉得SDK不稳定或者让开发者频繁的集成.SDK看起来变化较慢,实则变化频繁.就以以前做的广告SDK而言,有时候经常需要对某类机型进行数据采集或者及时更新反作弊模块,在没有使用插件化之前,解决该问题是非常麻烦的.但是在我们利用插件化之后,解决该问题就变得非常容易:我们将SDK整体划分为两部分:宿主和插件.宿主只向开发者提供必要的服务接口,并提供了自定义插件加载器.而核心的逻辑则是存在于插件中.当需要采集数据的时候,只需要由开发人员开发好数据采集插件并下发到指定设备即可;当需要修复SDK缺陷时,同样也只需要下发新的插件包即可.

通过在SDK使用插件化方案,可以有效的对开发者屏蔽手动更新的过程.宿主相对稳定,一旦确定,一般不会变动,而后续的业务变化则只需要通过更新插件来支撑.

除了上面谈到的利用插件化解决动态更新之外,通过将整个工程分为宿主和插件可以实现宿主的并行开发和分开编译,并且能有效的解决方法数65535的限制.在没有使用插件化之前,我们整个项目是由很多组件通过依赖形成的庞大工程,不得不通过

SDK初始化

和应用开发不同,很多情况下SDK没有自身的上下文Context,而必须要借助应用提供.SDK初始化的常见做法:Ad.init(Context context,AdParams params),我们往往推荐开发者在应用Application组件中的onCreate()中去掉用该方法,这就意味着该初始化过程是同步的,假如SDK本身初始化时间较长,就会影响应用的启动速度.

在这种情况下,作为SDK的设计者必须着手解决该问题.通常将SDK服务进一步划分成核心服务和辅助服务,之后通过并行初始化和延迟初始化的手段来减少SDK初始化耗时.曾经在我所负责的广告SDK中,有开发者反馈我们的SDK启动较慢,通过对整个SDK启动流程进行分析后,我们将插件加载服务和云控服务并行初始化,而对于像日志服务则采用颜值初始化,通过该手段有效的减少了初始化耗时

云更新控制

云控服务作为一种服务端控制客户端的手段在SDK中开发中非常重要,现在的SDK开发可以不支持插件化,但是必须要提供云控服务,以便让服务端能控制SDK,比如在不需要进行数据采集的时候,可以通过云控服务关闭SDK采集功能,在需要的时候在将其打开.

对本身是基于插件化开发的SDK而言,云控服务更是不可或缺.

从实现的角度而言,云控服务分为服务端主动和客户端主动.服务端主动是指服务端会将最新的云控开关的信息推送到SDK,而客户端主动则是SDK在进行操作之前会首先请求云控信息.对有推送开发经营的同学而言,这非常容易理解,就是像是为了实现消息推送功能,我们可以通过客户端轮训也可以通过服务端保持长连接进行消息推送一样.

SDK安全

SDK身份校验

为了区分接入者并挑高SDK自身安全性,我们通常会为开发者分配api key和api secret,SDK会读取开发者配置的api key和api secret,并用于随后的网络通信中.这是非常常见的做法,比如当你集成极光推送SDK的时候,它也许需要你提供api key和api secret,如果没有则需要到官网进行申请.

NDK加密

为了安全起见,数据加密类,模块算法类都都应该采用NDK开发,将其封装在so文件当中.有很多开发者不明白为什么这样会增强安全性.这里我们简单的做个说明.由于.so文件是通过c/c++编译出的文件,相对于java的反编译文件来说,可读性更差,另外大部分的Android开发者并不具备较深的C/C++能力,因此一定程度上增加了被破解的能力.

通讯加密

针对实际情况对通讯协议进行加密,具体是采用对称加密还是非对称加密,则需要根据实际情况做选择.另外,请尽可能使用https来代替http.

设备安全

在很多情况下,比如广告SDK中,有一些开发者会通过虚拟机来刷广告,因此有必要针对此情况做判断.一旦SDK检测出非法请求后可以采取两种方案,一种是SDK拒绝服务,另外一种则是正常服务,SDK会将作弊信息上传至服务器,以便后端服务定向排除数据.

流量控制

对于和服务端频繁通信的SDK而言,减少传输数据大小是重中之重.

在设计SDK和服务端通讯之间的数据协议时,需要根据实际情况考虑,但有以下几条建议值得我们接受:

  • 如果对传输的数据大小有要求,建议对数据进行压缩.
  • 可以采用json/xml/Protobuf等协议,如果它们仍然不能满足则可以考虑自定义二进制协议.

系统兼容

作为SDK的设计者,面临一个很大的问题是我们不得不考虑开发者应用所支持的系统最小版本,但是在SDK发布之前,我们并不知道会什么样的开发者使用我们提供的服务,因此为了让SDK支持更广泛的设备,我们需要降低最低支持的系统版本.比如现在失眠上主流的系统版本是Android 5.0,那么对SDK而言,起码要支持到Android 4.0,甚至是Android 2.3.

降低最低支持版本看起来很容易,但是我们不得不做更多的工作来确保SDK能表现出一致的工作行为(通常,我们在SDK内部检测当前系统版本来确定哪些方法可以被调用).更残酷的真相是我们花费了很大的精力去支持2.3,但来自2.3系统版本的请求量却连1%都不到.

权限管理

Android中任何开发都避不开权限申请.作为SDK的设计者,对于权限遵循”如无必要,无需增加”,换句话说就是用不到的权限,就不要加上去,这也是我们所谓的最小权限原则,该原则同样适用于普通应用开发.

在刚接触SDK开发时,某些早期功能需要某些权限,但是后期该功能被砍掉了,但是权限却忘记去掉,这就导致不必要权限仍然存在的情况.

另外过多的权限申请,会让开发者怀疑你的目的.比如一个广告SDK的你申请照相机权限是想干嘛?恩,我怀疑你在偷拍我….好吧,这里我只是开个玩笑.

另外,从android 6.0以上,google改变了权限申请的策略,因此需要单独对此做适配.

日志服务

无论系统大小,日志服务是基本的服务.一个良好的日志服务能够帮助我们快速的发现问题,定位缺陷,从而获得问题的解决方案.

SDK的日志服务和其他常见的日志服务并无太大的不同,但是要保证以下几点:

  1. 日志服务能够记录有效的信息,在SDK要关键位置进行打点.
  2. 日志服务上传日志信息到服务器时,要保证最大的可靠性,不能发生上传失败后抛弃日志的情况.
  3. 日志服务不能影响对正常的操作流程有过多的性能影响.SDK产生的日志信息往往是非常多的,因此必须考虑日志IO操作所带来的开销.

版本管理策略

SDK 版本号命名及修改原则

SDK版本号命名和我们以往的命名规则并无太大不同,通由4部分组成,格式为:
V主版本号子版本号阶段版本号_日期版本号加希腊字母版本号.比如V1_1_2_161209_beta.

希腊字母版本号说明

  • Alpha版:内部测试版,此版本表示该软件在该阶段主要是以实现功能为主,Bug相对较多,需要继续修改,通常只在内部流通流通而不对外开放.
  • Beta版:外部测试版,该版本相对Alpha已经有了很大的改进,不存在严重的Bug,但还是存在一些缺陷,需要进一步的测试以检查和消除Bug.
  • RC版:该版本已经相当成熟,不存在导致错误的Bug.与正式版相差无几.
  • Release版:该版本意味着”最终版本”,是最终交付用户或者公开发布的版本,也称为标准版.需要注意的是,该版本在发布的时候回以符合R来代替Release单词.

版本号修改规则

  1. 主版本号变化:当功能模块有较大的变化或者整体架构发生变化
  2. 子版本号变化:当功能有一定变化
  3. 阶段版本号变化:一般是Bug修复或者较小的变动,根据反馈,需要经常发布修订版本.
  4. 日期版本号(161209):用于记录修改项目的当前日期,每天对项目的修改都要更改日期版本号.
  5. 希腊字母版本号:此版本�号用于标注当前软件处于那个开发阶段,当软件进入到另一个阶段是需要修改.

API版本管理

和普通应用API版本管理不同,SDK设计者需要着重关注SDK API的管理.原则上SDK API一旦公开发布后其状态(签名和具体实现)应为不可变.

对于特殊情况下API的变更,需要遵守”开闭原则”,即一个类,模块,方法应该对扩展开发,对修改关闭.这就要求我们做到以下几点:

  1. 在需要调整SDK API时,优先选择添加新方法,而不是在原方法上修改.对于实现相同功能的新方法,尽可能的要兼容原始方法.
  2. 在需要废除某些方法时,需要在正式版发版前使用@deprecated标识,并给出替代方案和废弃的时间(通常是SDK版本号)

接入文档和API文档版本管理

接入文档是用来告诉SDK使用者,如何使用SDK,使用的详细步骤和可能发生的问题,每个公司会有自己的一套规则,这个不需要做太多的解释.

另外,接入文档通常分为两份:内部版和公开版.内部版通常用于内部开发人员和测试人员,信息较为详细,而公开版则是面向开发者,相比内部版会省略的一些信息.

API文档其实就是对SDK API的更详细说明,类似java中的api doc,可以借助jdk的自带javadoc直接生成,当然在android studio也提供了便捷的生成方式.

无论是接入文档还是api说明文档,其变更一般发生在SDK版本发生变化时.当SDK发生变更时,文档必须随之更新,不能出现SDK更新后说明文档不与之匹配的情况.

集成Demo版本管理

集成Demo通常是一个简单的app,用来展示如何快速的接入SDK.其版本变更策略和SDK版本的变化保持一致.


总结

SDK开发中需要关注的点非常多,每个点都不能用三言两语完成的,后面会在此基础上慢慢的补充.

CATALOG
  1. 1. 概念简介
    1. 1.1. 什么是SDK
    2. 1.2. 什么是Library
    3. 1.3. 什么是Framework
    4. 1.4. 什么是API
    5. 1.5. 小结
  2. 2. 浅谈SDK实现目标
    1. 2.1. 简洁易用
    2. 2.2. 稳定
    3. 2.3. 高效
  3. 3. 整体架构设计
    1. 3.1. 模块化开发
    2. 3.2. 组件化开发
    3. 3.3. 插件化开发
    4. 3.4. SDK初始化
    5. 3.5. 云更新控制
    6. 3.6. SDK安全
      1. 3.6.1. SDK身份校验
      2. 3.6.2. NDK加密
      3. 3.6.3. 通讯加密
      4. 3.6.4. 设备安全
    7. 3.7. 流量控制
    8. 3.8. 系统兼容
    9. 3.9. 权限管理
    10. 3.10. 日志服务
  4. 4. 版本管理策略
    1. 4.1. SDK 版本号命名及修改原则
      1. 4.1.1. 希腊字母版本号说明
      2. 4.1.2. 版本号修改规则
    2. 4.2. API版本管理
    3. 4.3. 接入文档和API文档版本管理
    4. 4.4. 集成Demo版本管理
  5. 5. 总结