• Kotlin协程的简单用法(GlobalScope、lifecycleScope、viewModelScope)


    协程(Coroutine)

    • 协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
    • 协程就像轻量级的线程。线程由系统调度,协程由开发者控制。
    • kotlin协程本质上是对线程池的封装。协程通过将线程切换的复杂性封装入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。

    GlobalScope(不推荐)

    • GlobalScope.launch
      使用的是DefaultDispatcher,会自动切换到后台线程,不能做UI操作
    1. GlobalScope.launch {
    2. //GlobalScope开启协程:DefaultDispatcher-worker-1
    3. Log.d(TAG, "GlobalScope开启协程:" + Thread.currentThread().name)
    4. //子线程中此处不可以做UI操作
    5. //Toast.makeText(this@MainActivity, "GlobalScope开启协程", Toast.LENGTH_SHORT).show()
    6. }
    • 可以在协程中切换线程
    1. GlobalScope.launch {
    2. //GlobalScope开启协程:DefaultDispatcher-worker-1
    3. Log.d(TAG, "GlobalScope开启协程:" + Thread.currentThread().name)
    4. //子线程中此处不可以做UI操作
    5. //Toast.makeText(this@MainActivity, "GlobalScope开启协程", Toast.LENGTH_SHORT).show()
    6. withContext(Dispatchers.Main){
    7. Toast.makeText(this@MainActivity, "协程中切换线程", Toast.LENGTH_SHORT).show()
    8. }
    9. }
    • GlobalScope.launch(Dispatchers.Main)
      通过Dispatchers.Main使协程依托于主线程中,此时可以更新UI等操作
    1. GlobalScope.launch(Dispatchers.Main) {
    2. //GlobalScope开启协程:main
    3. Log.d(TAG, "GlobalScope开启协程:" + Thread.currentThread().name)
    4. //可以做UI操作
    5. Toast.makeText(this@MainActivity, "GlobalScope开启协程", Toast.LENGTH_SHORT).show()
    6. }

    lifecycleScope、viewModelScope(推荐)

    • 引入方式
    1. implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'//lifecycleScope
    2. implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'//viewModelScope
    • GlobalScope是生命周期是process级别的,即使Activity或Fragment已经被销毁,协程仍然在执行。所以需要绑定生命周期。
    • lifecycleScope只能在Activity、Fragment中使用,会绑定Activity和Fragment的生命周期
    • viewModelScope只能在ViewModel中使用,绑定ViewModel的生命周期

    协程的执行顺序

    1. private fun test() {
    2. Log.d(TAG, "test: 方法开始")
    3. lifecycleScope.launch {
    4. delay(1000)
    5. Log.d(TAG, "test: " + Thread.currentThread().name)
    6. Log.d(TAG, "test: 协程结束")
    7. Toast.makeText(this@MainActivity, "协程结束", Toast.LENGTH_SHORT).show()
    8. }
    9. Log.d(TAG, "test: 方法结束")
    10. }
    1. D/MainActivity: test: 方法开始
    2. D/MainActivity: test: 方法结束
    3. D/MainActivity: test: main
    4. D/MainActivity: test: 协程结束
    • 协程内的阻塞不会影响协程外
    • 由打印结果可以看出协程体是异步执行的,但是可以在其中做UI操作。线程也是异步的,但是不能更新UI,线程需要先切换到主线程。

    协程中多个耗时任务的串行

    • 默认情况下协程中的内容是串行的
    1. private fun test2() {
    2. lifecycleScope.launch {
    3. val startTime = System.currentTimeMillis()
    4. val a = getDataA()
    5. val b = getDataB()
    6. val sum = a + b
    7. //D/MainActivity: test2: sum = 3,耗时:3008
    8. Log.d(TAG, "test2: sum = $sum,耗时:${System.currentTimeMillis() - startTime}")
    9. }
    10. }
    11. private suspend fun getDataA(): Int {
    12. delay(1000)
    13. return 1
    14. }
    15. private suspend fun getDataB(): Int {
    16. delay(2000)
    17. return 2
    18. }
    D/MainActivity: test2: sum = 3,耗时:3008
    

    协程中多个耗时任务的并行

    • 如果需要并行,例如请求多个接口拿到数据后才能进行操作
    1. private fun test3(){
    2. lifecycleScope.launch {
    3. val startTime = System.currentTimeMillis()
    4. val a = lifecycleScope.async { getDataA() }
    5. val b = lifecycleScope.async { getDataB() }
    6. val sum = a.await() + b.await()
    7. //D/MainActivity: test3: sum = 3,耗时:2009
    8. Log.d(TAG, "test3: sum = $sum,耗时:${System.currentTimeMillis() - startTime}")
    9. }
    10. }
    11. private suspend fun getDataA(): Int {
    12. delay(1000)
    13. return 1
    14. }
    15. private suspend fun getDataB(): Int {
    16. delay(2000)
    17. return 2
    18. }
    D/MainActivity: test3: sum = 3,耗时:2009

    协程的停止

    • 手动停止的情况 job?.cancel()
    1. private var job: Job? = null
    2. private fun test4() {
    3. job = lifecycleScope.launch {
    4. ...
    5. }
    6. job?.cancel()
    7. }
    • lifecycleScope和viewModelScope会绑定调用者的生命周期,因此通常情况下不需要手动去停止

    suspend协程挂起原理

    • 在编译期,将suspend标记的方法转化成接口回调的方式,本质上还是基于回调实现的。
  • 相关阅读:
    在uniapp中,如何去掉一些不想要的权限,
    Chromium源码阅读(8):了解Base库里的PartitionAlloc模块
    从0开始学人工智能测试节选:Spark -- 结构化数据领域中测试人员的万金油技术(二)
    学Python的漫画漫步进阶 -- 第十一步.常用的内置模块
    记一次JVM参数调优经历
    【从零开始学习 SystemVerilog】5.3、SystemVerilog 通信—— Mailbox(邮箱)
    抖音招聘直播报白有成本低和招聘效果精准的优势
    A × B Problem(高精度计算)
    最新全流程GMS地下水数值模拟及溶质(包含反应性溶质)运移模拟教程
    Netty(一)- Netty与BIO、NIO、AIO介绍
  • 原文地址:https://blog.csdn.net/Goals1989/article/details/126964335