开发一流的 Android SDK:Fabric SDK 的创建经验

WarMonti 9年前

来自: https://realm.io/cn/news/oredev-ty-smith-building-android-sdks-fabric/

About the Speaker: Ty Smith

Ty 是一个在 推ter 的 Android 技术负责人,专职于 Fabric 开发工具团队。他曾经负责架构了 Fabric 平台和 推ter 的 Android SDK,推动了 Digits 和 推ter SDK 的开源事业,可以说是他一手创建了更大的 推ter 体系结构。他一直专注于 Android 超过7年了,也是 GDE(谷歌开发专家)组织的一员。他经常在 Android 开发者的国际会议和各大 Android 开发者大会上进行演讲。在到 推ter 之前,Ty 曾在 Evernote 的 Android 团队工作,他带领构建 SDK 和合作伙伴的集成工作,带领开发短信平台,并建立 Zagat for Android,Zagat 之后被谷歌收购。

@tsmith

</div>

APIs & Fabric(0:00)

作为开发者,我们中的大多数人不得不使用 SDK 和 API,但经常地,我们会遇到一些令人沮丧的或设计不好的东西。要使得开发伟大的应用程序更加轻松,作为核心的软件开发工具包还有很长的路要走。在 Fabric 上,我们很关心开发者的经验,我们花了大量的时间,使我们的 SDK 更容易和更有趣。我的名字是 Ty,在 推ter 的 Android Fabric 工程师团队。我想与你们分享一些我们在开发 SDK 过程中学到的经验。

那么 Fabric 究竟是什么呢?它是一套 推ter 开发的模块化开发工具包,推ter 共享了一个共同的移动和Web平台,用于减少占用空间和提供一致性的解决问题方案,旨在提升第三方移动 App 的质量。我们去年发布了 Fabric,这是完全免费的。我们相信这是一个伟大的方式来引导你的移动应用程序。就在上个月,我们宣布了多个外部合作伙伴,现在正在基于Fabric 进行开发,未来将有更多高质量的内容提供给大家。

我们建立 Fabric SDK,我们保持了几个目标,帮助引导我们进行开发。这些原则决定了我们开发 API 和做决策的选择。这些想法可以融入你自己的SDK或甚至你的应用。我们很高兴能够看到大家离开这里后,在未来开发的你们的 SDK 中采纳和我们一致的想法。

注意事项(1:19)

在我们进入 library 或 SDK 编码之前,我们有必要考虑几个方面。

客户需求(1:27)

首先要考虑的是要找出你在 library 里的实际服务的对象是谁,是内部开发人员还是公共开发人员?谁会使用它?它带来的新价值是什么?市场上已经有了一个解决办法吗?如果是这样的话,你应该是去对其进行开发和贡献而不是重新创造一个“轮子”。

开源 vs. 闭源(1:42)

考虑开源与闭源是一个大问题。开源通常会让你更好地通过社区,获得更稳定的软件,以及更热心的内部工程师。然而,需要思考的事,你的 SDK 仅仅是集中于一个工程点呢?还是说它是一个完整的产品,但有一个后台服务呢?

因此,仔细考虑你将采用哪一种许可证(开源协议)。例如,如果你使用 GPL 许可,那么将会使得用了你的 SDK 或 library 的人也必须得使用 GPL 开源协议。更灵活的许可证可能是 Apache 2 或 MIT 许可。

Binary 打包(2:18)

特别是对于 Android,打包你的代码并不一定是简单的。你有三个问题需要考虑。首先是对于一个标准的库项目,开发者将他们包含到他们的代码中,并且由 IDE 帮忙连接它们,它是非常灵活的,但是如果他们需要分叉(fork),他们得如何保持更新?

在 Java 的世界 jar 包是另一个好例子——很标准的二进制包装。不幸的是,对于 Android,它们不能捆绑打包自己的资源文件,所以对于一些和视图相关的 library 一般都不能采用 jar 方式打包。

最后,在 Android 世界还有一种打包方式即 aar,它是谷歌现在支持二进制打包方式。这是一个压缩的容器,包含了编译的源代码以及资源文件,它可以通过 Gradle Maven 依赖源从而非常方便快捷地分发给开发者。

Hosting the artifacts(3:10)

最后的考虑是在哪里托管你的打包结果。Maven Central,是标准的仓库。然而,他们都需要开源许可,因为他们想保护他们的服务的用户,他们不希望有人会隐式地拉下来一个二进制包,并选择一个他们没有得到审查的服务条款。如果使用了另外的资源库(如果你有一个专有的二进制文件),则开发人员必须手动添库到编译脚本中。

创建伟大的 SDK(3:38)

在创造 Fabric SDK 的工作是一个梦幻般的学习过程。我们的目标就是涵盖这五大方面:易用、稳定、轻巧、灵活,很好的支持。我们相信伟大的 SDK 要实现这些得走很长的路。

易用(3:50)

其中的一个关键就是可用性。我们认为产品应该是易于使用的。

那么所谓的易用到底是什么呢?我们想创造一种最简单的方式,让人们在他们的应用中开始使用 Fabric. 如果它是易用的,它应该是不需要侵入太多你的代码或者你需要做很多繁琐的集成工作。只要在你的代码中新增一行我们的代码,就可以使用它了,类似这样:

Fabric.with(this, new Crashlytics());

但易用的同时,有时还得能够定制,许多开发者可能希望更多的定制。要做到这一点,我们使用的 Builder 生成器模式设置一些选项,比如设置一个监听器好让程序在应用程序崩溃之前通知你。

Crashlytics crashlytics = new Crashlytics.Builder()        .delay(1)        .listener(createCrashlyticsListener())        .pinningInfo(createPinningInfoProvider())        .build();    Fabric.with(this, crashlytics);

对于 Fabric SDK,我们需要一个 API key 作为连接我们网络服务器的验证密钥。这是我们要开发人员处理的事情,但需要尽量减少所需的工作量或者说繁琐度。我们的标准方法是:通过我们的构建插件提供的方式,并将其注入到清单(manifest)文件中。这里是一个例子,使用 metadata  在清单文件中插入数据:

<manifest package="con.example.SDK">    <application>      <meta-data android:value="01235813213455"        android:name="com.fabric.ApiKey" />    </application>  </manifest>

当 Fabric 在初始化的时候,我们可以通过 package manager 获取到我们插入在清单文件中的 API key 并且继续后续工作。另外,我们可以允许其他的方法来管理这个 API key 的值,对于开源项目这么做可能会更好(保护 key 的值,因为有时我们对项目进行开源,但 key 不想开源)。例如,您可以创建一个属性文件,然后我们将在运行时读取该目录文件内容。

易用性的表征(5:05)

除了我刚才提到的实施细节,我们喜欢在设计 API 时考虑这些特点:第一个是 直觉 。如果一个接口调用的行为恰好是开发人员预期的方式,而无需参考文档。

我们发现,在你的 SDK API 中使用 一致 的命名,也是有助于使用者理解。使用平常的表达语言来命名你的方法,以及类似的设计模式。并且遵循各个平台约定俗成的命名规则,比如 iOS 和 Android 平台,它们各有不同的命名规则。

最后,如果 API 很难被误用 ,将可以防止一些错误的发生。验证输入的参数,和书写明确的文档,将使得开发者在使用的时候,能够有信心和避免错误。也会带来一个更愉快的体验。

直观性(5:56)

让我们看一个反直觉的例子:

URL url1 = new URL("http://foo.example.com");  URL url2 = new URL("http://example.com");    url1.equals(url2)

这一个感觉,将影响很深,也是 API 中最难的部分。当使用一个伟大的 API 时,我们可以猜测它是如何表现的。在这个例子里,我们将期望 equals 执行某种标准化的字符串比较。

但是实际上反直觉的是,equals 代表如果这两个 URL 解析到相同的IP地址,在 Java 中的实现,将返回 true,这里的原因是这个 API 的实现十分有趣:它发射同步的 DNS 请求。谁会想到?阻塞调用线程是一个意外行为的例子,在 API 中应该是非常明确指出的。

一致性(6:38)

举一个例子,Fabric 和 Crashlytics 的初始化方式便都是一致的。在初始化 Fabric 或 Crashlytics,两个不同的二进制依赖库文件,正如我们之前看到的我们允许它们使用同一模式建造。用户可以使用无参数构造函数,或定义辅助方法来设置默认值,另外,这两者都提供了一个可用于重定义对象的生成器(builder)。

防止误用(6:59)

最后该讲到如何防止误用了。例如,从 Fabric Builder 的构造函数我们可以得知,Context 对象是必须的,而其它一些 setter 是可选的。一旦我们在构建的阶段中创建实例,这些可选参数也就一并被初始化。

这样设计的话,开发人员使用 API 将不能不提供 context,但可以使用其它 setters 在另外的闲暇时间。我们相信这样就很难被误用了。

怎么设计 API(7:30)

我们如何才能设计出高品质的 API 呢?让我们来看看我们的设计流程。设计 API 是很难的,它通常不只是一个工程师独自坐在一个黑暗的房间,决定该是什么样子,它需要整个团队付出大量的工作。

我们在 Fabric 的 API 设计上第一个重点就着眼于我们将支持的几个平台。我们创建一个设计文档之前,任何实施工作都是这样做的,进行讨论在这些平台上,不同的方法的优点和缺点。

有一句话我很喜欢:一个 API 就像一个婴儿。他们很有趣,但他们需要18年的支持。任何 API 我们都必须要长期地支持,所以我们要让大家感觉到,我们正走在正确的路上,才能才久坚持支持下去。

最后,即使我们可以让 iOS 或者 Android 开发中愉快地使用我们的 API 了,我们还需要建立相关的平台,首要的就是让开发者们感到最舒服的。

API 稳定性(8:26)

现在我们已经设计了一些很容易使用的东西,让我们来讨论一下我们如何能获得开发者的信任,相信这是非常重要的。因此,确保软件开发工具包是可靠的,他们不影响应用程序本身的稳定性。大家都知道,相比开发应用程序,开发一个 SDK 需要更高的稳定性要求。让我们来看看如果产生了一个错误将会有什么影响。

如果一个应用程序有一个关键的错误,阻碍了它的用户使用,它可能仅仅需要发送一个新版应用程序给顾客进行更新即可。而如果是我们 SDK 发现了一个漏洞,我们很快修复它,它可能还需要一个月才能到达你的用户,在此期间,你的用户就会有很不好的体验了。

显然,如果一个 SDK 有一个严重的 bug,它的修复更新到达时间要长得多。这可能需要几个月,用你的SDK应用程序的用户才能得到错误修正。应用程序开发人员可能需要数周才能注意或升级您的 SDK 版本,并进行修复、测试 bug。所以说确保一个 SDK 的稳定性是我们的最高优先事项之一。

如何确保稳定?(9:36)

作为开发人员我们可以做什么,以确保尽可能高的稳定性?有一些事情是我们开发过程中的关键。首先,代码审查是非常重要的,必须得认真对待它们。然后,通过不断地问自己“这个代码有什么问题吗?”我们可以这样试着去问自己,以达到尽可能的防守。

如果能够自动获得一些基本的正确性保证,也可以在早期帮助捕捉错误,所以单元测试是非常有用的。

另一方面,人们经常忽略的是:在用户使用初次使用进行测试时候,使它能够运行你的一些 SDK 代码,这样做他们可以在你的 SDK 集成时进行捕捉 bug。

最后,持续整合(译者注:维基百科词条 - 持续整合 )和”吃你自家的狗粮”(译者注:维基百科词条 - Eating your own dog food )也都可以作为你的保护层,可能有助于早期快速识别问题。

使 SDK 具备可测试性和可模拟性(10:16)

有一些技巧可以让你的 SDK 具备更好的可测试性。其中,为了测试,有时我们需要进行模拟,模拟(mock)类作为真实类的仿制类,它没有真实操作,并且允许被重写调用和验证方式。

通过避免静态方法,您可以允许在模拟实例上进行操作任何方法的调用。如果您将使用静态方法,需要确保它可以被隔离,并且您将提供所有的依赖关系,并且没有基于任何状态。

许多 mocking libraries 对于 final 的类也会产生许多问题 ,所以要考虑你的类扩展。在你的模拟类中不应该存在 public 属性,所以需要被访问的一切都应该通过一个访问的方法来运行。

在你的 API 中使用接口。如果您的输入点使用接口,设置类来测试将更容易。该接口允许开发人员进行重写的行为,比如契合模拟服务器或在内存中存储,来替代真实场景真实存储的开销。

最后,需要考虑到测试人员不需要构造多个层次深度的模拟。这个鼓励测试的原则应该被写入你的指引文档,并提供更稳定的测试框架。

测试用例(11:39)

有一些 class 很难被模拟,比如 final 类型的,它将创建它自己的依赖关系,并且是一个基于状态的单例。这在 Java 中是很常见的,虽然它通常是一个反模式。这使得它在隔离测试中非常具有挑战性。那么,我们能做什么来解决它?其实只要有一些小修改,我们可以使这些难解的点变得更容易测试一些。

public class Tweeter {    private Network network;      private public Tweeter(Network network) {      this.network = network;    }    public List<Tweet> getTweets() {      return getNetwork.getTweets();    }  }

与其把它作为一个单例,为什么不把它做为一个实例?开发者可以自己去重用它,缓存,或者做其它的事情。删除在 class 中的 final Mockito 或其他框架就可以模拟它了,同时也能让 SDK 在初始化、构造时候管理它的依赖。依赖注入不仅仅是一个框架,它还是一个帮助组织代码的设计模式,使得代码更具有模块性和可测试性。

优美的降解(12:29)

开发者经常是容易不耐烦的,所以有一些错误越尽早抛出就越好。如果你一直在使用 Gradle 你应该会明白我的意思,一些错误如果在 build 期间不能通过总是好于 build 完成之后5分钟才出现错误。你应该 把一些可以预期的异常抛出 ,以便于开发者能够尽快知道这些异常,比如在这种情况下,开发者试图设置一个 null 的 logger 到我们的 builder 里,我们得马上抛出一个异常,这样他们就可以很快知道并解决他们的错误。

然而,你得保证你的 SDK 在生产环境中绝不会出错,让你的代码持续运行在他们的应用中,是你保证开发者们信心的唯一方法。他们的应用程序往往是他们生计的依赖,所以他们不会喜欢去赌着使用一个经常崩溃的库。所以当他们在调试过程中出现问题的时候,你可以提供额外的信息,写清楚这个 Exception,但要隐藏在生产过程中可能出现的问题,这样允许他们的应用程序的其余部分继续运行。你的 SDK 的出现问题可能对你来说是一个大问题,但并不是世界末日。

作为一个开发者,你使用 SDK,你的应用程序应该增加价值,而最糟糕的事情就是你引进了某物反而使得原本多价值降低了甚至完全破坏用户体验。开发者们,包括我自己,不需要任何人来帮助我们写一个糟糕的 App。

轻量(13:43)

除了稳定,用户不太可能下载大的应用程序,这意味着安装包的大小是一个关键内容。下载软件产生的流量需要用户去付钱,所以即使你的应用是完全免费的,用户也得为下载它付出流量。在许多新兴市场,因为下载速度太慢,所以很多用户不爱下载大型应用程序;在某些市场,用户主动选择更新的应用仅仅基于更新日志和添加新的特性是否值得,因为他们需要支付每千字节流量费用。

让我们来讨论一些 Fabric 用于保持轻量的技术吧。有一些伟大的第三方库,可以真正给予贡献于你的应用程序,但当涉及到 size 规模和影响时,他们会他们显得不自由。例如,图像加载方面有各种不同体积大小的图片加载库。其中 Fresco,是比其他任何一个第三方库都还大量级的一个库。然而,它对于旧设备有更好的支持,加载速度快而且内存友好,并支持渐进式JPEG。

作为一个 SDK 你应该努力平衡你的尺寸与功能。因此,要注意引入第三方库,以确保它们只满足所需的内容。

使用开源库有很大的优势,因为这些库往往经过很多的测试了,大家都使用得很好,并且他们有定期向他们提供更新的社区。这通常提供了一个更好的方案。在我们的 推ter SDK,我们利用 Square 的 RetroFit 这个库作为一个依赖来简化我们的 API,而且也能使我们提供更好的可扩展性,这是值得的。

报告二进制文件(binary)的大小(15:22)

task reportSdkFootprint << {    def sdkProject = project(':clients:SdkProject')    def nonSdkProject = project(':clients:NonSdkProject')    def footprint = getSizeDifferent(      new File("$sdkProject.buildDir}.../Sdk.apk"),      new File("${nonSdkProject.buildDir}.../onSdk.apk"))    println footprint  }

这里有一个很好的减少你的库大小的方法,就是如果你每天都称量自己的体重,你就能够减肥。我们采取这种方法来关注和减小我们的 SDK 大小。我们积极地监控每个 build,使我们能够衡量的真实影响开发者的 APK 大小情况。没有硬性和快速的规则来解决大小增加,但一直关注它显然是有帮助的。

Dalvik 方法数量限制(15:52)

很多人应该都会遇到过 Dalvik 65K 的限制吧?不过你们可能不熟悉这个错误的具体原因,对于方法的调用,可以通过在 Android 一个 DEX 文件,而它的引用的数量有限。问题的关键是,DEX 的工具,在编译时,试图把所有的方法引用到一定的空间中,但引用数目大于空间所能容纳的数目,导致分配失败。

现在你可以使用 multidex 来解决这个问题,但这增加了在较旧的设备上应用程序的加载时间,它的初始化时间显著增加,它并不适合一些较新的设备用户以外的人。它甚至某些流行的三星设备上会导致出错,并导致应用程序崩溃。这些特定的设备有数以百万计的设备,在欧洲和亚洲都是很流行的,所以说这个解决方案真是迫不得已,能不触及尽量不要触及。

但如果开发者遇到这个问题,在他们去使用 multidex 或类似的东西之前,他们经常决定审核他们的第三方库,选择是否可以减少应用程序方法数量的库。所以我们的目标和建议是尽可能地使你的库模块化和精益。

我们用一个伟大的库被称为 dex-method-counts 在 Gradle 中,它包装了一些Android 构建工具对每个第三方库的方法数量并给出了详细的数据分析。这让我们快速洞察到我们的库大小和我们的依赖的大小。

模块化(17:31)

我们想要模块化开发人员需要的特定功能。我们这样做是通过指定一个树的传递依赖关系。所以,我们有两个例子展示如何初始化 Fabric:

容易整合的:

Fabric.with(this, new 推ter());

更多控制的:

Fabric.with(this, new TweetUi(), new TweetComposer());

第一个例子让你马上开始,其次是一个拥有更多定制的版本,在这里你可以选择需要的特定组件,然后它们才生效。

让我们的 SDK 尽可能小,我们着眼于模块化来设计我们的架构。就像我们讨论过的,在 Android 上二进制文件的大小和方法计数是非常重要的,所以这使我们能够尽可能高效。因为如果我们利用 AAR(标准的通过 Maven 提供标准的库),我们可以使用分解依赖来满足我们的需求。

在架构 推ter SDK 堆栈,所有的一切都建立在 Fabric 上,它提供共享的 common 代码给所有我们的 SDKs. 我们建立了 推terCore 层,它提供了登录和 API 客户端和其他一些核心功能。

然后,我们才有基于特征的 SDKs,像我们的 Tweets,我们的 composer,我们的短信登录基础设施;这些特点都可能是你使用我们 SDK 的理由。最后,我们提供了一个接口,通过传递依赖关系将其包装起来。这允许应用程序开发人员根据他们需要的层次结构选择组件。

压缩数据(19:04)

除了注意 SDK 本身的大小之外,我们建议你在“压线”之前进行压缩。我们可以明显看到,不同格式之间有压缩大小差异,如 XML 和 JSON,一起使用jzip压缩格式,或二进制格式 Protobuf 。有些人可能不熟悉 Protobuf,它是一个二进制的压缩格式,在服务器和客户端接口使用一致的预期协议,使得不需要将所有字段都进行传输。它可以非常高效地进行序列化和反序列化。

批量处理请求(19:36)

有效地发射是避免消耗过多电源功率的关键。Android 有三种典型的能量状态:全功率、低功耗、空闲状态或称待机状态。

在高功耗的时候进行统一的网络传输,比在空闲的时候断断续续进行多次传输来得节约时间和节约电源。

对于在典型的3G设备上每一个数据连接,网络接收模块会产生约20秒的活跃时间。这意味着如果你每分钟有三个网络连接,我们将会保持这个网络接收模块不断地处于激活。通过批处理这三个连接,我们可以在这里减少20秒的启用速率,以及至少40秒的待机或空闲时间。

不过对于这个方面,有一个例子,说的是天真统计分析 SDK,它们有时会不停地 ping 你的服务器,大约20秒一次,仅仅是为了告知服务器你的应用当前处于前台。这么做的后果就是会造成网络接收模块一直处于激活状态,并且把电源耗尽又没有传输什么实质上的数据内容。

异步任务(20:54)

保持 Fabric 的轻量部分方式是,我们应该清楚地知道何时在主线程做的工作何时在后台线程工作,我们建议在你的应用程序启动时候初始 Fabric,因此它的初始化将在主线程,我们从我们周围很多顾客那里听到大家都是比较关注启动时间的。

为了缓解这一点,我们做了非常有限的一些同步工作,然后立马返回到开发者的 application,同时在后台继续做一些毕竟耗时的运行工作,以保持您的应用程序下次能够快速启动。

有些事情需要用同步做的一个例子:如果你使用在使用我们的 Crashlytics ,你得立即使用它的 crash handler,因为一旦崩溃异常发生在异步初始化 crash handler 完成之前,就会捕捉不到这个异常。

灵活性(22:36)

一个 SDK 的开发者没有像应用开发者那么多的选择权。你不能选择你的设备,API level,或客户。你需要支持更大范围的设备,应用程序开发者并不局限在选择你的SDK,所以提供最大程度的灵活性是很重要的。

建立工具支持(22:53)

灵活性的一个体现是,可以让开发者选择不同的依赖管理器或者构件工具来引入或集成你的库。

我们提供一些主要的开发工具插件支持,包括 Gradle, Maven, 和 Ant. 我们还为通常的 IDE 提供 GUI 插件,以及一个帮助开发 Mac 和 iOS app 的应用。尽管我们这是主要在讲 Android 方面开发 SDK 的内容,但我还是忍不住想告诉大家一个好消息就是我们最近完成了令人兴奋的 CocoaPods 支持,这将非常方便于 iOS 开发者使用我们的 SDK.

尽量选择兼容最低的系统版本(23:26)

灵活性的关键是了解您的 SDK 用户的需求。然后做出需要支持的最低系统版本的决定。我们很希望我们的 SDK 能够尽可能支持更多的系统设备。对于这一点,降低支持最低操作系统版本是很有必要尽力去做的。

但另一方面,兼容低版本也是要付出代价的。并没有什么直接的法则能够告诉我们如何才能在繁琐度和更好的兼容性上确定平衡。支持旧的操作系统版本,通常意味着不利用更加好用的新接口,同时还要面对一些旧版本存在的问题。初次之后,你还要花费更多精力去测试你的代码之正确性。

一般来说,SDK 会比应用软件在支持的最低系统版本的选择上,更近保守。例如,Crashlytics 提供了 Android 2.2 的支持,因为这是一个还基本实用的 Android 应用程序。而不是像一些应用程序,它们仅支持某一操作系统的版本,它们关注用户数量而去选择最佳系统版本。

另一个重要的部分是能够检测出你正在运行的 Android 版本,所以你才能知道,哪些是可以调用的方法。通常,SDK 支持更老版本的 Android SDK。这对我们来说是非常重要的,因为我们想提供最大限度的设备支持。

Android Manifests(24:57)

在 Android 系统上,运行时兼容性是灵活性的重要组成部分。Android 开发者都知道,一个应用程序的元数据(metadata)在 manifest 文件中声明,一个 apk 一个 manifest 文件。

此外,每一个你包含进来的 aar 都会带一个 Manifest 文件,只是在 build 的过程中,Android 工具会自动帮它们合并到一个 Manifest 里面。这意味着,如果一个 AAR 的 Manifest 会影响最终 Manifest 的生成,比如新增一个权限声明,这对于开发者来说是很难注意到的。

权限(25:37)

权限是任何 Android 应用程序的关键。在 Crashlytics 我们利用 wifi state permission 可以更好地管理上传崩溃日志。在 Android 的棉花糖系统上,这是属于正常水平的权限,这意味着它还需要在 manifest 文件中声明并同意。然而,我们没有在我们的清单中声明这个权限,所以,如果使用了我们 SDK 的开发者想要上传崩溃日志,就必须得在他的清单文件中声明上这条权限。如果开发者声明了那条权限,我们在下面的代码中就能够知道拥有这个权限了。

protected boolean canCheckNetworkState(Context context) {    String permission = Manifest.permission.ACCESS_NETWORK_STATE;    int result = context.checkCallingOrSelfPermission(permission);      return (result == PackageManager.PERMISSION_GRANTED);  }

我们只需要检查上下文对象中的权限,以判断是否已授予该权限。如果没有获得该权限也没办法了,我们不能为了权限而可能影响到安全异常。一个 SDK 可以在运行时检查是否被允许使用某权限,如果可用再调用相应的 API.

很多时候我们都需要回退机制,但是在这种情况下,如果我们不知道WiFi或互联网的状态,我们必须假定它总是连接尝试,让超时发生。

可选的功能(26:42)

有很多 Android 设备的存在,同时有很多各式各样的特性或者功能可能其它机器设备并没有,比如有的有 Kindle Fire 而有的没有,有的设备甚至还没有摄像头。

如果你正在构建一个基于相机的 SDK,你会在清单文件中声明使用相机。这就要求商店不要将应用程序展现给一个没有摄像头的设备。我鼓励你,把这个功能列为可选的,并允许你的库在运行时检测和修改它的行为。

在运行时检测硬件功能非常简单。您只需要查询 package manager 该特定功能是否存在即可。这样以后你的应用程序可以确定哪些功能可以使用了。以我们的相机库示例,您可能还允许用户浏览照片和上传照片,只是他们不能够在没有相机的设备上拍照就是了。

Classpath 检测(27:41)

有很多很好的第三方库可以节约你的开发时间以及帮助你开发更好的 SDK. 当我们提供 SDK 给用户,可能需要检测用户是否有引入这些可选的第三方库,比如 RxJava 或者 Square 公司的 OkHttp ,如果有,我们就可以利用它们提供相应的支持。但你没必要把这些包括到你的 SDK 当中,因为我们前面说了,保持轻量是最好的。

private boolean hasOkHttpOnClasspath() {    try {      Class.forName("com.squareup.okhttp.OkHttpClient");      return true;    } catch (ClassNotFoundException e) { }    return false;  }

在这个例子中,你可以通过 class path 来检测 OkHttp 是否存在。你可以告诉用户可以引入这个第三方库进行更好的支持,但没有必要主动把它们包括到你的 SDK 当中。如果有,你可以利用,如果没有,你也可以有你的另外选择。

可扩展类和接口(29:16)

除了运行时检测,我们不能满足每个开发者的需求。我们的 推ter SDK 提供了易于使用和流行的一些特性让用户去发现。我们预先就会准备好,帮你简化签名操作,我们使用持续的 token,签署了所有的输出请求。

但是,如果你想使用一个我们 SDK 目前没有提供的 推ter API 功能怎么办呢?如果是这样,你可以继承 推ter API client,并且提供你的 retroift 接口,我们可以接受它并帮你进行签名。

正如我们所知道的,开发人员有很多工具,有很多的选择,他们需要在他们的代码中保持灵活性。这是很现实的。我们提供可扩展的接口,使用 Fabric,使开发人员可以利用扩展他们想要的功能接口。

这里还有一个例子是日志记录。有许多不同的库来使用,我们提供了日志接口,以便它可以在 Fabric 开始前提供实现,然后我们将尊重并使用开发人员的日志记录需求。

但如果开发者选择不配置他个性的日志内容,我们提供一个健全的默认日志,便是标准的 Android Logger.

另外,对于 Java 之外的方面,我们也支持让用户自定义他们想要的界面风格,比如开发者可以修改 color 的值,这些值将被应用于 推terUI SDK.

这里有一个需要注意的点,因为 dex 合并点时候不支持在 name 中使用空格符号,所以不要使用类似”background color”这样的 name.

Callbacks(31:09)

制定灵活代码的一部分,就是允许开发人员选择监听一些事件发生并获得通知。在我们的 builder,我们可以设置同步或异步任务结束的时候进行回调,也可以设置如果出错了可以得到回调通知。它允许开发者根据自己的状态和所获取的信息进行定制决策。

良好支持的 SDK(31:37)

当你完成了你的 SDK 开发的时候,并不代表着你的 SDK 真正完成了,你还需要有很多开发之外的内容要做,要建立开发者交流的社区,还要有 Apple 文档、Java 文档,以及 README 文档,另外还有很重要的就是要有使用你的库的示例教程。

添加注释给你的所有 public 的内容,以及顺带说明一些使用案例。

发布简明的 示例(sample)代码 ,让使用的人可以遵循着你的代码进行初次尝试。但切记不要把你的示例代码写得太复杂或者在无关紧要的内容上纠结太多,不然会导致用户花费更多时间去学习你的示例代码,并且使用到他们的项目中时候,产生了很多疑问或者 bug.

记得思考一些你想废弃的旧版本和方法。你们有多少人没有回头看旧的代码?除了去重写重构这些代码和方法,你可以需要对于旧的不要的代码进行 废弃 注解提醒。

尊敬版本更新日志,这是一个你和开发者通过你的 SDK 沟通的方式,你可以去知道他们需要什么,而他们从你们的 SDK 更新日志中获得他们是否需要你这个新版本的信息,如果值得,开发者们就会决定更新到目标的这一个版本。

最后,使用开发者预期的、常用的 交流社区或称交流方式 。如果这是一个开源库,应该是使用 GitHub 的 Issues,关注 Stack Overflow,Freenode.

虽然我们认为所有的这些观点都非常重要,但它们确实是伟大的库/SDK建设中的冰山一角。我们希望你喜欢我们作为内部开发人员在 Fabric SDK 开发中的思考,那你觉得有用的建议和特性,或许就将出现在你现在手头或者将来要开发的 SDK 身上。谢谢!

See the discussion on Hacker News .

Sign up to be notified of new videos — we won’t email you for any other reason, ever.

</div> </span></code></code></code></code></code></code></code></code></code></code>