记录一下自己这段时间对 Android 架构方面的一些探索。
前言
关于Android架构方法的简单介绍,在我的上一篇文章。
Q:为什么介绍完 MVP、MVVM 后还会有后续的这篇文章呢?
A:其实上一篇只是介绍了这两种方法的思想,并没有给出一个最佳实践案例。(然而这篇文章也没能提供像 Sunflower 那样完整的 demo,下次一定)
Q:就这?那我为什么要进来看啊!
A:仔细看标题。之所以用了“现代”这个词,是打算在这篇文章里面介绍一下近几年 Google 对 Android 应用开发提出的新的思想。顺便刷新一下许多人对 Android 开发的理解。
对了,官方文档有一个架构指南,如果你没研究过应用架构,可以是非常好的入门材料(入门到入土可以看看这篇文章)。
那开始正经的介绍了哦。
可能在许多人眼里,能用 View 控件做出 UI 界面、能用 Java 代码写出能用的业务逻辑、能够成功通过编译和测试就差不多学会了 Android 开发。但是正经的 Android 程序员肯定不会抱着这样的态度,因为随着项目的扩大,需要考虑的会越来越多,所以他们会对应用的架构进行探索,寻找一个可扩展、可测试、风格统一的架构方法来组织这个庞大的项目。其实关于“大型项目”的架构,还是要比我这篇文章里面讲到的要复杂得多,但不是说这篇文章提到的方法还不能驾驭大型项目,只是大型项目会在这个基础上增加一些能力(而不是去调整整个架构),比如最近比较火的Flutter
、模块化
和热修复
。
我努力地想要总结一下这篇文章的受众画像,但是想了半天也只能含糊地列出一些:
- 基于 MVC 思想进行 Android 开发的开发者
- 只是入门过 Android 开发但是想要从事这个岗位的开发者
- 一个页面开一个 Activity 的开发者
- 不了解 Jetpack 的 Android 开发者
- 尝试做 Full Stack 的 iOS 开发者
大概就是这样。顺便说一下文章中我会介绍的我对与 Android 架构方法的探索过程,这些都是基于 Google 官方的资料进行的,所以很多地方是官方资料的引用,但是我会尽量多地加上我自己的理解,这样也方便读者理解。
MVP 方法
关于 MVP 的介绍可以看上一篇
MVP 的本质是什么
从书写业务逻辑的思想来说,P 层无非是把业务逻辑独立了出来,换句话说,P 层的方法移到 Activity / Fragment 里面也一样可以运行。它只是对 Activity / Fragment 类做了一个瘦身。本质上还是一种MVC的方法。
Presenter 这个词可能会让很多人感到疑惑,因为字面上的意思大概是对数据进行呈现。那么令人困惑的地方就在于:数据从哪里来的?我什么时候去获取数据?针对这两个问题,官方给出的架构指南 MVP 分支并没有能很好地解释。同时 Google 官方对 Presenter 层的职能甚至还是有分歧的……请看:(注意:以下内容的前提都是应用基于官方推荐的 MVVM 架构,这点可能造成误会,请先不要深入解读)
If your UI is complex, consider creating a presenter class to handle UI modifications. This might be a laborious task, but it can make your UI components easier to test.
如果界面很复杂,不妨考虑创建 presenter 类来处理界面的修改。这可能是一项艰巨的任务,但这样做可使界面组件更易于测试。
- Handling Lifecycles with Lifecycle-Aware Components
Whatever lets you separate concerns is a good idea. If your ViewModel is holding too much code or has too many responsibilities consider: Moving some logic out to a presenter, with the same scope as the ViewModel. It will communicate with other parts of your app and update the LiveData holders in the ViewModel.
能减轻你的担心的主意一定是个好主意。如果你的 ViewModel 里代码太多、承担了太多职责,试着去:将一些代码移到一个和 ViewModel 具有相同生命周期的 Presenter。让 Presenter 来跟应用的其他部分进行沟通并更新 ViewModel 中持有的 LiveData。
- ViewModels and LiveData: Patterns + AntiPatterns (译文摘自:ViewModel 和 LiveData:为设计模式打 Call 还是唱反调?)
在 Lifecycle 文档中(前段),我们可以认为这个 P 层是拥有数据,它的作用是用数据去更新界面。在官方的一篇博客中(后端),这里的 P 层是用于做数据的获取,并没有更新 UI 的职责。
是不是有点晕了,没事问题不大,待会儿我们就能明白应该怎么解决获取数据并更新 UI 的方法。
MVP 的缺点
正如上一段所说,MVP 本质还是 MVC 的思想。我们的业务逻辑是去响应UI事件,或者说我们的业务逻辑是一个个 UseCase(用例)的集合。
这样做有问题吗?当然没有。从编写程序的角度去看,它可以承担大规模的项目。但是,它不是 Google 推荐的最佳实践是有理由的。
在读这篇文章的 Android 开发者有没有想过换一个方法来看待 Android APP。传统的开发思想是用户操作,或者说视图事件驱动数据。是吧,思考一下是不是往往都从用户的事件出发,去编写如何操作数据,如何让视图更新数据。这样写下来会感觉流程特别清晰简单。但是如果我反过来,让数据来驱动界面呢?
不着急,先举个例子。Android 开发中让开发者感到最奇怪的事情,就是界面的旋转。为什么 Android 框架设定就是屏幕旋转后一定会重新启动 Activity 呢?啊这个我也不清楚,但是这一波操作会是一个坑点,那就是屏幕旋转后整个页面又要去重新加载一遍数据,这是不是挺尴尬的?如果是联网的一个复杂页面,那就是要被迫做几次网络请求。在 MVP 架构中,一个比较好的方法是在 DataRepository 中做一个in-memory cache
,这样可以减少一次网络请求。但是,Repository 是全局单例的存在,这样做会一是会占用着内存,二是实现起来确实也非常麻烦,因为一个页面的数据可以用到很多很多的 POJO 类。
这种情况应该怎么办呢?我觉得还是听一句劝,用上 ViewModel 吧。
换句话说,MVP/MVC 的缺点就在于数据完全是被动的,而且数据的操作会非常频繁,往往是伴随着生命周期一次次的回调一次次地去操作数据。在 Android 中,视图层会非常容易被杀死,除了屏幕旋转,还有 App 进入后台、Activity / Fragment 切换和进出返回栈……所以最佳的做法还是需要把页面数据分离到 ViewModel,这样就会好很多(具体怎么好会在后面讲到)
MVVM 方法
关于Android架构方法的简单介绍,在我的上一篇文章有简单介绍。
为什么会抵触 MVVM on Android?
实不相瞒一开始我也是抵触 MVVM 的,原因嘛,有以下几点:
一、MVC 的思维惯性让我感觉 Android MVVM 很奇怪。因为官方给的纯 MVVM 都是用 DataBinding 做数据的双向绑定的,但是我一直以来开发 Android 的习惯就是会去获取 View 组件的引用,是一种典型的面向对象思想。突然告诉我不需要引用视图真的让我很难接受。
二、DataBinding 的一系列问题。因为是双向绑定,所以对于测试会不够友好,有时候会不知道是数据不对还是渲染的方法不对。而且 DataBinding 对 RecyclerView 的绑定方法不是很好用。
MVVM 的灵魂是什么
这个问题是我自己提出来的,不知道读者有没有过这个疑问。在 Android MVVM 中,灵魂到底是 ViewModel 还是 DataBinding?
一开始我以为灵魂是 DataBinding,所以觉得 MVVM 并不能很好地去取代 MVC / MVP。后来熟读了 Google 给的文档后,我感觉灵魂还应该是 ViewModel,DataBinding 设计的初衷应该只是适用于简单的页面。
当我改变答案后,我对 MVVM 的看法也随之改变了。因为让数据驱动界面真的是一个很不错的思想(针不绰)。所以我下面讲的都是会基于 MVVM 的思想,或者说是数据驱动界面的思想。
Jetpack 架构组件
Jetpack是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。
ViewModel
在上文我也提到了一下 ViewModel,如果你之前没有听说过这个东西,可以暂时把它理解为“存放页面数据的地方”。做过前端的小伙伴一定会联想到this.data
,没错差不多就是这样。
使用 ViewModel 有哪些好处,官方文档其实说得非常详细。在我看来使用 ViewModel 主要的原因就是可以弥补上文提到的 MVC / MVP 在 Android 上的缺陷。
ViewModel 一个很重要的特性就是它的生命周期,ViewModel 的生命周期是会比它依附的 Activity / Fragment 略长的,因为它创建时会接受 Activity / Fragment 的Lifecycle
,在接收到这个Lifecycle
完全结束的回调后才会回调自身的onCleared()
方法。这么一说,是不是需要先介绍一下 Lifecycle 呢?其实 Lifecycle 也是 Jetpack 的一部分,它的用途是可以让那些原本没有生命周期的类或对象去监听它们依附的具有生命周期的组件(比如 Activity / Fragment)的生命周期。说起来有点绕,但是道理是简单的。
ViewModel 具有生命周期感知的能力,又有什么样的作用呢?在这里官方给了一个例子(如图):
可以看到 ViewModel 对象并没有在 Activity 第一次onDestroy
时被清除,这是为什么呢?因为第一次的onDestroy
是因为配置变更(这里体现是屏幕的旋转),这个时候系统和开发者都不希望页面数据也跟随视图被杀掉了。如果使用 MVC / MVP 的话,需要在代码中加入逻辑来判断导致onDestroy
的原因,再决定要不要清除数据。这样的代码片段就是所谓的样板代码,为了实现一些特定的功能逻辑而需要在若干个地方重复编写的代码块。因为在 Android 中不可避免地有很多的坑,如果用样板代码去填坑的话,不如换成官方给我们造好了的轮子,这就是 Google 大力推荐开发者使用 Jetpack 的原因之一。
言归正传,ViewModel 因为集成了 Lifecycle,所以可以帮我们处理好生命周期事件。当然 ViewModel 还可以有高级的用法,那就是处理 SavedInstanceState,这个使用场景会相对少很多,所以我直接贴文档。总之,使用ViewModel可以让我们思维中数据和界面的“耦合”自然而然地解开。
Navigation 是一个“来晚了”的 Android 页面路由框架。它的作用是可以替代startActivity()
和FragmentTransaction
的调用。以往,Android 启动一个 Activity 需要手动声明一个 Intent,显式调用的话还需要给出目标 Activity 的类名,非常地原始。类似的,Fragment 的切换需要在 Fragment 宿主 Activity 中获取supportFragmentManager
才能进行,同时还需要手动 new Fragment 对象。
不仅仅是这些,Navigation 对于返回栈的处理是非常好用的。举个例子,当我们点 App 的商品推送消息,进入的肯定是商详的 Activity,这个时候如果我们点击返回箭头,它会返回到哪里?Navigation 给出的示例是这样的:
这个问题中,我们遇到的麻烦就是,App 可以且应当允许用户快捷地进入一个页面,而不是每次都从 MainActivity 出发一步一步导航过去。当然用原始的方法我们也可以写,但是这也是一种样板代码。所以,还是建议开发者把页面路由交给 Navigation 框架去做。不仅是因为它可以处理复杂的导航,而且在绘制导航图的过程中,开发者可以对应用的页面导航逻辑更加了如指掌。
LiveData
上文讲到 ViewModel 的时候我有提到“视图和数据的解耦”,那么具体是如何实现的,这部分工作就要交给 LiveData 了。
首先来解释一下 LiveData 这个词,因为一般来说加了 Live 这个词的东西总是比较高级的。LiveData 和普通数据的区别就在于 LiveData 的设计贯穿着 Observer 模式的概念。熟悉 RxJava 的同学肯定对观察者模式有着更深刻的理解。当然不了解观察者模式也可以稍后再深入了解,这里只需要知道 LiveData 怎么融合在架构中即可。
LiveData 包裹的内容主要是:数据本身、数据的观察者们(Observer)
请记住,一般来说数据的观察者应该是 LifecycleOwner ,就是那些自身具有生命周期的组件(Activity / Fragment等),因为具有生命周期,所以 LiveData 可以知道这些观察者们的状态,如果是处于活跃状态的,那么在数据更新时会将更新通知给这些活跃的观察者。那么如果观察者被销毁呢?LiveData 会及时移除这个观察者,否则就是大家熟知的内存泄漏了。一般的,对于 Activity 和 Fragment,LiveData 是可以放心使用,不需要担心内存泄漏的,因为 LiveData 已经为我们做好了许多操作。
这样一来,视图和 ViewModel 需要做哪些变更呢?Google 已经给了答案:
视图不再是需要有个东西去给它塞数据,而是视图直接持有 ViewModel 的引用,用户事件如果需要修改数据的,直接调用 ViewModel 中写好的方法,但是不直接接收和这个方法绑定的回调,这是和 MVP 模式截然不同的。那么视图这个时候如何知道数据的变化呢?它需要借助 LiveData 来实现。ViewModel 有public
修饰的LiveData<T>
对象,视图需要去观察这个对象,当 LiveData 的value
变更时,它会给观察着它的活跃观察者发送通知(上一段有说过啦),那么视图在收到通知的时候再去主动获取这个 LiveData 的value
,就可以得到数据了。
这个例子只是单个视图观察,事实上很多场景中会出现共享 ViewModel 的概念,就是好多个观察者都需要观察这个 LiveData,这个时候通过 LiveData 的机制可以非常容易地 get 到最新的数据,而且代码写起来也非常简单,不需要再关注数据是否是最新的等等琐碎的问题。
Hilt
Hilt 是今年刚推出的 Jetpack 组件,用来取代 Dagger2 来做依赖注入的。为什么推荐使用依赖注入,我会在后面讲到。如何使用 Hilt,请参考官方文档。
其实对于依赖注入这个概念,一开始听会觉得很玄学,容易和 Gradle 里面添加的“依赖库”相混淆。其实依赖注入……就是在一个对象里面new
一个它需要用到的对象的过程。没错就是一个new
而已,用new
来写的话称为“手动依赖注入”,而 Dagger、Hilt 都是依赖注入框架。考虑到简单说说不太好理解,贴一段 Google I/O 2019的演讲链接 - Youtube视频。
视频中讲的是 Dagger,不过问题不大,Hilt 就是 Jetpack 团队和 Dagger 团队(square 带佬)合作的为 Android 而生的依赖注入框架。(Dagger 是为 Java 而生的,不是很适应 Android 的注入)。当时看了这个视频,我的反应就是:“真不戳,依赖注入真不戳。”然而那个时候 Dagger2 的文档也标明了适合 Android 的 Dagger 还在构建中,所以我就没用,直到今年 Jetpack 正式推出了 Hilt,我才开始使用依赖注入框架。
ViewBinding
ViewBinding 其实挺捞的,说实话用起来没有 ButterKnife 注解这么舒服。而且……从 ButterKnife 迁移到 ViewBinding 会遇上各种蛋疼的问题,需要去404的网站慢慢找才能找到答案……但是无可奈何,ButterKnife 已经标注deprecated
了。
贴一段 ButterKnife 的遗言:
Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with AGP will be considered. Feature development and general bug fixes have stopped.
DataBinding
emmmmm 这个库表面上是实现数据的双向绑定的,实际上用起来没有那么舒服,特别是当页面使用了列表视图的时候……这个绑定和前端框架的绑定完全不是一个级别的。如果是实现简单的页面的话,它的好处就是可以通过data-binding
的语法,在layout.xml
里面完成 ViewModel 的绑定和数据的双向操作,确实可以减少很多又臭又长的业务逻辑代码。
说起来面试的时候和网抑云的面试官聊了以下对这个库的看法,我们一致的意见就是,虽然用它确实没什么问题,但是复杂的页面还是不要依赖这个框架。
探索
这一部分我会和大家分享一下我在“蜗壳”项目重构中对 Android 应用架构的探索。这边就比较主观了,因为大部分都是我个人的意见,所以也请读者不用追究对错,就当故事看看就 OK 啦~
MVP + ViewModel ?
因为“蜗壳”重构前的版本用的是 MVP 方法,所以我有点舍不得割掉 P 层。一开始认为用 P 层做数据展示的中间层应该还是可行的,可以让 P 层持有 V 层的 Lifecycle,用 P 层去观察 ViewModel,再去更新页面。不过从现在的实践来看,这样的 P 层显得有点多余了,因为改用数据驱动界面后,直接让 V 层去观察数据就足够了,V 层的代码量也不会过于庞大。于是就有了下图这样尴尬的情况:
emmm……这 TM 不就是纯粹的 MVVM 吗!
但是这样又有新的问题。在原来的 MVP 中,处理数据的业务逻辑是全部放在 P 层,虽然现在是数据驱动界面,但是对于数据本身还是会有许多的逻辑代码的,那么如果界面复杂,现在这样做会导致 ViewModel 变得十分臃肿,因为目前大部分业务逻辑都在 ViewModel 里面,这显然也不是我们想要的。
在官方给的一篇参考博客中,我好像找到了一点眉目。这篇文章刚才也有引用过,就是ViewModel 和 LiveData:为设计模式打 Call 还是唱反调?
臃肿的 ViewModel
能减轻你的担心的主意一定是个好主意。如果你的 ViewModel 里代码太多、承担了太多职责,试着去:
将一些代码移到一个和 ViewModel 具有相同生命周期的 Presenter。让 Presenter 来跟应用的其他部分进行沟通并更新 ViewModel 中持有的 LiveData。
添加一个 Domain 层,使用 Clean Architecture 架构。 这个架构很方便测试和维护,同时它也有助于快速的脱离主线程。 Architecture Blueprints 里面有关于 Clean Architecture 的示例。
✅ 把代码职责分散出去。如果需要的话,加上一个 Domain 层。
所以,合适的方法是尽可能让 ViewModel 只持有数据,像一个庞大的 POJO 类一样,不去关注什么时候应该去获取数据。那么按照文中给的两步这样分离出来,可以得到下图的解决方案:
这是什么意思呢,看上去就是多了 P 层和 Domain 层。Domain 层是为了体现 Clean Architecture,后面会解释。我们先举个例子。
例:用户点击按钮,刷新界面数据。刷新时显示 Loading 圆圈(为了省事先不考虑刷新出错的情况)
过程如下:
- Fragment 中响应按钮的
onClick()
回调,回调方法中调用 P 层的刷新方法,随便取名叫fetchData()
好了。 - 在
fetchData()
方法中,首先对 ViewModel 的dataState
字段进行更新,修改为Loading
。这时 Fragment 因为也持有 ViewModel 的引用,同时观察着这个字段,所以会收到变更消息,在 Fragmentobserve
的回调中更新界面,显示 Loading 圆圈。 - OK,
fetchData()
方法还在进行,它会去运行一个 UseCase,UseCase 就是一个个用例,是用来处理数据操作的,只需要简单地去invoke()
然后[1]接收数据即可。 - 现在进入 Use Case,这个用例会去向 Repository 获取数据,就是调用 Repository 里面相应的方法。
- 获取到数据后,P 层也不需要向 Fragment 回调数据,而是应该去[2]修改 ViewModel 中相应的字段,注意哦,视图层都是通过观察者模式来订阅数据的。
- 这个时候 Fragment 对应的
observe
回调都会收到最新刷新的数据,就可以做界面更新啦~
【1】 这一步可以用相似的方法改为观察者模式,需要 Repository 的数据也改用 LiveData,ViewModel 中进行订阅也可以。但是会比较复杂,可以参考这里
【2】 这样做可以在数据刷新完成时 Fragment 已经处于不活跃状态的情况下暂存最新的页面数据
看起来会不会有点晕呢?别紧张,你这样没事。只要了解个大概我想也可以有很多的启发了。当然实践是最好的老师,可以去尝试着做一做。
Clean Architecture
其实 Clean Architecture 这篇论文写得是非常地抽象,所以我也没有很清楚地理明白到底是怎么一回事。但是可以讲一讲它的一些好处。
如果实现 Clean Architecture 的话,可以大概想象一下,每一层的职责都是很清晰的,而且耦合会非常低。这个时候处于上层的对象完全不需要知道底层的实现,只要不涉及返回类型和调用参数的修改,那么改动一处业务逻辑就不至于牵一发而动全身。这对于项目的扩展和测试工作都是非常友好的。
然后是涉及到异步的地方,因为在 Android 中我们一般会开工作线程来实现异步,在 Domain 层中,我们可以直接进行线程切换,继而在 Data 层我们可以不用去做这一方面的工作,专注于 API 调用和本地数据读写即可。这样 Domain 层可以起到一个承上启下的作用。
Why DI (依赖注入) ?
既然都看到这里了,不如去看一看Google I/O 2019的演讲吧。链接 - Youtube视频
上面我也说过了,DI 是随处可见的。一个对象包含了另一个对象这样的关系就是依赖。但是一般在提到“依赖注入”这个词的时候,一定是会提及依赖注入库的。为什么要使用依赖注入库?Google 的回答是:
这张图可以看出随着项目的扩大,使用 Dagger 可以让我们花费在依赖注入上的成本相对平稳。当然我们也可以发现如果是手动依赖注入,在项目体积较小的时候成本其实是非常低的。尽管如此,Google 还是推荐开发者在一开始就使用 Dagger 做依赖注入,虽然 Dagger 的上手成本会比较高。
考虑到项目的可扩展性,越早使用依赖注入库会为后期节省更多的成本,这也是显而易见的。
关于 Hilt 的使用,我整理了一点参考资料:
Single Activity方法与见解
Single Activity: Why, When, and How (Android Dev Summit '18)
什么是 Single Activity ?顾名思义就是在 Application 中只开一个 Activity。那如何实现多页面呢?答案是:使用 Fragment。
很多时候,我们对 Activity 的认识其实是不对的,换句话说 Google 不希望 Activity 被开发者们这样使用。怎样呢?就是一个页面开一个 Activity。了解一下 Android Framework 我们就会发现系统启动一个 Activity 的开销是不小的,那么,如果一个简单页面也要用 Activity 去承载的话,是不是杀鸡用牛刀呢?
大家都知道,Activity 和 Fragment 都可以承载用户界面,但是为了区分,我们可以将 Activity 理解为容器,把原来的一个个页面都放到 Fragment 里面。这样总体的效果是一样的,使用的区别就是页面切换用FragmentTransaction
而不是startActivity()
。诶说到这里有没有回想起来刚才在 Jetpack 中介绍的 Navigation 框架?没错,Single Activity 架构结合 Navigation 是 Google 官方推荐的方式。具体的迁移可以看文档 - 迁移到导航组件
好了念课文结束。说一说我的看法。
所谓 Single Activity,我认为并不是真的一个应用只需要开一个 Activity,它应该是一种呼吁大家使用 Fragment 呈现一个 UI Page 的思想。同时,Single Activity 可以让我们更清楚 Activity 的职责应该是什么。
怎么解释 Activity 的职责呢?我们可以讲一讲 Android 的一些特性。准备过 Android 面试的小伙伴一定复习过 Activity 的launchMode
,但是可能很多人都没有了解过documentLaunchMode
。说一个场景大家一定能明白,那就是微信小程序。打开一个微信小程序的时候,你会发现在“最近任务”的视图中,既有一个微信,也有一个小程序。就好像是开了一个“应用”一样。这个能力是基于documentLaunchMode
实现的。
再举个例子,当 Android 设备连接显示器时,一些手机可以变身成为一个类似桌面操作系统的界面,其中还允许用户打开多个窗口进行多任务,就像用电脑一样。
有感觉了吗?其实我们应该把 Activity 理解为“窗口”,这么一说大家应该能明白什么时候可以开一个 Activity 了。顺带一提,Window
(窗口)也是 Android Framework 中的一个类,它的作用是真正的窗口,不要混淆就 OK 了。
在我的实践中,我会给每个功能模块开辟 Activity,这样可以单独设计模块内部功能页面的导航,然后在所有模块之上加一个顶层的导航图。这样可以更好地理清 App 的页面导航逻辑。
从Java到Kotlin
kotlin-first
不得不说 Kotlin 真的很好用,也很适合“现代”的 Android 应用开发。因为我自己对 Kotlin 的特性也没有非常熟悉,所以我在文中不会讲到用法,只是说说我用下来的一些感受。
Kotlin 的语法和 Swift 真的很像,给一个链接看看对比
好了我说说 Kotlin 比较香的地方
Kotlin 协程
虽然目前,Kotlin/JVM 的协程归根到底还是 OS Thread。它并不能像 Go 一样实现一个真正意义上的“协程”。但是至少在写代码的时候,我们可以不用去关注手动创建和维护线程池这样一系列的问题。在异步操作中,我们只需要将异步执行的方法声明为suspend
,在开始异步调用的地方用withContext
做线程切换和使用launch
启动协程就可以了。相比于自己创建线程池,使用ThreadPoolExecutor.execute()
的方法,Kotlin 协程书写起来要方便得多。
值得一提的是,如果需要获取异步返回值的话,Kotlin 协程提供了一个用于启动协程的async
方法,和launch
不同,它所返回的是一个Deffered
,我们可以使用Deffered.await()
来得到这个返回值。在网上找了一段代码:
1 | val deferred = async(Dispatchers.IO) { |
大致的用法和 ES7 或者 C# 的 async / await 是差不多的。只是在 Android 中因为要区分主线程(UI 线程)和工作线程,需要手动指定一下具体的Dispatchers
。然后 async / await 也不是作为语言的关键字,只是封装好的方法名。
总之,Kotlin 协程是一个封装好的用于简化异步编程的库,尽管它在性能上和线程池调度线程没有太大的区别,但是它好就好在开发者可以用几乎阻塞的方法来实现非阻塞的任务,这是比 RxJava 这个异步解决方案要优秀的。
Null 安全
一开始写 Kotlin 代码的时候会经常出现短短的红波浪线,告诉我这个地方可能是空值,总觉得很奇怪,那我岂不是要在每一处引用都加上!!
修饰?emmm 后来发现是我的用法有问题。
在 Java 中,Null 安全可以用@Nullable
、@NonNull
注解。而 Kotlin 的实现更简单,如果是可能为空的,在类型后加一个?
即可。用到的时候 IDE 会提醒注意空安全。
1 | var i: String = null (wrong) |
闭包
有 JS 那味儿了。
Lambda
相比于 Java 8 奇奇怪怪的 Lambda 表达式,Kotlin 的 Lambda 才是真的 Lambda,熟练之后可以减少很多没必要的代码,比如setOnClickListener
,Java中实现回调要写一大段,而 Kotlin 只需要在方法后加大括号,直接在大括号里面写回调中要执行的操作就可以了。
使用 Lambda 还有一个好处就是不用为了一个回调定义一个interface
了
var / val
等同于 JS 和 Swift 中的var
、let
。虽然是静态语言,但是让编译器去做类型推断在写代码的时候确实会省力一点,可读性也好一点。举个例子:
1 | ----- java ----- |
看到var
、val
就知道这里肯定是变量的声明,在 Java 中还要瞥一眼变量名前面的类名,或者根据首字母大小写来推断,相比之下可读性就差一点。
fun 关键字声明函数
Just for fun.
类扩展
懂的都懂。实现一个扩展函数没必要继承了,写个类扩展就可以。
……还有很多很多
总之,如果你是刚开始学 Android,那么 Kotlin 一定是你的首选语言。当然在上手 Kotlin 之前最好还是有一定的 Java 基础,毕竟 Kotlin Android 也是运行在 JVM 上的,很多 Java 的概念还是要知道。
如果你用了很久 Java 了呢?我还是建议你迁移到 Kotlin,那将是截然不同的写代码体验。
结语
好了,终于说完了。写了好多的感觉。(花了大概3天时间)
只是记录一下自己这段时间对 Android 架构方面的一些探索。