一手遮天 Android - kotlin 协程: 协程基础(CoroutineScope, 为 CoroutineScope 扩展方法, runBlocking, launch, async, await, suspend, withContext, 设置/获取 CoroutineScope 的名称)
一手遮天 Android - kotlin 协程: 协程基础(CoroutineScope, 为 CoroutineScope 扩展方法, runBlocking, launch, async, await, suspend, withContext, 设置/获取 CoroutineScope 的名称)
示例如下:
/kotlin/coroutine/Demo1.kt
/**
* coroutine - 协程
* 本利用于演示协程基础,包括 CoroutineScope, 为 CoroutineScope 扩展方法, runBlocking, launch, async, await, suspend, withContext, 设置/获取 CoroutineScope 的名称
*
* 进程是资源分配的最小单位,不同进程之间资源都是独立的
* 线程是 CPU 调度的基本单位,本身并不拥有系统资源,所有线程会共享进程的资源
* 协程可以认为是运行在线程上的代码块,协程提供的挂起操作会使协程暂停执行,而不会导致线程阻塞。一个线程内部即使创建大量的协程都不会有任何问题
* 一个线程内,同一时刻只会有一个协程在运行,其他协程挂起等待,不同协程之间的切换不涉及内核(线程的切换会涉及到内核),所以切换代价更小,更轻量级
* 一个协程内出现挂起时只是暂停,其所属线程可以运行其他逻辑
* 比如你在主线程启动一个协程然后挂起,其并不会阻塞主线程
* 一个协程并不绑定在任何特定的线程上(如果你不强制指定某一特定线程的话),它可以在一个线程中暂停执行,在另一个线程中继续执行
* 比如你在 Dispatchers.Default 启动一个协程,它开始可能运行在 worker-1 线程,然后协程挂起,然后协程再恢复,此时它可能会运行在 worker-2 线程
* 如果你是在 Dispatchers.Main 启动一个协程,则它只会运行在主线程中
* 如果你是在 newSingleThreadContext("myThread") 启动一个协程,则它只会运行在 myThread 线程中
*/
package com.webabcd.androiddemo.kotlin.coroutine
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.webabcd.androiddemo.R
import kotlinx.android.synthetic.main.activity_kotlin_coroutine_demo1.*
import kotlinx.android.synthetic.main.activity_kotlin_helloworld.button1
import kotlinx.android.synthetic.main.activity_kotlin_helloworld.textView1
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*
class Demo1 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kotlin_coroutine_demo1)
// 演示 CoroutineScope.launch
button1.setOnClickListener {
sample1()
}
// 演示 runBlocking
button2.setOnClickListener {
sample2()
}
// 演示 MainScope, GlobalScope
button3.setOnClickListener {
sample3()
}
// 演示 async, await
button4.setOnClickListener {
sample4()
}
// 演示 suspend 函数以及如何为 CoroutineScope 扩展方法
button5.setOnClickListener {
sample5()
}
// 演示 withContext
button6.setOnClickListener {
sample6()
}
// 演示如何设置/获取 CoroutineScope 的名称
button7.setOnClickListener {
sample7()
}
}
fun sample1() {
/**
* CoroutineScope.launch { } - 在当前线程中启动一个新的协程
* CoroutineScope(Dispatchers.Default).launch { } - 在子线程中启动一个新的协程(适合 cpu 密集型的工作)
* CoroutineScope(Dispatchers.IO).launch { } - 在子线程中启动一个新的协程(适合存储或网络等 IO 操作的工作)
* CoroutineScope(Dispatchers.Main).launch { } - 在主线程(UI 线程)中启动一个新的协程
* CoroutineScope(Dispatchers.Unconfined).launch { } - 在当前线程中启动一个新的协程,然后在第一个挂起点恢复后再从其他某线程中恢复协程
* CoroutineScope(newSingleThreadContext("myThread")).launch { } - 新开一个名为 myThread 的线程,并在此线程中启动一个新的协程
*/
// launch 的返回值是一个 Job 对象,后续再说
val job = CoroutineScope(Dispatchers.Default).launch {
// this 是一个 CoroutineScope 对象,通过他可以启动新的协程
/**
* launch { } - 在当前线程中启动一个新的协程
* launch(Dispatchers.Default|Dispatchers.IO|Dispatchers.Main) { } - 在指定线程中启动一个新的协程
*/
this.launch(Dispatchers.Default) {
appendMessage("x") // x(DefaultDispatcher-worker-2)
delay(1000) // delay 是一个 suspend 函数
appendMessage("y") // y(DefaultDispatcher-worker-2)
}
appendMessage("b") // b(DefaultDispatcher-worker-1)
delay(1000) // delay 是一个 suspend 函数
appendMessage("c") // c(DefaultDispatcher-worker-1)
}
/**
* 注意:
* 1、一般来说本例的执行顺序是 a b x,但是因为协程足够快,所以执行顺序也可能是 b a x 之类的
* 2、本例中 x 和 y 不一定运行在同一个线程,b 和 c 不一定运行在同一个线程
* 因为一个协程并不绑定在任何特定的线程上,它可以在一个线程中暂停执行,在另一个线程中继续执行
*/
appendMessage("a") // a(main)
}
fun sample2() {
/**
* runBlocking { } - 阻塞当前线程,直到内部的所有协程执行完毕
* 在 { } 中的 this 是一个 CoroutineScope 对象,通过他可以启动新的协程
*/
runBlocking {
this.launch(Dispatchers.Default) {
appendMessage("b") // b(DefaultDispatcher-worker-2)
delay(1000) // delay 是一个 suspend 函数
appendMessage("c") // c(DefaultDispatcher-worker-2)
}
appendMessage("a") // a(main)
}
appendMessage("d") // d(main)
}
fun sample3() {
/**
* MainScope().launch - 相当于 CoroutineScope(Dispatchers.Main).launch
* GlobalScope.launch - 与 CoroutineScope.launch 的区别是 GlobalScope 是作用域为全局的顶级协程
* 也就是说如果你取消一个协程的话,那么所有其内启动的协程也都会被取消,除了 GlobalScope 方式启动的协程
*
* 注:MainScope() 和 GlobalScope 都实现了 CoroutineScope 接口
*/
val job1 = MainScope().launch {
delay(500) // b(main)
appendMessage("b")
}
val job2 = GlobalScope.launch(Dispatchers.Default) {
delay(1000)
appendMessage("c") // c(DefaultDispatcher-worker-1)
}
appendMessage("a") // a(main)
}
fun sample4() {
/**
* async - 其和 launch 的区别是:
* 1、launch 返回的是 Job 对象,async 返回的是 Deferred<T> 对象(注:Deferred<T> 继承自 Job)
* Deferred<T> 可以通过 await() 在当前线程阻塞,直到他执行完
* 2、launch 启动的协程是没有返回值的,async 启动的协程可以有返回值
*/
val task1 = CoroutineScope(Dispatchers.Default).async {
delay(2000)
appendMessage("e") // e(DefaultDispatcher-worker-1)
}
runBlocking { // this: CoroutineScope
// this 是一个 CoroutineScope 对象,通过他可以启动新的协程
// 无返回值的任务
val task2 = this.async {
delay(500)
appendMessage("a") // a(main)
}
// 有返回值的任务
val task3 = this.async(Dispatchers.Default) {
delay(1000)
appendMessage("b") // b(DefaultDispatcher-worker-1)
"c" // 返回值
}
// await() - 在当前线程阻塞,直到任务执行完
task2.await()
appendMessage(task3.await()) // c(main)
}
// 本例中 task1, task2, task3 是并行执行的,主线程会阻塞直到 task2 和 task3 执行完
appendMessage("d") // d(main)
}
fun sample5() {
/**
* 在协程中调用的函数必须是 suspend 函数,也就是说在 launch 或 async 中调用的函数必须是 suspend 函数
* 也可以为 CoroutineScope 扩展方法,然后在协程中调用
*/
var job = CoroutineScope(Dispatchers.Default).launch {
// fun1() 执行完后执行 fun2(),fun2() 执行完后执行 fun3()
// fun3() 执行完后执行 fun4(),不等 fun4() 执行完就执行 fun5()
fun1() // 04:17:37.168: a(DefaultDispatcher-worker-1)
fun2() // 04:17:38.193: b(DefaultDispatcher-worker-1)
fun3() // 04:17:39.214: c(DefaultDispatcher-worker-1)
fun4() // 04:17:40.222: d(DefaultDispatcher-worker-1)
fun5() // 04:17:40.253: e(DefaultDispatcher-worker-1)
}
}
// suspend 函数,可以在协程或其他 suspend 函数中被调用
suspend fun fun1() {
delay(1000)
appendMessage("a")
}
// coroutineScope - 在 { } 中的所有逻辑执行完毕后才会返回
// coroutineScope 挂起时只是暂停,其所属线程可以运行其他逻辑
suspend fun fun2() = coroutineScope { // this: CoroutineScope
// this 是一个 CoroutineScope 对象,通过他可以启动新的协程
// this.launch { }
// this.async { }
delay(1000)
appendMessage("b")
}
// runBlocking - 在 { } 中的所有逻辑执行完毕后才会返回
// runBlocking 挂起时会阻塞其所属线程
suspend fun fun3() = runBlocking { // this: CoroutineScope
// this 是一个 CoroutineScope 对象,通过他可以启动新的协程
// this.launch { }
// this.async { }
delay(1000)
appendMessage("c")
}
// 为 CoroutineScope 扩展一个 fun4() 方法
fun CoroutineScope.fun4() = launch {
delay(1000)
appendMessage("d")
}
// 为 CoroutineScope 扩展一个 fun5() 方法
fun CoroutineScope.fun5() = async {
delay(1000)
appendMessage("e")
}
fun sample6() {
val job = CoroutineScope(Dispatchers.Default).launch {
delay(1000)
appendMessage("b") // b(DefaultDispatcher-worker-1)
val task = async {
delay(1000)
appendMessage("d") // d(DefaultDispatcher-worker-1)
}
// withContext() - 在指定的线程中执行相关逻辑,执行完毕后再把线程切回去
// withContext() 可以有返回值
// withContext() 必须在协程中或 suspend 函数中调用
val v = withContext(Dispatchers.Main) {
appendMessage("c") // c(main)
task.await()
appendMessage("e") // e(main)
"f" // 返回值
}
appendMessage(v) // f(DefaultDispatcher-worker-1)
}
appendMessage("a") // a(main)
}
fun sample7() {
/**
* CoroutineName() - 指定 CoroutineScope 的名称
* currentCoroutineContext()[CoroutineName]?.name - 获取当前 CoroutineScope 的名称
*/
// 如需为 CoroutineContext 定义多个元素的话,就用 + 运算符,比如本例中的 Dispatchers.Default + CoroutineName("myCoroutine")
val job = CoroutineScope(Dispatchers.Default + CoroutineName("myCoroutine")).launch {
var coroutineName = currentCoroutineContext()[CoroutineName]?.name
appendMessage("coroutineName:$coroutineName")
}
}
fun appendMessage(message: String) {
val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.ENGLISH)
val time = dateFormat.format(Date());
val threadName = Thread.currentThread().name
CoroutineScope(Dispatchers.Main).launch{
val log = "$time: $message($threadName)"
textView1.append(log);
textView1.append("\n");
Log.d("coroutine", log)
}
}
}
/layout/activity_kotlin_coroutine_demo1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="CoroutineScope.launch"/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="runBlocking"/>
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="MainScope, GlobalScope"/>
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="async, await"/>
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="演示 suspend 函数以及如何为 CoroutineScope 扩展方法"/>
<Button
android:id="@+id/button6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="withContext"/>
<Button
android:id="@+id/button7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="设置/获取 CoroutineScope 的名称"/>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>