Android Kotlin回顾10.如何启动协程

Posted datian1234

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Kotlin回顾10.如何启动协程相关的知识,希望对你有一定的参考价值。

1.launch启动协程

fun main() = runBlocking 
    launch  
        delay(1000L) 
        println("World!") 
    
    println("Hello") 


fun main() 
    GlobalScope.launch 
        delay(1000L)
        println("World!")
    

    println("Hello")
    Thread.sleep(2000L)


//输出结果
//Hello
//World!

上面是两段代码,这两段代码都是通过launch启动了一个协程并且输出结果也是一样的。

第一段代码中的runBlocking是协程的另一种启动方式,这里先看第二段代码中的launch的启动方式;

  • GlobalScope.launch

GlobalScope.launch是一个扩展函数,接收者是CoroutineScope,意思就是协程作用域,这里的launch等价于CoroutineScope的成员方法,如果要调用launch来启动一个协程就必须先拿到CoroutineScope对象。GlobalScope.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

里面有三个参数:

  • context: 意思是上下文,默认是EmptyCoroutineContext,有默认值就可以不传,但是也可以传递Kotlin提供的Dispatchers来指定协程运行在哪一个线程中;

  • start: CoroutineStart代表了协程的启动模式,不传则默认使用DEFAULT(根据上下文立即调度协程执行),除DEFAULT外还有其他类型:

    • LAZY:延迟启动协程,只在需要时才启动。
    • ATOMIC:以一种不可取消的方式,根据其上下文安排执行的协程;
    • UNDISPATCHED:立即执行协程,直到它在当前线程中的第一个挂起点;
  • block: suspend是挂起的意思,CoroutineScope.()是一个扩展函数,Unit是一个函数类似于Java的void,那么suspend CoroutineScope.() -> Unit就可以这么理解了:首先,它是一个挂起函数,然后它还是CoroutineScope类的成员或者扩展函数,参数为空,返回值类型为Unit

  • delay(): delay()方法从字面理解就是延迟的意思,在上面的代码中延迟了1秒再执行World,从源码可以看出来它跟其他方法不一样,多了一个suspend关键字

//      挂起
//       ↓
public suspend fun delay(timeMillis: Long) 
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@  cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) 
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        
    

suspend的意思就是挂起,被它修饰的函数就是挂起函数, 这也就意味着delay()方法具有挂起和恢复的能力;

  • Thread.sleep(2000L)

这个是休眠2秒,那么这里为什么要有这个呢?要解答这疑问其实不难,将Thread.sleep(2000L)删除后在运行代码可以发现只打印了Hello然后程序就结束了,World!并没有被打印出来。

为什么? 将上面的代码转换成线程实现如下:

fun main() 
    thread(isDaemon = true) 
        Thread.sleep(1000L)
        println("Hello World!")
    

如果不添加isDaemon = true结果输出正常,如果加了那么就没有结果输出。isDaemon的加入后其实是创建了一个【守护线程】,这就意味着主线程结束的时候它会跟着被销毁,所以对于将Thread.sleep删除后导致GlobalScope创建的协程不能正常运行的主要原因就是通过launch创建的协程还没开始执行程序就结束了。那么Thread.sleep(2000L)的作用就是为了不让主线程退出。

另外这里还有一点需要注意:程序的执行过程并不是按照顺序执行的。

fun main() 
    GlobalScope.launch                 // 1
        println("Launch started!")      // 2
        delay(1000L)                    // 3
        println("World!")         	   // 4
    

    println("Hello")            		// 5
    Thread.sleep(2000L)                 // 6
    println("Process end!")             // 7


/*
输出结果:
Hello
Launch started!
World!
Process end!
*/

上面的代码执行顺序是1、5、6、2、3、4、7,这个其实好理解,首先执行1,然后再执行5,执行6的时候等待2秒,在这个等待过程中协程创建完毕了开始执行2、3、4都可以执行了,当2、3、4执行完毕后等待6执行完毕,最后执行7,程序结束。

2.runBlocking启动协程

fun main() 
    runBlocking                 // 1
        println("launch started!")      // 2
        delay(1000L)           // 3
        println("World!")         	    // 4
    

    println("Hello")            		// 5
    Thread.sleep(2000L)           // 6
    println("Process end!")             // 7

上面这段代码只是将GlobalScope.launch改成了runBlocking,但是执行顺序却完全不一样,它的执行顺讯为代码顺序1~7,这是因为runBlocking是带有阻塞属性的,它会阻塞当前线程的执行。这是它跟launch的最大差异。

runBlockinglanuch的另外一个差异是GlobalScope,从代码中可以看出runBlocking并不需要这个,这点可以从源码中分析

public actual fun <T> runBlocking(
    context: CoroutineContext, 
    block: suspend CoroutineScope.() -> T): T 
    ...

顶层函数:类似于Java中的静态函数,在Java中常用与工具类,例如StringUtils.lastElement();

runBlocking是一个顶层函数,因此可以直接使用它;在它的第二个参数block中有一个返回值类型:T,它刚好跟runBlocking的返回值类型是一样的,因此可以推测出runBlocking是可以有返回值的

fun main() 
    val result = test(1)
    println("result:$result")


fun test(num: Int) = runBlocking 
    return@runBlocking num.toString()


//输出结果:
//result:1

但是,Kotlin在文档中注明了这个函数不应该从协程中使用。它的设计目的是将常规的阻塞代码与以挂起风格编写的库连接起来,以便在主函数和测试中使用。 因此在正式环境中这种方式最好不用。

3.async启动协程

在 Kotlin 当中,可以使用 async 创建协程,并且还能通过它返回的句柄拿到协程的执行结果。

fun main() = runBlocking 
    val deferred = async 
        1 + 1
    

    println("result:$deferred.await()")


//输出结果:
//result:2

上面的代码启动了两个协程,启动方式是runBlockingasync,因为async的调用需要一个作用域,而runBlocking恰好满足这个条件,GlobalScope.launch也可以满足这个条件但是GlobalScope也不建议在生产环境中使用,因为GlobalScope 创建的协程没有父协程,GlobalScope 通常也不与任何生命周期组件绑定。除非手动管理,否则很难满足我们实际开发中的需求。

上面的代码多了一个deferred.await()它就是获取最终结果的关键。

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> 
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine

asynclaunch一样也是一个扩展函数,也有三个参数,和launch的区别在于两点:

  • block的函数类型: launch返回的是Unit类型,async返回的是泛型T
  • 返回值不同: launch返回的是Jobasync返回的是Deffered<T>,而async可以返回执行结果的关键就在这里。

启动协程的三种方式都讲完了,这里存在一个疑问,launchasync都有返回值,为什么async可以获取执行结果,launch却不行?

这主要跟launch的返回值有关,launch的返回值Job代表的是协程的句柄,而句柄并不能返回协程的执行结果。

句柄: 句柄指的是中间媒介,通过这个中间媒介可以控制、操作某样东西。举个例子,door handle 是指门把手,通过门把手可以去控制门,但 door handle 并非 door 本身,只是一个中间媒介。又比如 knife handle 是刀柄,通过刀柄可以使用刀。

协程的三中启动方式区别如下:

  • launch:无法获取执行结果,返回类型Job,不会阻塞;
  • async:可获取执行结果,返回类型Deferred,调用await()会阻塞不调用则不会但也无法获取执行结果;
  • runBlocking:可获取执行结果,阻塞当前线程的执行,多用于Demo、测试,官方推荐只用于连接线程与协程。

作者:无糖可乐爱好者
链接:https://juejin.cn/post/7171981069720223751

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

以上是关于Android Kotlin回顾10.如何启动协程的主要内容,如果未能解决你的问题,请参考以下文章

android kotlin 协程 协程间的通信

史上最详Android版kotlin协程入门进阶实战

如何启动Kotlin协程

如何启动Kotlin协程

Kotlin回顾9.协程思维模型

Kotlin回顾25.Kotlin协程—select