Android开发之MVVM模式实践(四):协程的简单认识和使用,我在美团Android研发岗工作的那5年

68 阅读7分钟

前言

大家好,我是小益!众所周知,在2017年Google I/O大会上,Google宣布将Kotlin作为Android的第一开发语言,而且近年来很多中大型公司招聘Andorid开发者都要求会使用Kotlin开发,所以如果你还没又开始使用Kotlin,就赶紧上船吧!

推荐

文章将率先在公众号「Code满满」与个人博客「李益的小站」上发布,快来关注吧!

wxgzh-qrcode.jpg

一、认识协程

使用Kotlin开发有很多好处,在这我就不一一赘述了。而在Kotlin中有一样东西叫做“协程”,一些同学可能听过甚至使用过,但也有很多同学可能对其不是很了解,这里我简单介绍一下协程是什么!

协程简介

协程其实是一种编程思想,在很多语言中都存在,大致作用也都一样,都是为了帮助我们在处理多线程编码时提供更友好和简便的编码方式,只是每种语言的具体实现不一样,在Kotlin中的协程我们可以简单的理解为是一个线程框架。

普通线程的不便

我们通过一个小例子对比一下使用普通线程和使用协程实现起来有什么不同:

假设有四个接口请求A、B、C、D,接口C需要获取接口A与接口B的执行结果后才能执行,接口D需要获取接口C的执行结果才能执行。

上述场景是我们在平时Android开发中最常见的场景,如果用普通线程的方式来实现,接口请求的结果一般使用callback获取,接口A与接口B应该是并发执行,我们必须在接口A执行完成的callback中检查接口B是否执行完成,同样必须在接口B执行完成的callback中检查接口A是否执行完成,这样才能在第一时间将两个接口的结果交给接口C,而接口C则需要在执行完成的callback中调用接口D,使其执行。

fun apiA(object: callback(){ 获取 —> 结果A 调用 —> 接口AB执行完毕检测方法() } )

fun apiB(object: callback(){ 获取 —> 结果A 调用 —> 接口AB执行完毕检测方法() } )

fun 接口AB执行完毕检测方法(){ if( 结果A!=null && 结果B!=null ){ apiC(结果A , 结果B, object: callback(){ 调用 —> apiD(结果C) } } }

// 开始执行 fun start(){ apiA(); apiB(); }

可以看出,在整个执行流程中都充满了callback,并且接口A与B之间的相互监测是否执行完毕也会多写很多代码。如果这种逻辑再稍微复杂点,简直就是回调地狱,代码的阅读体验也会变的越来越差。当然我们可以使用Handler等消息通知方式来统一处理,但是阅读体验也是提升有限。

以上不便,其归根结底是因为线程是系统调度的,系统控制线程的执行结束,我们开发者在主线程中是无法得知线程何时执行结束,只能等待线程自己通知我们。

协程的优势

而上述场景使用协程来实现,其实现逻辑大致如下:

val 结果A = 协程A执行()

val 结果B = 协程B执行()

val 结果C = 协程C执行(结果A, 结果B)

协程D执行(结果C)

PS:此处特别提示一点,上述协程A与B的逻辑看起来是协程A先执行完后再协程B执行,但其实A、B也是并发执行的

从上述流程中不难看出,其写法直接从异步代码写法变成了同步代码写法,逻辑瞬间清晰了很多,少了很多callback,代码的阅读体验也是直线上升。而这都归功于协程可以灵活的在不同线程之间切换,开发者可以明确的控制协程的结束,再也不用被动的等待通知。说到此处,有的同学可能就想到了RxJava,两者都能实现我们想要的效果,不过两者的设计理念并不相同,语法也是天差地别,有兴趣的同学两者可以都了解一下。

综上,我们可以得出一点,协程可以将异步编码简化,用同步的方式写异步,开发者可以灵活的控制协程的执行与结束以及线程的切换。而这也是我们在Android开发中最在意的特性,协程的其他优点我们暂时不必去了解。

二、导入协程

目前高版本的Android Studio添加Kotlin支持时,已经自动添加Kotlin协程支持了,如果不能使用协程,可以添加如下依赖

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"

三、协程的几种使用方式

1. runBlocking

runBlocking { getUserInfo(userId) }

此方法一般不推荐使用,因为它会阻塞线程

2. GlobalScope.launch

GlobalScope.launch { getUserInfo(userId) }

此种方法需要慎用,虽然它不会阻塞线程,但是它的生命周期与app一致,并且不能取消

3. 自行创建CoroutineScope

// 此处的context是CoroutineContext,和Activity继承的Context不是同一个东西 val coroutineScope = CoroutineScope(context) coroutineScope.launch { getUserInfo(userId) }

此方法比较推荐,使用此方法我们可以灵活的控制协程的生命周期。所以下面我们主要介绍此种方式的协程使用方式,其他两种方式有兴趣的同学可以自行去了解。

四、协程的简单使用

协程目前常用的几个方法有launchwithContext以及async/await,下面我们主要介绍一下launchwithContextasync/await留到下章与suspend一起讲解。

launch

首先我们先来看下launch的源码,简单了解一下launch是什么!

public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }

可以看出,launchCoroutineScope扩展函数(即launch是CoroutineScope的内部函数),并且最后返回了一个新的协程(即创建了新的Coroutine),具体实现我们暂且不管。其使用方法如下:

// 切换到主线程 coroutineScope.launch(Dispatchers.Main) { ... }

最后说一下我的学习路线

其实很简单就下面这张图,含概了Android所有需要学的知识点,一共8大板块:

  1. 架构师筑基必备技能
  2. Android框架体系架构(高级UI+FrameWork源码)
  3. 360°Androidapp全方位性能调优
  4. 设计思想解读开源框架
  5. NDK模块开发
  6. 移动架构师专题项目实战环节
  7. 移动架构师不可不学习微信小程序
  8. 混合开发的flutter

Android学习的资料

我呢,把上面八大板块的分支都系统的做了一份学习系统的资料和视频,大概就下面这些,我就不全部写出来了,不然太长了影响大家的阅读。需要的小伙伴可以私信我【进阶】我免费分享给大家,或者直接点击下面链接领取,谢谢大家这么久以来的支持。

Android学习PDF+架构视频+面试文档+源码笔记

如果你有其他需要的话,也可以在GitHub上查看,下面的资料也会陆续上传到Github

330页PDF Android学习核心笔记(内含上面8大板块)

Android学习的系统对应视频

总结

我希望通过我自己的学习方法来帮助大家去提升技术:

  • 1、多看书、看源码和做项目,平时多种总结

  • 2、不能停留在一些基本api的使用上,应该往更深层次的方向去研究,比如activity、view的内部运行机制,比如Android内存优化,比如aidl,比如JNI等,并不仅仅停留在会用,而要通过阅读源码,理解其实现原理

  • 3、同时对架构是有一定要求的,架构是抽象的,但是设计模式是具体的,所以一定要加强下设计模式的学习

  • 4、android的方向也很多,高级UI,移动架构师,数据结构与算法和音视频FFMpeg解码,如果你对其中一项比较感兴趣,就大胆的进阶吧!

    进阶学习资料领取方式:GitHub

希望大家多多点赞,转发,评论加关注,你们的支持就是我继续下去的动力!加油!