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 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Kotlin协程(上) -> 正文阅读

[移动开发]Kotlin协程(上)

1、协程基本概念

协程就像非常轻量级的线程。

  • 协程让异步逻辑同步化,杜绝回调地狱,代码逻辑非常简洁易懂
  • 相对于线程切换是由操作系统进行调度的,程序员无法进行控制。
    而协程的调度是由程序员在代码层面上进行控制的,程序员可以通过控制suspend函数的挂起和恢复,从而控制程序运行流程
    ?
2、协程挂起与恢复
  • 挂起(suspend),用于暂停执行当前协程,并保存所有局部变量
  • 恢复(resume),用于让已暂停的协程从其暂停处继续执行
  • 挂起函数,被suspend修饰的函数称为挂起函数;挂起函数只能在协程体内或其他挂起函数内调用
    ?
3、挂起与阻塞区别
  • 阻塞,比如Thread.sleep(5000),当前线程执行到这行代码时,会停下来等待其执行完毕,这5秒期间它干不了任何的其他工作。
  • 挂起,比如协程里的delay(5000),当前协程执行到这行代码时,会挂起,然后该干嘛就干嘛去,等到它delay完毕,就恢复,当前协程从挂起的位置继续往下执行。
findViewById<TextView>(R.id.btn_click).setOnClickListener {
    Log.i("MainActivity", "run start")
    GlobalScope.launch(Dispatchers.Main) {
        Log.i("MainActivity", "delay start, current thread: ${Thread.currentThread().name}")
        // 挂起函数,主协程不会在这里等待5S,而是该处理别的UI响应就去处理
        delay(5000)
        Log.i("MainActivity", "delay over, current thread: ${Thread.currentThread().name}")
    }
    // 阻塞主线程50毫秒
    Thread.sleep(50)
    Log.i("MainActivity", "run end")
}
// 结果
// I: run start
// I: run end
// I: delay start, current thread: main
// I: delay over, current thread: main
// 把调度器改成IO后
findViewById<TextView>(R.id.btn_click).setOnClickListener {
    Log.i("MainActivity", "run start")
    GlobalScope.launch(Dispatchers.IO) {
        Log.i("MainActivity", "delay start, current thread: ${Thread.currentThread().name}")
        // 挂起函数,主协程不会在这里等待5S,而是该处理别的UI响应就去处理
        delay(5000)
        Log.i("MainActivity", "delay over, current thread: ${Thread.currentThread().name}")
    }
    Thread.sleep(50)
    Log.i("MainActivity", "run end")
}
// 结果
// I: run start
// I: delay start, current thread: main
// I: run end
// I: delay over, current thread: main

从运行结果对比来看,得到的结论是,主线程里开启一个主协程,哪怕主线程sleep了,主协程里的内容也不会马上执行,这是因为都是属于主线程,就得看CPU调度了。而且使用Sleep阻塞了主线程,主线程也干不了别的事。
?

4、调度器
  • Dispatchers.Main
    该调度器限制所有执行都在UI主线程,它是专门用于UI的,并且会随着平台的不同而不同。
    对于JS或Native,其效果等同于Dispatchers.Default;对于JVM,它是Android的主线程、JavaFx或者Swing EDT的dispatcher之一。另外,使用该调度器,必须增加相应的组件依赖:
    kotlinx-coroutines-android 、kotlinx-coroutines-javafx、kotlinx-coroutines-swing
  • Dispatchers.Default
    默认调度器,非主线程。它使用JVM的共享线程池,该调度器的最大并发度是CPU的核心数,默认为2。专为CPU密集型任务进行了优化,适用于数据排序、数据解析等
  • Dispatchers.IO
    IO调度器,他将阻塞的IO任务分流到一个共享的线程池中,使得不阻塞当前线程。该线程池大小为环境变量kotlinx.coroutines.io.parallelism的值,默认是64或核心数的较大者。该调度器和Dispatchers.Default共享线程,因此同样环境下创建的新协程不一定会导致线程的切换
    专为磁盘和网络IO进行了优化,适用于文件读写、数据库读写、网络操作等
    ?
5、启动

在非协程环境中凭空启动协程,有以下三种方式

  • runBlocking{}
    启动一个新协程,并阻塞当前线程,直到其内部所有逻辑及子协程逻辑全部执行完成。不常用,仅在测试、调试中使用。
runBlocking (Dispatchers.Default) {
    launch(Dispatchers.IO) {
        delay(500)
        Log.i("MainActivity", "Launch end")
    }
    Log.i("MainActivity", "runBlocking continue")
    val result = async(Dispatchers.IO) {
        delay(500)
        Log.i("MainActivity", "Async end")
        "ABC"
    }
    Log.i("MainActivity", result.await())
}
Log.i("MainActivity", "runBlocking end")
// 结果
// I: runBlocking continue
// I: Async end
// I: ABC
// I: Launch end
// I: runBlocking end
  • GlobalScope.launch{}
    在应用范围内启动一个全局的新协程,协程的生命周期与应用程序一致。这样启动的协程并不能使线程保活,就像守护线程。由于这样启动的协程存在启动协程的组件已被销毁但协程还存在的情况,极限情况下可能导致资源耗尽,因此并不推荐这样启动,尤其是在客户端这种需要频繁创建销毁组件的场景。
GlobalScope.launch(Dispatchers.Default) {
    launch(Dispatchers.IO) {
        delay(500)
        Log.i("MainActivity", "Launch end")
    }
    Log.i("MainActivity", "runBlocking continue")
    val result = async(Dispatchers.IO) {
        delay(500)
        Log.i("MainActivity", "Async end")
        "ABC"
    }
    Log.i("MainActivity", result.await())
}
Log.i("MainActivity", "runBlocking end")
// 结果
// I: runBlocking end
// I: runBlocking continue
// I: Launch end
// I: Async end
// I: ABC
  • 实现CoroutineScope接口,使用launch{}
    这是在应用中最推荐使用的协程使用方式——为自己的组件实现CoroutieScope接口,在需要的地方使用launch{}方法启动协程。使得协程和该组件生命周期绑定,组件销毁时,协程一并销毁。从而实现安全可靠地协程调用。
    通过该方式实现的,常见的API有
    MainScope(应用在Activity中)
    ViewModelScope(应用在ViewModel中,绑定ViewModel生命周期)
    LifecycleScope(应用在带Lifecycle实现的Activity和Fragment中,绑定组件的生命周期)
class MainActivity : AppCompatActivity(), CoroutineScope {
    companion object {
        const val TAG = "MainActivity"
    }
    lateinit var job: Job
    
	// 上下文,调度器 + Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.IO + job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        job = Job()
        findViewById<TextView>(R.id.btn_click).setOnClickListener {
            launch {
                Log.i(TAG, "Launch start, current thread: ${Thread.currentThread().name}")
                delay(5000)
                Log.i(TAG, "Launch end, current thread: ${Thread.currentThread().name}")
            }
            Log.i(TAG, "Button click event end, current thread: ${Thread.currentThread().name}")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Activity销毁,终止协程的运行
        job.cancel()
    }

}
// 点击按钮后的结果:
// I: Button click event end, current thread: main
// I: Launch start, current thread: DefaultDispatcher-worker-1
// I: Launch end, current thread: DefaultDispatcher-worker-3

在一个协程中启动另外一个协程,一般有这两种方式:

  • launch{},CoroutineScope的扩展方法,创建并启动一个子协程,不阻塞当前协程,并返回新协程的Job
runBlocking {
    // 创建调度器是IO的子协程
    launch(Dispatchers.IO) { 
    }
}
  • async{},CoroutineScope的扩展方法,创建并启动一个子协程,不阻塞当前协程,返回一个Deffer,除包装了返回的结果外,其余特性与launch一致
GlobalScope.launch {
    // 创建调度器是Default的子协程
    async(Dispatchers.Default) {
    }
}

withContext() {}
是一个挂起函数,它不创建新的协程,这个函数作用是可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行。

GlobalScope.launch(Dispatchers.Main) {
    // 在主协程执行,当协程运行到withContext时,主协程就会挂起,等待withContext的内容执行完毕。
    // withContext执行完毕后,主协程继续往下执行。
    val image = withContext(Dispatchers.IO) {
        // 切换到IO线程中执行
        getImage(url)
    }
    // 回到UI线程继续执行
    imageIv.setImageBitmap(image)
}

?

6、launch和async
  • launch,返回一个Job,但不附带任何的结果
  • async,返回一个Deferred,其实也是一个Job,可以使用await等待子协程执行完毕获取结果
/**
** launch并行
*/
GlobalScope.launch {
    val job1 = launch(Dispatchers.IO) {
        Log.i(TAG, "Job1 start")
        delay(3000)
        Log.i(TAG, "Job1 end")
    }

    val job2 = launch(Dispatchers.IO) {
        Log.i(TAG, "Job2 start")
        delay(2000)
        Log.i(TAG, "Job2 end")
    }
}
// 结果
// I: Job1 start
// I: Job2 start
// I: Job2 end
// I: Job1 end
/**
** launch串行
*/
GlobalScope.launch {
    val job1 = launch(Dispatchers.IO) {
        Log.i(TAG, "Job1 start")
        delay(3000)
        Log.i(TAG, "Job1 end")
    }
    // 阻塞着当前的协程,等待job1执行完毕
    job1.join()

    val job2 = launch(Dispatchers.IO) {
        Log.i(TAG, "Job2 start")
        delay(2000)
        Log.i(TAG, "Job2 end")
    }
    // 阻塞着当前的协程,等待job2执行完毕
    job2.join()
}
// 结果
// I: Job1 start
// I: Job1 end
// I: Job2 start
// I: Job2 end
/**
** async并行
*/
GlobalScope.launch {
    Log.i(TAG, "Launch start")
    val job1 = async(Dispatchers.IO) {
        Log.i(TAG, "Job1 start")
        delay(6000)
        Log.i(TAG, "Job1 end")
        "Hello"
    }
    val job2 = async(Dispatchers.IO) {
        Log.i(TAG, "Job2 start")
        delay(5000)
        Log.i(TAG, "Job2 end")
        "World"
    }
    Log.i(TAG, "Launch continue")
    // await等待job1对应的协程执行完毕并获取结果
    val result1 = job1.await()
    Log.i(TAG, "Launch job1 await")
    val result2 = job2.await()
    Log.i(TAG, "$result1 $result2")
}
// 结果
// I: Launch start
// I: Job1 start
// I: Launch continue
// I: Job2 start
// 等待5S
// I: Job2 end
// I: Job1 end
// I: Launch job1 await
// I: Hello World

?

7、启动模式
  • CoroutineStart.DEFAULT
    默认的启动模式,协程创建后,立即开始调度,调度器OK后就马上执行;随时可以取消,取消将其直接进入取消响应状态。
GlobalScope.launch {
    Log.i(TAG, "Launch start")
    val job1 = launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
        Log.i(TAG, "Job1 start")
    }
    // 此处cancel后,Job1 start有可能输出不了,因为执行到这里时,协程有可能还处于创建期间
    job1.cancel()
    Log.i(TAG, "Launch end")
}
  • CoroutineStart.LAZY
    协程创建后,不会有任何调度行为,协程体也自然不会进入执行状态,直到我们需要它执行的时候。需要它执行时,调用Job.start、Job.join、Deferred.await函数都能触发协程的调度器执行。
GlobalScope.launch(Dispatchers.Default) {
    Log.i(TAG, "Launch start")
    val job1 = launch(Dispatchers.IO, CoroutineStart.LAZY) {
        Log.i(TAG, "Job1 start")
    }
    delay(500)
    Log.i(TAG, "Launch continue")
    // 协程调度启动
    job1.start()
    Log.i(TAG, "Launch end")
}
// 结果
// I: Launch start
// I: Launch continue
// I: Launch end
// I: Job1 start
  • CoroutineStart.ATOMIC
    协程创建后,立即开始调度,调度器OK后就马上执行,在协程开始运行前无法取消;和DEFAULT的区别在于取消的时机。
GlobalScope.launch {
    Log.i(TAG, "Launch start")
    val job1 = launch(Dispatchers.IO, CoroutineStart.ATOMIC) {
        Log.i(TAG, "Job1 start")
    }
    // 此处cancel后,Job1 start是肯定会输出,因为ATOMIC模式下,如果协程在创建调度执行前,是取消不了的。
    job1.cancel()
    Log.i(TAG, "Launch end")
}
  • CoroutineStart.UNDISPATCHED
    立即在当前线程执行协程体,直到第一个suspend挂起函数调用,才会创建协程并开启异步模式。
GlobalScope.launch(Dispatchers.Default) {
    Log.i(TAG, "Launch start, current thread: ${Thread.currentThread().name}")
    val job1 = launch(Dispatchers.IO, CoroutineStart.UNDISPATCHED) {
        Log.i(TAG, "Job1 start, current thread: ${Thread.currentThread().name}")
        // 遇到挂起函数,协程才开始创建和启动,执行挂起函数
        delay(3000)
        Log.i(TAG, "Job1 end, current thread: ${Thread.currentThread().name}")
    }
    Log.i(TAG, "Launch continue")
    job1.join()
    Log.i(TAG, "Launch end")
}
// 结果
// I: Launch start, current thread: DefaultDispatcher-worker-1
// I: Job1 start, current thread: DefaultDispatcher-worker-1
// I: Launch continue
// I: Job1 end, current thread: DefaultDispatcher-worker-3
// I: Launch end

下图表示DEFAULT和ATOMIC启动模式在cancel时的差异性
在这里插入图片描述
?

8、Job的生命周期

Job是协程上下文中的一部分,能够被组织成父子层次结构,并具有如下重要特性
1、父Job退出,所有子job会马上退出
2、子job抛出除CancellationException(意味着正常取消)意外的异常会导致父Job马上退出

Job的状态表如下:

状态isActiveisCompletedisCancelled
新建状态falsefalsefalse
活动状态truefalsefalse
正在完成truefalsefalse
正在取消falsefalsetrue
已取消falsetruetrue
已完成falsetruefalse

Job的生命周期图如下(盗图):
在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-19 17:44:27  更:2021-11-19 17:45:11 
 
开发: 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 3:42:39-

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