Kotlin进阶指南

您所在的位置:网站首页 kotlin协程切换 Kotlin进阶指南

Kotlin进阶指南

2023-04-14 10:26| 来源: 网络整理| 查看: 265

那段日子抽时间学了一下协程,发现协程其实在几年前就已经存在了,只是近一俩年才开始有了慢慢普及的现象,所以学完后及时输出一下~

不知能否帮到你,但愿别带歪你,时间过得可真是快啊,一晃多年...

协程是什么?

关于协程,其实在Lua语言、Python语言、Go语言、Java语言中都早已存在,Android中是在Kotlin 1.3版本后引入了协程,只是因为当时Kotlin都还没有普及,所以了解协程的人更少了,虽然2018协程已经有了初期稳定版本,但是依旧普及率不高...

协程(coroutines) 是由 JetBrains 开发的丰富的协程库,英语好的同学可以看官网学学基础使用

如果英文不好的话,看看官网中文版的Kotlin协程使用指导吧

在Google中有出过一篇:如何在 Android 应用中使用 Kotlin 协程

话说,android的协程主要体现在Kotlin语言方面,众所周知Kotlin也就是近两三年开始普及的,那么现在掌握协程也是必不可少的技能了

Kotlin协程:我认为Kotlin协程,更多的时候代表的是一个轻量级的线程库或者说是线程框架

初步特征

协程是运行在单线程中的并发程序,意味着它的体量比线程更小 协程支持自动切换线程,子主线程可随意切换

关于协程环境主要涉及到了Dispatchers调度器,常见有三种环境(最后一个个人较少使用- - )

Dispatchers.Main:调用程序在Android 中的主线程 Dispatchers.IO:适合主线程之外的操作,主要针对磁盘和网络 IO 进行了优化,适合 IO 密集型的任务,比如:读写文件,操作数据库以及网络请求 Dispatchers.Default:适合 CPU 密集型的任务,比如计算,json数据的解析,以及列表的排序, Dispatchers.Unconfined:在调用的线程直接执行

协程 - 启动模式(枚举)

public enum class CoroutineStart { DEFAULT, LAZY, @ExperimentalCoroutinesApi ATOMIC, @ExperimentalCoroutinesApi UNDISPATCHED; } 复制代码

四种启动模式,含义如下

DEFAULT:默认的模式,立即执行协程体 LAZY:只有在需要的情况下运行 ATOMIC:立即执行协程体,但在开始运行之前无法取消 UNDISPATCHED:立即在当前线程执行协程体,直到第一个 suspend 调用 为什么要使用协程?

关于协程的特点,Google早有说明

轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。 Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。

话说,协程在写法上允许在不同线程的代码,写在同一个代码块中,还是比较方便的 (这点比较符合内存泄露更少的描述)~

可能很多人都会有一些和我一样的疑问 - 如果只是线程框架的话,为何不继续使用Thread?如果只是为了方便线程切换的话,为何继续使用RxJava?

协程是基于线程的,意味着协程体量比线程要小(看下图秒懂),但是关于性能提升,并不明显; 协程提供了专属的Dispatchers可满足不同场景的线程使用,可及时切换线程; 协程隶属Jetpack组件库,首先Jetpack组件库是Google首推,同时Jetpack的组件被使用率很高 协程兼容了Lifecycle、ViewModel、LiveData等组件库,现在这些组件库已经都开始支持协程的使用了

这里借用一下网图,说明线程和协程运行的环境

线程运行环境 在这里插入图片描述 协程运行环境

在这里插入图片描述

如何使用协程?

如果你准备开始使用协程的话,最好是有一定的Kotlin基础,同时对Jetpack相关组件的了解,它会使你事半功倍

开启协程的方式通常有俩种,其一是launch函数,其二是async函数

launch 更多是用来发起一个无需结果的耗时任务(如批量文件删除、创建),这个工作不需要返回结果。 async 则是更进一步,用于异步执行耗时任务,并且需要返回值(如网络请求、数据库读写、文件读写),在执行完毕通过 await() 函数获取返回值。

我们可以在协程中动态切换对应任务的执行环境,主要是通过withContext(Dispatchers.环境)方式

协程需要运行在协程上下文环境,在非协程环境中凭空启动协程 - 三种方式

runBlocking 建立新的协程,运行在当前线程上,因此会堵塞当前线程,直到协程体结束 GlobalScope.launch 启动一个新的线程,在新线程上建立运行协程,不堵塞当前线程 GlobalScope.asyn 启动一个新的线程,在新线程上建立运行协程,而且不堵塞当前线程,支持 经过await获取返回值

协程中的任务如何挂起和恢复?

协程中进行协程切换的场景,主要涉及到suspend和resume,意图是在挂起函数执行完毕之后,协程会自动的重新切回它原先的线程(注意:普通函数没有suspend和resume这两个特性)

关于suspend挂起函数,更多是作为一个标记和提醒,提醒调用者我是需要耗时操作,需要用挂起的方式,在协程中使用放在后台执行

suspend 用于暂停执行的当前协程,并保存所有的局部变量 resume 用于已暂停的协程中暂停出恢复

add 依赖

//新版本,慎用,不知有没有坑,介意的话可以使用1.1.1版本 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' 复制代码

先讲解launch的使用

在主线程中通过GlobalScope.launch(Dispatchers.Main) {}启动协程,这里要注意我们一般将上下文环境设为Dispatchers.Main 关于子线程(IO线程)执行函数,我们首先需要用suspend进行修饰为挂起函数,同时在内部通过withContext进行线程切换

主要使用GlobalScope.launch函数开启全局范围的协程,而其参数我们一般使用的是Dispatchers.Main,意味着主线程协程

GlobalScope.launch(Dispatchers.Main) { //子线程函数 ioThread() //主线程函数 mainThread() } 复制代码

一般子线程方法,我们使用suspend挂起函数,结合withContext切换子主线程

//IO线程执行的挂起函数,内部通过withContext声明内部逻辑在子线程执行,执行完毕后会自动切回主线程 suspend fun ioThread() { withContext(Dispatchers.IO) { print("IOThread: ${Thread.currentThread().name}") } } 复制代码

像上方的写法你可能感觉不到协程的快感,那么你在看看下方同等代码

GlobalScope.launch { //子线程任务 withContext(Dispatchers.IO) { print("IOThread: ${Thread.currentThread().name}") } //自动切回主线程 print("MainThread:+${Thread.currentThread().name}") //子线程任务 withContext(Dispatchers.IO) { print("IOThread: ${Thread.currentThread().name}") } } 复制代码

在讲解async的使用

首先async执行的协程是支持通过await()返回数据的,同时async也常用于并行任务,我们可以同步执行多个协程任务,最后一起同步返回

协程的suspend挂起函数,除了本身提醒的作用外,一般涵盖着线程切换

//并发请求 val asyncLaunch = GlobalScope.launch { val async = async { add1() } val async1 = async { add2() } System.out.println(async.await() + async1.await()) } suspend fun add1(): Int { delay(1000L) return 10 + 10; } suspend fun add2(): Int { delay(2000L) return 5 + 8; } 复制代码

在文档中有一种通过awaitAll批量获取async数据的方式,有兴趣可以学学,简单方便

suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main) coroutineScope { val deferreds = listOf( // fetch two docs at the same time async { fetchDoc(1) }, // async returns a result for the first doc async { fetchDoc(2) } // async returns a result for the second doc ) deferreds.awaitAll() // use awaitAll to wait for both network requests } 复制代码 如何避免协程泄露、内存泄露?

首先想一下我们常规是如何避免内存泄漏的?嗯... 有点墨迹了,其实大多是在onDestroy中将组件cancle或将数据设置为null等~

通过launch函数查看内部源码可以发现它会返回一个Job对象,那么我们在往内部看一看

在这里插入图片描述

查看Job对象内部可以看出Job是拥有cancel方法的,那么我们完全可以在组件的onDestroy中取消协程,避免协程内部持续引用外部对象而造成泄露

在这里插入图片描述 故此,我们可以直接获取协程的对象,然后调用cancel的方法,从而防止内存泄露;不过现在使用Lifecycle更便捷一些

//开启协程 val job = GlobalScope.launch { //子线程任务 withContext(Dispatchers.IO) { print("IOThread: ${Thread.currentThread().name}") } //自动切回主线程 print("MainThread:+${Thread.currentThread().name}") //子线程任务 withContext(Dispatchers.IO) { print("IOThread: ${Thread.currentThread().name}") } } //取消协程 job.cancel() 复制代码

兴趣扩展

Job:协程构建函数的返回值,可以把 Job 看成协程对象本身,协程的操作方法都在 Job 身上了

job.start() - 启动协程,除了 lazy 模式,协程都不需要手动启动 job.join() - 等待协程执行完毕 job.cancel() - 取消一个协程 job.cancelAndJoin() - 等待协程执行完毕然后再取消 Jetpack AAC 哪些组件支持协程?

谈支持协程的组件前,还是想说一下GlobeScope不受欢迎的原因 - - ~

GlobeScope:生命周期与app同步,随着kotlin的更新,已经慢慢不推荐使用这个了

不推荐的原因:主要是很难避免因自己失误操作,出现的内存泄漏问题

推荐原因:个人认为作为新手入门使用的Scope还是可以的

话说回来,目前来看AAC组件库中的Lifecycle、ViewModel、LiveData都已经开始支持协程的使用了,也都提供了对应的协程调用方式

使用 lifecycleScope 或 viewModelScope ,最好有 Lifecycle 、ViewModel 基础,记得加入以下依赖

//lifecycleScope implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' //viewModelScope implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' 复制代码

lifecycleScope:在activity或者fragment里面使用协程的时候,用lifecycleScope,它在Lifecycle执行,onDestory的时候取消

半夜了,有点无聊,我们看看lifecycleScope提供的方法,内部封装了启动协程的生命周期,又一次可以偷懒了..

在这里插入图片描述

LifecycleCoroutineScope 内涵盖方法

在这里插入图片描述

无聊,写个样例,我们可以通过lifecycleScope动态设置启动协程的时间

val launchWhenCreated = lifecycleScope.launchWhenCreated { print("半夜咯") suspend { withContext(Dispatchers.IO){ print("睡觉吧") } } print("晚安") } override fun onDestroy() { super.onDestroy() launchWhenCreated.cancel() } 复制代码

viewModelScope:viewModelScope只能在ViewModel里面使用协程,它会在ViewModel调用clear方法的时候取消;这方便近期忙着学习,还没有用到,等以后我回头补全



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3