1.导入

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

2.使用

A. 开启协程的方式:GlobalScope.launch
val launch = GlobalScope.launch {
	.....
}

launch接收3个参数,context: CoroutineContextstart: CoroutineStart 以及 block: suspend CoroutineScope.() -> Unit


第三个参数:就是你需要执行的代码,这个不用讲;


第一个参数:(我们可传入以下4种)

  • Dispatchers.Default
    默认线程池,协程体中的代码将被分配到 线程 中执行;
  • Dispatchers.IO
    线程池,协程体中的代码将被分配到 线程 中执行;
  • Dispatchers.Main
    主线程,协程体中的的代码将在 主线程 中执行;
  • Dispatchers.Unconfined
    将在调用协程时所处的线程中执行协程体中的代码;
    如:在主线程中调用协程,则协程体中的代码也将运行在主线程中;

第二个参数:

  • start = CoroutineStart.DEFAULT
    作用:立即执行协程体 默认值
  println("协程 主线程:${Thread.currentThread()}")
       val launch = GlobalScope.launch {
    
            println("协程1:${Thread.currentThread()}")
            println("协程1 主线程?:${Looper.getMainLooper() == Looper.myLooper()}")
            
            val deferredA: Deferred<Int> = async(Dispatchers.Default) {
            
                println("协程A:${Thread.currentThread()}")
                println("协程A 主线程?:${Looper.getMainLooper() == Looper.myLooper()}")
            
                delay(2000)
                var a = 0
                for (i in 0..100) a += i
                a
            }

            val deferredB = async(Dispatchers.Default) {
            
                println("协程B:${Thread.currentThread()}")
                println("协程B 主线程?:${Looper.getMainLooper() == Looper.myLooper()}")
            
                delay(4000)
                var b = 1
                for (i in 0..50) b += i
                b
            }

            println("协程:输出第一句")
            val i = deferredA.await() + deferredB.await()
            println("协程:$i")
            println("协程:输出第二句")
        }

	launch.cancel()//直接取消

打印日志:

I/System.out: 协程 主线程:true
I/System.out: 协程1:Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程1 主线程?:false
I/System.out: 协程:输出第一句

结论:
调用launch.cancel()后,协程体会被直接停止运行;

注释掉launch.cancel()后,打印日志:

I/System.out: 协程 主线程:true
I/System.out: 协程1:Thread[DefaultDispatcher-worker-2,5,main]
I/System.out: 协程1 主线程?:false
I/System.out: 协程A:Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程A 主线程?:false
I/System.out: 协程:输出第一句
I/System.out: 协程B:Thread[DefaultDispatcher-worker-3,5,main]
I/System.out: 协程B 主线程?:false
I/System.out: 协程:6326
I/System.out: 协程:输出第二句

结论:
正常执行,直到协程体执行结束;


  • start = CoroutineStart.ATOMIC
    作用:立即执行协程体,但在开始运行之前无法取消
      println("协程 主线程:${Thread.currentThread()}")
       val launch = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
    
            println("协程1:${Thread.currentThread()}")
            println("协程1 主线程?:${Looper.getMainLooper() == Looper.myLooper()}")
            
            val deferredA: Deferred<Int> = async(Dispatchers.Default) {
            
                println("协程A:${Thread.currentThread()}")
                println("协程A 主线程?:${Looper.getMainLooper() == Looper.myLooper()}")
            
                delay(2000)
                var a = 0
                for (i in 0..100) a += i
                a
            }

            val deferredB = async(Dispatchers.Default) {
            
                println("协程B:${Thread.currentThread()}")
                println("协程B 主线程?:${Looper.getMainLooper() == Looper.myLooper()}")
            
                delay(4000)
                var b = 1
                for (i in 0..50) b += i
                b
            }

            println("协程:输出第一句")
            val i = deferredA.await() + deferredB.await()
            println("协程:$i")
            println("协程:输出第二句")
        }

	launch.cancel()//直接取消

在调用了 GlobalScope.launch 后,又直接调用取消,打印出来的日志为:

I/System.out: 协程 主线程:true
I/System.out: 协程1:Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程1 主线程?:false
I/System.out: 协程A:Thread[DefaultDispatcher-worker-2,5,main]
I/System.out: 协程A 主线程?:false
I/System.out: 协程:输出第一句

结论:
launch.cancel() 无法停止启动方式为 ATOMIC 的协程,协程体中出现delay()方法时,才会被阻断,并且无法继续执行(协程B无法执行);


  • start = CoroutineStart.UNDISPATCHED
    作用:立即在当前线程执行协程体,直到第一个 suspend 调用;
    执行上面相同的代码,修改 start =CoroutineStart.UNDISPATCHED ,删除launch.cancel()
    打印日志为:
I/System.out: 协程 主线程:true
I/System.out: 协程1:Thread[main,5,main]
I/System.out: 协程1 主线程?:true
I/System.out: 协程A:Thread[DefaultDispatcher-worker-2,5,main]
I/System.out: 协程A 主线程?:false
I/System.out: 协程B:Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程B 主线程?:false
I/System.out: 协程:输出第一句
I/System.out: 协程:6326
I/System.out: 协程:输出第二句

结论:
协程体将会在当前线程中执行,直到第一个 suspend 调用,async{}的本质也是suspend 修饰的函数,所以在进入async{} 后,切换了线程;

执行上面相同的代码,修改 start =CoroutineStart.UNDISPATCHED ,保留launch.cancel()
打印日志为:

I/System.out: 协程 主线程:true
I/System.out: 协程1:Thread[main,5,main]
I/System.out: 协程1 主线程?:true
I/System.out: 协程A:Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程A 主线程?:false
I/System.out: 协程:输出第一句
I/System.out: 协程B:Thread[DefaultDispatcher-worker-2,5,main]
I/System.out: 协程B 主线程?:false

结论:
launch.cancel() 无法停止启动方式为 UNDISPATCHED 的协程,协程体中出现delay()方法时,才会被阻断,但可继续向下执行 协程B 的代码,直到被在 协程B 中被delay()阻断


  • start = CoroutineStart.LAZY
    作用:不执行协程体的代码,只有在需要的情况下运行(即调用launch.start() )

执行上面相同的代码,修改 start =CoroutineStart.LAZY,删除launch.cancel() ,在代码中增加点击事件:

binding.button.setOnClickListener {
       launch.start()
}

launch.start() :让协程体开始运行;

点击执行后打印日志:

I/System.out: 协程 主线程:true
I/System.out: 协程1:Thread[DefaultDispatcher-worker-2,5,main]
I/System.out: 协程1 主线程?:false
I/System.out: 协程A:Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程A 主线程?:false
I/System.out: 协程:输出第一句
I/System.out: 协程B:Thread[DefaultDispatcher-worker-3,5,main]
I/System.out: 协程B 主线程?:false
I/System.out: 协程:6326
I/System.out: 协程:输出第二句

结论:
start = CoroutineStart.DEFAULT执行结果相同,区别在于使用此模式需要自己启动协程;

执行上面相同的代码,修改 点击事件,在运行之后立即取消:

binding.button.setOnClickListener {
            launch.start()
            launch.cancel()
 }

打印日志:

I/System.out: 协程 主线程:true
I/System.out: 协程1:Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程1 主线程?:false
I/System.out: 协程:输出第一句

结论:
start = CoroutineStart.DEFAULT执行结果相同,会立即取消协程体的执行;


B. suspendCoroutine<T>

作用:
挂起函数,可以封装获取数据的过程;
返回T中的数据类型,通过调用 it.resume(T) 返回
也可返回异常,通过调用 it.resumeWithException(NullPointerException()) 返回

  • 写出一个数据类
data class TestItem(var name: String, val id: Int)
  • 模拟请求获取到数据
private suspend fun getTestCoroutine() = suspendCoroutine<TestItem> {
        val testItem = TestItem("coroutine", 1)
        it.resume(testItem)
    }
  • 发起请求
val launch = GlobalScope.launch(Dispatchers.Main) {
            val async = async(Dispatchers.Default) { getTestCoroutine().name }
            println(async.await())
        }

打印日志:

I/System.out: coroutine
  • 修改模拟请求,模拟其请求异常并抛出
    private suspend fun getTestCoroutine() = suspendCoroutine<TestItem> {
        val testItem = TestItem("coroutine", 1)
        it.resumeWithException(NullPointerException())
    }
  • 再修改发起请求的代码,接收异常:
val launch = GlobalScope.launch(context = Dispatchers.Main) {
            val async = async(Dispatchers.Default) {
                try {
                    getTestCoroutine().name
                } catch (e: Exception) {
                    println("接收到异常")
                }
            }
            println(async.await())
        }

打印出的日志:

I/System.out: 接收到异常
I/System.out: kotlin.Unit

C.异常处理方案
  • 定义CoroutineExceptionHandler
 val exceptionHandler =
            CoroutineExceptionHandler { coroutineContext, throwable -> println("接收到异常") }

1.修改发起请求的代码:

val launch = GlobalScope.launch(context = Dispatchers.Main + exceptionHandler) {
            val async = async(Dispatchers.Default) {
                getTestCoroutine().name
            }
            println(async.await())
        }

2.这样子,我们就能在 exceptionHandler 当中统一接收异常并处理;
3.context = Dispatchers.Main + exceptionHandler 的写法是因为内部重写了plus()方法,所以我们直接用+号即可同时拥有俩种属性;
4.打印日志:

I/System.out: 接收到异常

5.如果我们将接收异常的exceptionHandler 放入async中,如下:

 val launch = GlobalScope.launch(context = Dispatchers.Main) {
            val async = async(context = exceptionHandler + Dispatchers.Default) {
                getTestCoroutine().name
            }
            println(async.await())
        }

则,程序会直接崩溃,因为在async中,会忽略CoroutineExceptionHandler,并传播异常出去,因为其本质为 coroutineScope,关于这点,将在下面详细写出;


D.supervisorScope 与 coroutineScope

supervisorScope 特点:任何一个任务出问题,并不会影响其他任务的工作
coroutineScope 特点:任何一个子协程异常退出,那么整体都将退出

  • 定义三个函数
private fun getA(): String {
        println("正常a")
        return "正常a"
    }

    private fun getB(): String {
        println("正常b")
        return "正常b"
    }

    private fun getE(): String {
        throw NullPointerException()
    }

  val exceptionHandler =
            CoroutineExceptionHandler { coroutineContext, throwable -> println("接收到异常") }

  • 使用supervisorScope
 val launch = GlobalScope.launch(context = Dispatchers.Main) {
                supervisorScope {
                    println("supervisorScope:" + Thread.currentThread())
                    launch {
                        println("launchA:" + Thread.currentThread())
                        getA()
                    }
                    
                    launch(exceptionHandler) {
                        println("launchE:" + Thread.currentThread())
                        getE()
                    }
                    
                    val lau = launch {
                        delay(1000)
                        println("launchB:" + Thread.currentThread())
                        getB()
                    }
                    lau.join()
                }
        }

getE()处的launch我们用exceptionHandler接收了异常,那么打印出的日志为:

I/System.out: supervisorScope:Thread[main,5,main]
I/System.out: launchA:Thread[main,5,main]
I/System.out: 正常a
I/System.out: launchE:Thread[main,5,main]
I/System.out: 接收到异常
I/System.out: launchB:Thread[main,5,main]
I/System.out: 正常b

我们可以看到,launchB被正常执行;并不被launchE所影响;

我们再修改一下代码,将接收异常移动到最外层,观察会发生什么:

 val launch = GlobalScope.launch(context = Dispatchers.Main) {
            try {
                supervisorScope {
                    println("supervisorScope:" + Thread.currentThread())
                    launch {
                        println("launchA:" + Thread.currentThread())
                        getA()
                    }
                    launch {
                        println("launchE:" + Thread.currentThread())
                        getE()
                    }
                    val launch = launch {
                        delay(1000)
                        println("launchB:" + Thread.currentThread())
                        getB()
                    }
                    launch.join()
                }
            } catch (e: Exception) {
                println("异常")
            }
        }

打印日志:

I/System.out: supervisorScope:Thread[main,5,main]
I/System.out: launchA:Thread[main,5,main]
I/System.out: 正常a
I/System.out: launchE:Thread[main,5,main]
    
    --------- beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: main

可以看到,程序执行到 launchE 时,直接崩溃,我们可以得知,supervisorScope 并不会接收到内部的子协程异常,而是必须子协程自己处理,这样便不会影响到其他子线程的执行,继续往下看,将更理解这段话;

  • 使用coroutineScope
    修改代码:
   val launch = GlobalScope.launch(context = Dispatchers.Main) {
                coroutineScope {
                    println("coroutineScope:" + Thread.currentThread())
                    launch {
                        println("launchA:" + Thread.currentThread())
                        getA()
                    }
                    launch(exceptionHandler) {
                        println("launchE:" + Thread.currentThread())
                        getE()
                    }
                    val launch = launch {
                        delay(1000)
                        println("launchB:" + Thread.currentThread())
                        getB()
                    }
                    launch.join()
        }
I/System.out: coroutineScope:Thread[main,5,main]
I/System.out: launchA:Thread[main,5,main]
I/System.out: 正常a
I/System.out: launchE:Thread[main,5,main]
    
    --------- beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: main

我们可以看到,程序直接崩溃了,因为launchE 的崩溃,并没有办法被独立处理,而是会导致整个协程直接退出;
我们再修改代码,将接收异常移动到最外层,我们将更清楚的了解coroutineScope 的特性:

val launch = GlobalScope.launch(context = Dispatchers.Main) {
            try {
                coroutineScope {
                    println("coroutineScope:" + Thread.currentThread())
                    launch {
                        println("launchA:" + Thread.currentThread())
                        getA()
                    }
                    launch(exceptionHandler) {
                        println("launchE:" + Thread.currentThread())
                        getE()
                    }
                    val launch = launch {
                        delay(1000)
                        println("launchB:" + Thread.currentThread())
                        getB()
                    }
                    launch.join()
                }
            } catch (e: Exception) {
                println("异常")
            }
        }

打印出日志:

I/System.out: coroutineScope:Thread[main,5,main]
I/System.out: launchA:Thread[main,5,main]
I/System.out: 正常a
I/System.out: launchE:Thread[main,5,main]
I/System.out: 异常

我们可以看到,异常已经被接收,并且不会崩溃,但是 launchB 并没有被执行,launchE 的异常影响了其他协程的正常执行,可以得出:coroutineScope任何一个子协程异常退出,那么整体都将退出

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐