如何用 Kotlin Coroutines 和 Architecture Components 进行 Android 开发?

  • Marek Langiewicz
  • 运和凭

2017 年 10 月 19 日

话题:Android语言 & 开发架构

我们对这种新的可能性感到兴奋无比,但却很难真的投入大量时间重新编写原有应用以充分发挥新型编程风格带来的种种潜力。然而,如果我们从全新项目起步,结果又会如何?我们能够向其中引入哪些突破性的思维?哪些解决方案可以带来理想的稳定性?我们是否应该广泛使用 RxJava,并将响应式至上思路作为应用的结构指导?

Cycle.js 库 (来自André Staltz) 对于响应式至上思维作出了很好的诠释: Cycle.js — Streams

Rx 具备良好的可组合特性,因此拥有巨大的发展潜力,然而,其与常规的面向对象编程风格又存在着显著区别。事实上,对于毫无 RxJava 使用经验的开发人员而言,其确实存在着难以理解的问题。

在开始新项目之前,我们面临着更多需要回答的问题。举例来说:

  • 我们应该利用 Kotlin 替代 Java 吗?(简单来讲,答案是肯定的。)

  • 我们应该使用实验性的Kotlin Coroutines吗?(其带来了全新的编程风格。)

  • 我们是否该使用谷歌提供的实验性库:Android Architecture Components?

要为这些问题找到答案,我们需要首先创建一款小小应用,从而做出明智的决定。而这正是本文的内容所在——依托于整个流程得出有用的见解。如果大家希望了解更多细节,咱们马上进入主题。

关于这款应用

这次实验的目标在于创建一款应用,其能够下载用户所选定城市的相关天气数据,并将预报结果通过图形化图表的形式显示出来(再配上一些酷炫的动画)。要求很简单,但其中已经囊括了各类 Android 项目中会涉及到的大多数功能。

事实证明,coroutinesarchtiecture components确实能够良好协作,并给我们带来能够顺利解决大量问题的整洁应用架构。

Cortoutines 帮助我们以自然简洁的方式表达思路。如果大家希望代码能够逐行体现您所希望执行的确切逻辑(即使您可能也需要在其间进行一些异步调用),suspendable 函数也能很好地完成任务。

另外需要强调的是:无需在回调之间往来跳转。在本示例应用当中,coroutines 还彻底消除了对 RxJava 的依赖性。配合 suspendable 点的各函数在阅读及理解难度方面远低于部分 RxJava 运算符链——这些链可以快速实现函数化转换。

话虽如此,但我个人认为并不是在每个用例中都能将 RxJava 替换为 coroutines。我们发现,observalbes 就是一类无法被逐一映射为 suspendable 函数的表达类型。特别是如果 observable 运算符链允许多个事件从其中流经,且其中每个 suspendable 点只能在每次调用时恢复一次,则无法实现一对一映射。

回到我们这款天气应用中来:您可以查看其运作效果——但请注意,我并不是设计师,所以相关成果可能比较简陋。

图表动画显示您能够轻松利用简单的 cortoutine 以手动方式加以实现——其中不涉及任何 ObjectAnimators、Interpolators、Evaluators 或者 PropertyValuesHolders 等等。

最重要源代码片段已经展示如下。不过如果您希望查看完整项目,请参阅 GitHub

代码量并不大,相信大家能够轻松完成浏览。

我将从网络层开始介绍这款应用的具体结构。接下来,我会探讨业务逻辑(在MainModel.kt文件中),其几乎不受限于 Android 系统平台。最后,则是 UI 部分(这显然仅适用于 Android 系统)。

为了方便起见,我在这里为总体架构图添加了文本参考编号。我会特别关注其中绿色元素——即suspendable函数与actors(一个 actor 实际上就是一种非常实用的coroutine builder)。

总体来讲,actor 模型属于一种并行计算数学模型——我将在下一篇博文中就此展开详细探讨。

01 天气服务

这项服务负责从Open Weather Map REST API处下载特定城市的天气预报数据。

在这里,我使用了一套来自 Square 的简单但强大的库——Retrofit。我猜如今的 Android 开发人员不会没听说过它,但对于那些尚未接触过的朋友,这里再解释几句:这是一套非常流行的 Andoird 平台 HTTP 客户端。其能够面向POJO执行网络调用与响应解析。这里我们直接使用典型的 Retrofit 配置。我还插入了Moshi转换器将 JSON 响应转换为数据类。

这里需要强调的一点是,我将由 Retrofit 生成的函数类型返回至另一新函数: Call

我利用 Call.enqueue(回调)以面向 Open Weather Map 实际执行调用。这里我并没有使用由 Retrofit 提供的任何调用适配工具,这是为了保证自己能够将 Call 对象打包在 suspendable 函数当中。

02 实用工具

从这里,我们正式迈入了全新的 coroutines 世界:我们希望创建一条打包有 Call 对象的suspendable 函数

要完成这部分内容,您需要掌握 coroutines 的一部分基础知识。如果您尚不了解,请参阅《Coroutines 指南》的第一章内容(由Roman Elizarov撰写)。

这将是一条扩展函数:suspend fun Call.await(),其负责调用 Call.enqueue(…)(以实际执行网络调用),而后调用 suspends,再然后是resumes(当响应返回时)。

要将任意异步计算转化为suspendable 函数,我们需要使用 The Kotlin 标准库当中的suspendCoroutine函数。其能够为我们提供一个Continuation对象,这属于一类通用回调。我们只需要在对新的 suspendable 函数进行恢复时(通常是在出现异常的情况下),调用其resume方法(或者resumeWithException方法)即可。

下一步是使用我们的新suspend fun Call.await() 函数,其负责将由 Retrofit 生成的异步函数转换为便捷的suspendable 函数

03 Repository

Repository 对象属于我们在应用中需要显示的数据(图表)的实际来源。

在这里,我们会使用一些由suspend fun Call.await() 扩展生成的专用suspendable 函数,用以实现天气服务功能。如此一来,其返回的将全部为可立即使用的 Forecast 预报等数据,而非 Call。接下来,我们在一条公共suspendable 函数中使用这些数据:suspend fun getCityCharts(city:String):List。其能够将来自 API 的数据转化为可随时显示的图表清单。这里我使用 List 上的部分定制化扩展属性以将数据实际转换至 List。需要强调的是,只有suspendable 函数能够调用其它suspendable 函数

为了简单起见,我们这里对此 appid 进行硬编码。如果大家希望对应用进行测试,请点击此处生成新的 appid——这是因为如果此硬编码 appid 被多人频繁使用,则会自动被屏蔽 24 小时。

在下一步中,我们将创建主应用模型(采用 Android ViewModel架构组件),其利用一个actor(coroutine builder)以实现应用逻辑。

04 模型

在这款应用中,我们只使用一套简单的模型: MainModel : ViewModel,且仅供一种活动使用: MainActivity

这个类代表着应用本身。其将由我们的活动(实际上是由 Android 系统的ViewModelProvider)进行实例化,但能够在配置变更(例如屏幕旋转)后继续存在,且保证新的活动实例仍拥有相同的模型实例。我们完全不必担心活动生命周期问题。相较于实现与各方法(onCreate、onDestroy 等)相关的活动生命周期,这里我们只拥有一项 onCleared() 方法,其会在用户退出此应用时进行调用。

更确切地讲,当活动 finished 时,onCleared方法将得到调用。

尽管并没有与活动生命周期紧密结合,我们仍然需要采取某种方式以发布应用模型的当前状态,从而将其显示在某些特定位置(在活动中)。在这方面,LiveData 能够发挥良好作用。

LiveData类似于对RxJava BehaviorSubject的二次创造……其拥有一项observable可变值。其最重要的差异体现在订阅方式以及随后在MainActivity当中进行查看的方式上。

不过 LiveData 也不具备像 Observable 那样强大的可组合运算符。大家可以查看 LiveData 提供的部分简单转换信息。

不过 LiveData 也不具备像 Observable 那样强大的可组合运算符。大家可以点击此处查看 LiveData 提供的部分简单转换信息

另一项区别在于,LiveData 仅适用于 Android,但 RxJava 主题则更具普适性 ; 因此我们可以利用常规非 Android JUnit 对后者进行轻松测 试。

最后一项不同是,LiveData 具有“生命周期意识”——我将在下一篇介绍MainActivity类的博文当中对此作出详尽探讨。

在本示例中,我们选择使用MutableLiveData:各LiveData对象允许直接向其中随意添加新值。应用 state 由 4 个 LiveData 对象负责体现:city、charts、loading 以及 message。其中最重要的自然是charts:LiveData>对象,其代表着需要显示的当前图表清单。

所有会引发应用状态变化以及响应用户操作的任务皆由ACTOR负责执行。

Actors 非常强大,我将在下一篇博文中进行具体解释。

总结

我们已经为主 actor 作好了一切筹备。如果大家认真查看actor代码内容,那么即使您并不了解coroutines或者actors理论,应该也能够看懂其工作原理。虽然只有寥寥数行,但其中实际包含着本款应用的全部重要业务逻辑。最值得强调的就是我们调用 suspendable 函数的位置(由绿线加灰色箭头所指定的部分)。第一个位置为suspendable 点,即随用户操作进行迭代的部分 ; 第二个位置则为网络调用。归功于coroutines,这里的内容更近似于同步阻塞代码,但实际上却并不会拥塞该线程。

请大家期待我的下一篇博文,届时我将详细讲解关于actorschannels的一切。

原文链接:https://blog.elpassion.com/create-a-clean-code-app-with-kotlin-coroutines-and-android-architecture-components-f533b04b5431


感谢覃云对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

Android语言 & 开发架构