IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 协程入门基础 -> 正文阅读

[移动开发]协程入门基础

概念

协程是一种非抢占式或者说协作式的计算机程序并发调度的实现,程序可以主动挂起或者恢复执行。

函数或者一段程序能够被挂起(可以理解成暂停),待会儿再恢复,挂起和恢复是开发者的程序逻辑自己控制的,协程是通过主动挂起出让运行权来实现协作的

协程和线程区别

  • 线程之间是抢占式的调度
  • 协程之间是协作式的调度

协程是依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的。所以协程像是一种用户态的线程,非常轻量级,一个线程中可以创建N个协程。

使用

依赖

 // 协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
    // 协程Android支持库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"

开启协程

使用 runBlocking 顶层函数

通常适用于单元测试的场景,业务开发中不会用到这种方法,因为它是线程阻塞的。

runBlocking {
    // 代码...
}

使用GlobalScope 对象

使用GlobalScope单例对象,可以直接调用launch、async开启协程。在Android开发中同样不推荐这种用法,因为它的生命周期会和app一致,且不能取消(和使用runBlocking的区别在于不会阻塞线程)。

GlobalScope.launch {
    // 代码...
}

GlobalScope.async {
    // 代码...
}

使用CoroutineScope 对象

通过 CoroutineContext创建一个CoroutineScope对象,需要一个类型为CoroutineContext 的参数(推荐的使用,通过 context 参数去管理和控制协程的生命周期)。

val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
     // 代码...
}

作用域

作用域(Coroutine Scope)是协程运行的作用范围。launch、async都是CoroutineScope的扩展函数,CoroutineScope定义了新启动的协程作用范围,同时会继承了他的coroutineContext自动传播其所有的 elements和取消操作。

实操

runBlocking 顶级函数

binding.tvBtn.setOnClickListener {
            val result= runBlocking {
                Log.e("CoroutineExActivity","---runBlocking--》启动协程")
                "使用runBlocking 启动协程"
            }

            Log.e("CoroutineExActivity","--runBlocking-->${result}")
}

// 执行结果
CoroutineExActivity: ---runBlocking--》启动协程
CoroutineExActivity: --runBlocking-->使用runBlocking 启动协程

runBlocking启动的是一个新的协程并阻塞调用它的线程,直到runBlocking运行结束才继续往下执行。并且在runBlocking协程最后一行增加一个返回值。

launch 函数

binding.tvBtn.setOnClickListener {
            val result=GlobalScope.launch {
                Log.e("CoroutineExActivity","-----》启动协程")
            }
            Log.e("CoroutineExActivity","---->${result}")
        }

// 执行结果(其中一种结果)
CoroutineExActivity: -----》启动协程
CoroutineExActivity: ---->StandaloneCoroutine{Completed}@b2fd55f

从上面的执行结果来看,launch方法返回StandaloneCoroutine类型的result。查看一下其源码:

// 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
}

从源码来看,launch方法返回了Job类型。那执行结果StandaloneCoroutine和源码中定义方法返回类型Job之间有啥关联?查看StandaloneCoroutine源码:

// StandaloneCoroutine 源码

private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
    ...
}

public abstract class AbstractCoroutine<in T>(
    /**
     * The context of the parent coroutine.
     */
    @JvmField
    protected val parentContext: CoroutineContext,
    active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    ...
}

从源码来看,StandaloneCoroutine是Job 的子类。

async 函数

 binding.tvBtn.setOnClickListener {
            val result=GlobalScope.async {
                Log.e("CoroutineExActivity","---async--》启动协程")
            }
            Log.e("CoroutineExActivity","--async-->${result}")
        }

// 执行结果(其中一种结果)
CoroutineExActivity: --async-->DeferredCoroutine{Active}@b2fd55f
CoroutineExActivity: ---async--》启动协程

从上面的执行结果来看,async方法返回DeferredCoroutine类型的result。查看一下其源码:

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
}

private open class DeferredCoroutine<T>(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<T>(parentContext, active), Deferred<T>, SelectClause1<T> {
    ...
}

public interface Deferred<out T> : Job {
    //返回结果值,或者如果延迟被取消,则抛出相应的异常
    public suspend fun await(): T  
    public val onAwait: SelectClause1<T>
    public fun getCompleted(): T
    public fun getCompletionExceptionOrNull(): Throwable?
}

从上面的源码来看,DeferredCoroutine是AbstractCoroutine的实现类,同时实现Deferred 接口。这么看来DeferredCoroutine就是一个Deferred,一个携带有返回值Job。 通过
Deferred 的await() 方法来获取返回值T。

// launch 源码 
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    ...
}

//  async 源码
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    ...
}

从launch和 async 方法源码来看,CoroutineContext代表上下文,它的作用就是线程切换,它的值有Dispatchers.Main、Dispatchers.IO、、Dispatchers.Default、Dispatchers.Unconfined。

CoroutineStart表示启动模式,其值有:CoroutineStart.DEFAULT、CoroutineStart.LAZY、CoroutineStart.ATOMIC、CoroutineStart.UNDISPATCHED,默认直接允许调度执行。各个值具体含义和用法在稍后文章中讲解。

// 第一种写法
coroutineScope.launch(Dispatchers.Main) {      // 在 UI 线程开始
    val image = withContext(Dispatchers.IO) {  //  切换到 IO 线程,并在执行完成后切回 UI 线程
        getImage(imageId)                      // 将会运行在 IO 线程
    }
    avatarIv.setImageBitmap(image)             // 回到 UI 线程更新 UI
} 


// 第二种方法
coroutineScope.launch(Dispatchers.Main) {// 在 UI 线程开始
     val image = getImage(imageId)    // 调用suspend 函数
     avatarIv.setImageBitmap(image)  // 回到 UI 线程更新 UI
}

// 挂起函数
suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
    ...
}

suspend 是 Kotlin 协程最核心的关键字,意思是可挂起(暂停)。挂起函数只能在协程中或者挂起函数中调用。

withContext()函数是一个比较特殊的作用域构建器,withContext()函数是一个挂起函数。调用
withContext()函数之后,会立即执行代码块中的代码,同时将当前协程阻塞住。当代码块中的代码全部执行完之后,会将最后一行的执行结构作为withContext()函数的返回值返回。

协程比线程轻量级原因

编程语言级别实现的协程就是程序内部的逻辑,不会涉及操作系统的资源之间的切换。操作系统的内核线程自然会重一些,且不说每创建一个线程就会开辟的栈带来的内存开销,线程在上下文切换的时候需要CPU把高速缓存清掉并从内存中替换下一个线程的内存数据,并且处理上一个内存的中断点保存就是一个开销很大的事儿。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-01 20:43:51  更:2022-02-01 20:45:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:20:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码