| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> Kotlin 之协程 -> 正文阅读 |
|
[移动开发]Kotlin 之协程 |
协程是 Kotlin 中的一个重要部分,协程是一种并发设计模式,那么在了解协程之前,我们需要了解一些概念。 参考文章: "Kotlin"系列: 三、Kotlin协程(上) - 掘金 将 Kotlin 协程与生命周期感知型组件一起使用 ?|? Android 开发者 ?|? Android Developers Android kotlin协程入门(二):kotlin协程的关键知识点初步讲解 - 知乎 一、什么是线程线程是一个基本的 cpu 执行单元,也是程序执行流的最小单位。 Android 中会有一个主线程,也就是 UI 线程,负责界面渲染。 二、什么是并发并发是指两个或多个事件在同一时间间隔内发生,这些事件宏观上是同时发生的,但微观上是交替发生的。单核 CPU 同一时刻只能执行一个程序,但由于 CPU 时间片非常小,多个指令前快速切换,会给人一种同时执行的感觉。 而容易混淆的计算机的另一个特性是并行,并行是指两个或多个事件在同一时刻同时发生。它存在于多核 CPU 中,同一时刻可以同时执行多个程序,多个程序可以并行执行。 举个栗子,一个人要画一个圆和一个正方形,他一会儿画圆,一会儿画正方形,这叫并发。但如果另一个人左右脑都很发达,可以左手画圆右手画正方形同时进行,这就叫并行。 三、什么是异步异步是指在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。 简单来说;
举个栗子,假如我去路边摊买了一份烤冷面,需要等待摊主煎蛋、煎饼、煎烤肠、放佐料、打包全部完成后,我才能取走,这中间的过程只有等待,这就是同步,而如果我去店里取了个号点餐,那我点完餐后就可以离开一会儿,自己做自己的事,等待后厨出完餐,叫到我的号了我再去取,这就是异步。 异步通常会以回调的方式来唤起刚才的线程,叫号就相当于一个唤醒操作。由此可见,异步的效率会更高一点,但是从编程角度来说需要注意的问题也更多一点。 四、什么是阻塞阻塞其实就是字面意思,就是当前线程停在这里不往下继续执行了。 五、协程1.什么是协程了解上述基本概念后,我们可以明白一个问题,因为 Android 的主流刷新率是 60Hz,即 1 秒绘制 60 帧,也就是约 16ms 绘制一帧,而界面绘制是由主线程去负责,所以主线程是不能阻塞的,否则轻则卡顿,重则 ANR,但程序中不可避免的会有一些阻塞的操作,比如读写文件、网络请求、数据库操作等,这时候我们就需要引入异步的概念,没有引入协程的概念前我们通常是将这些操作放到子线程中去进行,然后完成后通过回调的方式获取结果,再通知界面刷新。但创建线程是需要耗费内存资源的,而且线程间的切换是由系统决定,也是消耗一些内存资源,因此协程应用而生。 协程是一种编程思想,并不局限于特定的语言。协程设计的初衷是为了解决并发问题,让协作式多任务实现起来更加方便。 协程可以看作是轻量级线程,它相较于线程而言,是由应用程序去控制的,不涉及到系统内核状态的切换,因此会提高执行效率,并且 kotlin 的语法让我们更方便的控制协程的挂起和恢复,还能获取异步执行的结果,可以说是简化了很多操作。 2.基本使用首先我们需要添加协程依赖:
启动一个协程:
可以看到这样就启动了一个协程,看着还是挺简单的,我们可以看到打印的线程并不是主线程,协程名是 null,它所在的线程和协程是由什么确定的呢,来看看 launch 方法:
launch 是?CoroutineScope 的一个扩展方法,CoroutineScope 的意思是协程作用域,该方法由三个参数,第一个参数是 CoroutineContext,即协程上下文;第二个参数为 CoroutineStart,即协程启动方式;第三个参数为一个挂起函数,即真正执行我们逻辑的代码块。该方法还返回了一个 Job 对象,通过这个对象我们取消协程和观察协程的执行状态。Ok,那我们就逐一分析协程作用域以及这几个参数。 3.协程作用域我们刚才使用了?GlobalScope 去启动一个协程,它从字面意思来说是全局作用域,它在虚拟机中只有一份对象实例,而且它的生命周期贯穿整个 JVM,所以我们在使用它的时候需要警惕内存泄漏。所以一般并不推荐使用?GlobalScope 去启动协程,在 AS 中如果使用?GlobalScope 也会出现警告,而协程的作用域,大范围的有以下几个:
除了这几个大的启动协程的作用域范围,协程作用域还可以细分为:
用两段代码来演示一下协同作用域和主从作用域的区别:
Job1 正常执行,Job2 中抛出异常并且中断了程序执行。??
?Job1 正常执行,Job2 执行到异常代码后,后续代码没有执行,但并没有影响其他协程以及没有导致整个程序 crash。 4.CoroutineContext协程上下文用来定义协程行为的关键信息,它包含如下几个东西:
我们在启动一个协程时它是有默认参数?EmptyCoroutineContext 的,通常情况下我们不需要去自定义上下文,最常用的操作也就是指定协程调度器了,而 kotlin 默认提供了几种调度器已满足我们的大部分使用场景,我们通过 Dispatcher 创建如下调度器:
其中前三种使用更多。EmptyCoroutineContext 使用的默认调度器,所以上面的打印中,当前的线程是子线程的名字,如果指定 Dispatcher.Main,那么打印的线程名则会是 main。
5.CoroutineStart协程的启动模式也有默认值,通常我们不需要去特别指定,它的可选值有以下四种:
用不同启动方式看一下执行效果:
执行上述代码,可能产生如下几种结果:
DEFAULT 方式启动的协程紧接着执行了 cancel() 方法,由于立即调度但不是立即执行,所以有可能执行也有可能不执行; LAZY 方式启动的协程,由于没有调用 start() 或其他操作,所以一直没有执行; ATOMIC 和?UNDISPATCHED 也是紧接着执行了 cancel() 方法,但由于它们都是在遇到第一个挂起点之前是不响应 cancel() 操作的,所以挂起点之前的代码都执行了,并且这两个协程的先后顺序并不固定。 上面还提到一点,UNDISPATCHED?直接开始在当前线程下执行,直到运行到第一个挂起点,遇到挂起点之后的执行,将取决于挂起点本身的逻辑和协程上下文中的调度器。这一点我们也可以用代码演示一下:
执行上述代码,会看到如下结果:
可以看到以?UNDISPATCHED 方式启动的协程,虽然指定了调度器是 IO,但是挂起点之前却是执行在 main 里,在 join 之后才切换到了子线程中执行。 6.挂起函数什么是挂起呢?挂起就是保存当前状态,等待恢复执行,可以理解为切换到了一个指定的线程。launch 方法传递的第三个参数就是一个挂起函数,而 launch 方法后面如果还有代码的话,那么就会它们时并发执行的,而且极大概率是 launch 后的代码紧接着执行一段时间后才会执行挂起函数里面的代码。 这一点,我们通过刚才协程的启动方式就能很好理解,协程只是立即调度了,但不一定立即执行,从刚才 DEFAULT 方式启动的协程有可能被 cancel() 掉,我们就能明白。那么有什么办法让协程的挂起函数执行完成后再往下执行吗?答案是肯定的。 a.runBlocking使用 runBlocking 函数可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程。
runBlocking 作为顶层函数,可以在任意地方独立调用,但据说有性能问题,所以通常用于单元测试中,实际开发中并不推荐使用,那真正开发中我们应该用什么呢? b.asyncasync 不同于 runBlocking 直接返回结果,async 会返回一个 Deferred<T> 对象,这个对象有点类似 Java 中的 Future,我们可以通过调用它来获取协程的执行状态或者获取传入的函数的执行结果。
通过上述代码的 log 可以看出来,async 是立即执行的,并不是调用 await 才执行的,而调用 await() 时由于里面的代码块还没有执行完,所以当前协程阻塞,直到代码块执行完成才继续向下执行。 c.withContextwithContext 相当于 async 的简化版,直接返回函数体的执行结果,而不需要调用 await() 方法,但是 withContext 需要我们传入调度器参数。
7.JobJob 作为启动协程的返回值,我们可以通过它来获取协程的状态,也可以用于唤醒或者取消协程,Job 有如下状态:
我们通过 Job 的 isActive、isCompleted、isCancelled 来判断当前是否正处于某个状态。 在取消协程时,需要注意一个作用域的问题,当我们取消一个父协程时,它的所有子协程都会被取消。
协程取消的时候还需要注意一点,就是资源的释放问题,可以对阻塞的代码块使用 try-finally{} 代码块包裹,保证资源的释放。 六、总结以上是协程的初步了解,通过对这几个点的整理,基本可以正常的使用协程,以及可以通过协程的一些特性来简化一些异步操作,当然,如果想更透彻的理解协程是如何实现的,还需要仔细翻看源码,抽丝剥茧后方可融会贯通。 ? |
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 | -2025/3/1 0:53:43- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |