开发正在快速发展,众多开发人员与企业都在努力解决各类常见问题,并打造出一系列能够彻底改变我们应用结构的工具或库。
我们对这种新的可能性感到兴奋无比,但却很难真的投入大量时间重新编写原有应用以充分发挥新型编程风格带来的种种潜力。然而,如果我们从全新项目起步,结果又会如何?我们能够向其中引入哪些突破性的思维?哪些解决方案可以带来理想的稳定性?我们是否应该广泛使用 RxJava,并将 响应式至上思路 作为应用的结构指导?
Cycle.js 库 (来自 André Staltz) 对于响应式至上思维作出了很好的诠释: Cycle.js — Streams。
链接:
https://medium.com/@andrestaltz
https://cycle.js.org/streams.html
Rx 具备良好的可组合特性,因此拥有巨大的发展潜力 ; 然而,其与常规的面向对象编程风格又存在着显著区别。事实上,对于毫无 RxJava 使用经验的开发人员而言,其确实存在着难以理解的问题。
在开始新项目之前,我们面临着更多需要回答的问题。举例来说:
我们应该利用 Kotlin 替代 Java 吗?(简单来讲,答案是 肯定的。)
我们应该使用实验性的 Kotlin Coroutines 吗?(其带来了全新的编程风格。)
我们是否该使用谷歌提供的实验性库:Android Architecture Components?
要为这些问题找到答案,我们需要首先创建一款小小应用,从而做出明智的决定。而这正是本文的内容所在——依托于整个流程得出有用的见解。如果大家希望了解更多细节,咱们马上进入主题。
这次实验的目标在于创建一款应用,其能够下载用户所选定城市的相关天气数据,并将预报结果通过图形化图表的形式显示出来(再配上一些酷炫的动画)。要求很简单,但其中已经囊括了各类 Android 项目中会涉及到的大多数功能。
事实证明,coroutines 与 archtiecture components 确实能够良好协作,并给我们带来能够顺利解决大量问题的整洁应用架构。
Cortoutines 帮助我们以自然简洁的方式表达思路。如果大家希望代码能够逐行体现您所希望执行的确切逻辑(即使您可能也需要在其间进行一些异步调用),suspendable 函数也能很好地完成任务。
另外需要强调的是:无需在回调之间往来跳转。在本示例应用当中,coroutines 还彻底消除了对 RxJava 的依赖性。配合 suspendable 点的各函数在阅读及理解难度方面远低于部分 RxJava 运算符链——这些链可以快速实现函数化转换。
话虽如此,但我个人认为并不是在每个用例中都能将 RxJava 替换为 coroutines。我们发现,observalbes 就是一类无法被逐一映射为 suspendable 函数的表达类型。特别是如果 observable 运算符链允许多个事件从其中流经,且其中每个 suspendable 点只能在每次调用时恢复一次,则无法实现一对一映射。
回到我们这款天气应用中来:您可以查看其运作效果——但请注意,我并不是设计师,所以相关成果可能比较简陋。图表动画显示您能够轻松利用简单的 cortoutine 以手动方式加以实现——其中不涉及任何 ObjectAnimators、Interpolators、Evaluators 或者 PropertyValuesHolders 等等。
最重要源代码片段已经展示如下。不过如果您希望查看完整项目,请参阅 GitHub。
GitHub:
https://github.com/elpassion/crweather
代码量并不大,相信大家能够轻松完成浏览。
我将从网络层开始介绍这款应用的具体结构。接下来,我会探讨业务逻辑(在 MainModel.kt 文件中),其几乎不受限于 Android 系统平台。最后,则是 UI 部分(这显然仅适用于 Android 系统)。
MainModel.kt :
https://github.com/elpassion/crweather/blob/master/app/src/main/java/com/elpassion/crweather/MainModel.kt
为了方便起见,我在这里为总体架构图添加了文本参考编号。我会特别关注其中 绿色 元素——即 suspendable 函数与 actors(一个 actor 实际上就是一种非常实用的 coroutine builder)。
总体来讲,actor 模型属于一种并行计算数学模型——我将在下一篇博文中就此展开详细探讨。
这项服务负责从 Open Weather Map REST API 处下载特定城市的天气预报数据。
Open Weather Map REST API :
http://openweathermap.org/api
在这里,我使用了一套来自 Square 的简单但强大的库——Retrofit。我猜如今的 Android 开发人员不会没听说过它,但对于那些尚未接触过的朋友,这里再解释几句:这是一套非常流行的 Andoird 平台 HTTP 客户端。其能够面向 POJO 执行网络调用与响应解析。这里我们直接使用典型的 Retrofit 配置。我还插入了 Moshi 转换器将 JSON 响应转换为数据类。
Retrofit : http://square.github.io/retrofit/
POJO : https://en.wikipedia.org/wiki/Plain_old_Java_object
Moshi : https://github.com/square/retrofit/tree/master/retrofit-converters/moshi
这里需要强调的一点是,我将由 Retrofit 生成的函数类型返回至另一新函数: Call。
我利用 Call.enqueue(回调) 以面向 Open Weather Map 实际执行调用。这里我并没有使用由 Retrofit 提供的任何调用 适配工具,这是为了保证自己能够将 Call 对象打包在 suspendable 函数当中。
Call : https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/Call.java
Call.enqueue : https://github.com/square/retrofit/blob/b3ea768567e9e1fb1ba987bea021dbc0ead4acd4/retrofit/src/main/java/retrofit2/Call.java#L48
适配工具:
https://github.com/square/retrofit/tree/master/retrofit-adapters
从这里,我们正式迈入了 全新的 coroutines 世界:我们希望创建一条打包有 Call 对象的 suspendable 函数。
要完成这部分内容,您需要掌握 coroutines 的一部分基础知识。如果您尚不了解,请参阅 《Coroutines 指南》 的第一章内容(由 Roman Elizarov 撰写)。
《Coroutines 指南》 :
https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
这将是一条扩展函数:suspend fun Call
要将任意异步计算转化为 suspendable 函数,我们需要使用 The Kotlin 标准库当中的 suspendCoroutine 函数。其能够为我们提供一个 Continuation 对象,这属于一类通用回调。我们只需要在对新的 suspendable 函数进行恢复时(通常是在出现异常的情况下),调用其 resume 方法(或者 resumeWithException 方法)即可。
下一步是使用我们的新 suspend fun Call
suspendCoroutine 函数:
https://github.com/JetBrains/kotlin/blob/8f452ed0467e1239a7639b7ead3fb7bc5c1c4a52/libraries/stdlib/src/kotlin/coroutines/experimental/CoroutinesLibrary.kt#L89
Continuation 对象:
https://github.com/JetBrains/kotlin/blob/8fa8ba70558cfd610d91b1c6ba55c37967ac35c5/libraries/stdlib/src/kotlin/coroutines/experimental/Coroutines.kt#L23
resume 方法:
https://github.com/JetBrains/kotlin/blob/8fa8ba70558cfd610d91b1c6ba55c37967ac35c5/libraries/stdlib/src/kotlin/coroutines/experimental/Coroutines.kt#L32
resumeWithException 方法:
https://github.com/JetBrains/kotlin/blob/8fa8ba70558cfd610d91b1c6ba55c37967ac35c5/libraries/stdlib/src/kotlin/coroutines/experimental/Coroutines.kt#L38
Repository 对象属于我们在应用中需要显示的数据(图表)的实际来源。
在这里,我们会使用一些由 suspend fun Call
为了简单起见,我们这里对此 appid 进行硬编码。如果大家希望对应用进行测试,请点击此处生成新的 appid——这是因为如果此硬编码 appid 被多人频繁使用,则会自动被屏蔽 24 小时。
在下一步中,我们将创建主应用 模型(采用 Android ViewModel 架构组件),其利用一个 actor(coroutine builder) 以实现应用逻辑。
ViewModel :
https://developer.android.com/topic/libraries/architecture/viewmodel.html
在这款应用中,我们只使用一套简单的模型: MainModel : ViewModel,且仅供一种活动使用: MainActivity。
这个类代表着应用本身。其将由我们的活动(实际上是由 Android 系统的 ViewModelProvider)进行实例化,但能够在配置变更(例如屏幕旋转)后继续存在,且保证新的活动实例仍拥有相同的模型实例。我们完全不必担心活动生命周期问题。相较于实现与各方法(onCreate、onDestroy 等)相关的活动生命周期,这里我们只拥有一项 onCleared() 方法,其会在用户退出此应用时进行调用。更确切地讲,当活动 finished 时,onCleared 方法将得到调用。
ViewModelProvider:
https://developer.android.com/reference/android/arch/lifecycle/ViewModelProvider.html
更确切地讲,当活动 finished 时,onCleared 方法将得到调用。
尽管并没有与活动生命周期紧密结合,我们仍然需要采取某种方式以发布应用模型的当前状态,从而将其显示在某些特定位置(在活动中)。在这方面,LiveData 能够发挥良好作用。
LiveData 类似于对 RxJava BehaviorSubject 的二次创造……其拥有一项 observable 可变值。其最重要的差异体现在订阅方式以及随后在 MainActivity 当中进行查看的方式上。
LiveData:
https://developer.android.com/topic/libraries/architecture/livedata.html
RxJava:
https://github.com/ReactiveX/RxJava
BehaviorSubject :
https://github.com/ReactiveX/RxJava/wiki/Subject
MainActivity :
https://github.com/elpassion/crweather/blob/master/app/src/main/java/com/elpassion/crweather/MainActivity.kt
不过 LiveData 也不具备像 Observable 那样强大的可组合运算符。大家可以 查看 LiveData 提供的部分简单转换信息。
不过 LiveData 也不具备像 Observable 那样强大的可组合运算符。大家可以点击此处查看 LiveData 提供的部分简单 转换信息。
另一项区别在于,LiveData 仅适用于 Android,但 RxJava 主题则更具普适性 ; 因此我们可以利用常规非 Android JUnit 对后者进行轻松测试。
最后一项不同是,LiveData 具有“生命周期意识”——我将在下一篇介绍 MainActivity 类的博文当中对此作出详尽探讨。
在本示例中,我们选择使用 MutableLiveData:各 LiveData 对象允许直接向其中随意添加新值。应用 state 由 4 个 LiveData 对象负责体现:city、charts、loading 以及 message。其中最重要的自然是 charts:LiveData<list
所有会引发应用状态变化以及响应用户操作的任务皆由 ACTOR 负责执行。
Actors 非常强大,我将在下一篇博文中进行具体解释。
MutableLiveData :
https://developer.android.com/reference/android/arch/lifecycle/MutableLiveData.html
我们已经为主 actor 作好了一切筹备。如果大家认真查看 actor 代码内容,那么即使您并不了解 coroutines 或者 actors 理论,应该也能够看懂其工作原理。虽然只有寥寥数行,但其中实际包含着本款应用的全部重要业务逻辑。最值得强调的就是我们调用 suspendable 函数 的位置(由绿线加灰色箭头所指定的部分)。第一个位置为 suspendable 点,即随用户操作进行迭代的部分 ; 第二个位置则为网络调用。归功于 coroutines,这里的内容更近似于同步阻塞代码,但实际上却并不会拥塞该线程。
请大家期待我的下一篇博文,届时我将详细讲解关于 actors 与 channels 的一切。
原文链接:
https://blog.elpassion.com/create-a-clean-code-app-with-kotlin-coroutines-and-android-architecture-components-f533b04b5431
Google 如何用 AI 造聊天机器人?Pinterest 如何用机器学习获得两亿活跃用户?10 月 QCon 上海站,还有来自 Uber、Paypal、LinkedIn、Airbnb 等顶尖技术专家前来分享前沿实践经验。
QCon 报名即将结束,识别下方二维码或点击【阅读原文】与 100+ 国内外技术大咖零距离,如有问题欢迎联系票务经理 Hanna ,电话:15110019061,微信:qcon-0410。