一文学会 使用Kotlin Coroutine协程
协程的概念最早由Melvin Conway在1963年提出并实现,用于简化COBOL编译器的词法和句法分析器间的协作,当时其对协程的描述是“行为与主程序相似的子线程”。
协程可看作轻量级的线程
,目前 Python
、go
、Kotlin
等开发语言均已支持协程,Java
可以通过第三方扩展的方式来使用协程。
关于线程、协程两者的对比,可以简要叙述如下:
线程:
线程由操作系统调度,线程切换或线程阻塞由操作系统和CPU调度实现;协程:
协程运行于操作系统的用户态,其依赖于线程来实现,通过用户态程序控制,尽量减少或避免因线程阻塞造成的操作系统与CPU开销
。
与线程相比不同点在于,协程挂起时不需要阻塞其运行的线程
。协成挂起
期间,其对应的线程可以被分配其他协程任务来执行
,待该协程任务挂起结束再次开始时,将该协成再次交由某个线程来继续执行
(挂起期间,类似于将该协程任务添加到了某个任务队列中
)。
目前 Kotlin协程 在GitHub上的最新release版本为1.6.0
,其对应的 GitHub源码地址为:https://github.com/Kotlin/kotlinx.coroutines
- GlobalScope 使用简述
- CoroutineScope 使用简述
一、GlobalScope 使用简述
GlobalScope
继承于 CoroutineScope
(接口),其源码实现是一个全局的单例
,因为是单例,其生命周期跟随与整个应用程序的生命周期;可使用 GlobalScope.launch
启动一个顶层协程。
- GlobalScope 使用举例
- GlobalScope 简要说明
1.1 GlobalScope 使用举例
引入依赖包:
首先需要引入
Kotlin协程 相关依赖库
。
目前 Kotlin协程 在GitHub上的最新release版本为1.6.0
,其对应的 GitHub源码地址为:https://github.com/Kotlin/kotlinx.coroutines
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
// 协程Android库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
线程切换:
GlobalScope.launch 执行任务时,切换不同线程
:
// ------------应用举例-----------
// GlobalScope 执行任务时,切换不同线程
//
// 1、启动一个协程 (主线程 执行)
var job = GlobalScope.launch(context = Dispatchers.Main) {
// TODO Main线程
}
// 2、启动一个协程 (异步线程:线程数量默认为64)
var job = GlobalScope.launch(context = Dispatchers.IO) {
// TODO 异步线程:线程数量默认为64
}
// 3、启动一个协程 (异步线程:线程的最大数量等于 CPU 内核数)
var job = GlobalScope.launch(context = Dispatchers.Default) {
// TODO 异步线程:线程的最大数量等于 CPU 内核数
}
// 4、启动一个协程 (当前线程 执行)
var job = GlobalScope.launch(context = Dispatchers.Unconfined) {
// TODO 当前线程执行
}
// 取消协程
// job.cancel()
GlobalScope.launch使用:
GlobalScope.launch 执行异步网络任务,返回结果更新UI界面
。以下举例中,涉及到以下关键词或方法:
suspend
关键词:
当携程出现阻塞等待情况时,用于挂起当前的协程,并保存所有局部变量。withContext
方法:
将当前协程 移至一个I/O线程中执行异步操作。
// ------------应用举例-----------
// GlobalScope 执行异步网络任务,返回结果更新UI界面
//
class GlobalScopeActivity : AppCompatActivity() {
//
lateinit var job: Job;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// GlobalScope.launch 使用
launchGlobalScope();
}
/**
* GlobalScope.launch 使用
*/
private fun launchGlobalScope() {
// GlobalScope.launch 使用
job = GlobalScope.launch(Dispatchers.Main) {
// TODO 执行主线程任务 // main thread
// getNetData 异步获取网络数据
val netStr: String = getNetData() // IO thread
// 回到主线程
textView?.text = netStr // main thread
}
}
/**
* 异步获取网络数据
* suspend 关键词,当携程出现阻塞等待情况时,用于挂起当前的协程,并保存所有局部变量
*/
private suspend fun getNetData(): String {
// withContext 将当前协程 移至一个I/O线程中执行异步操作
return withContext(context = Dispatchers.IO) {
// TODO IO线程 网络请求
// 返回值为String的Http同步网络请求
HttpAgent.get_Sync(
"https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
String::class.java
)
}
}
}
GlobalScope.async使用:
GlobalScope.async 执行异步网络任务,返回结果更新UI界面
。以下举例中,涉及到以下关键词和方法:
async
方法:
async
方法会启动一个新的协程;await
关键词:
async
方法启动的协程,可以使用一个名为await
的关键词,等待耗时方法返回执行结果。
// ------------应用举例-----------
// GlobalScope.async 执行异步网络任务,返回结果更新UI界面
//
class GlobalScopeActivity : AppCompatActivity() {
//
lateinit var job: Job;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// GlobalScope.async 使用
asyncGlobalScope()
}
// ---------------------GlobalScope.async-----------------------
/**
* GlobalScope.async 使用:
* async 方法会启动一个新的协程,并使用一个名为 await 的关键词,等待耗时方法执行结束的返回结果。
*/
private fun asyncGlobalScope() {
// GlobalScope.async 使用
job = GlobalScope.launch(Dispatchers.Main) {
// TODO 执行主线程任务 // main thread
// 第一个异步网络请求
val taobaoData = async(Dispatchers.IO) { // IO thread
// TODO IO线程 网络请求
// 返回值为String的Http同步网络请求
HttpAgent.get_Sync(
"https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
String::class.java
)
}
// 第二个异步网络请求
val baiduData = async(Dispatchers.IO) { // IO thread
// TODO IO线程 网络请求
// 返回值为String的Http同步网络请求
HttpAgent.get_Sync(
"https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=18701636688", null, null,
String::class.java
)
}
// 待两个结果都返回后
val resultData: String = (taobaoData.await() + baiduData.await())
// 展示UI
textView?.text = resultData // main thread
}
}
}
1.2 GlobalScope 简要说明
不建议直接使用 GlobalScope ?
GlobalScope
的源码实现是一个全局的单例
( Kotlin 中单例对象通过 object 关键字实现),其对应的源码如下:
// GlobalScope 源码:GlobalScope为单例
public object GlobalScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
因为GlobalScope
是一个单例,又因为 GlobalScope 对象并未与Android应用生命周期组件相关联,因此需要自己管理 GlobalScope 所创建的 Coroutine。
否则通过 GlobalScope
启动的协程,其生命周期
将与整个Android应用程序的生命周期
相同,只要整个应用程序还在运行且协程的任务还未结束,协程就可以一直运行。
因此,一般不建议直接使用 GlobalScope
来创建 Coroutine协程。
协程不会阻塞线程?
文章一开始说过:
协成挂起期间,其对应的线程可以被分配其他协程任务来执行,待该协程任务挂起结束再次开始时,将该协成再次交由某个线程来继续执行(挂起期间,类似于将该协程任务添加到了某个任务队列中)。
这里写一段代码来验证一下:
// 举例 启动一个协程 (IO异步线程)
GlobalScope.launch(context = Dispatchers.IO) {
// 启动协程
Log.d(TAG, "[GlobalScope] start ")
// 挂起 2 秒钟
delay(2000)
// 继续协程
Log.d(TAG, "[GlobalScope] currThread: " + Thread.currentThread().name)
// 启动协程
launch {
Log.d(TAG, "[launch A] Begin")
delay(400)
Log.d(TAG, "[launch A] currThread: " + Thread.currentThread().name)
Log.d(TAG, "[launch A] end")
}
// 启动协程
launch {
Log.d(TAG, "[launch B] Begin")
delay(300)
Log.d(TAG, "[launch B] currThread: " + Thread.currentThread().name)
Log.d(TAG, "[launch B] end")
}
// 结束协程
Log.d(TAG, "[GlobalScope] end ")
}
// 运行结果如下:
// [GlobalScope] start
// [GlobalScope] currThread: DefaultDispatcher-worker-1
// [launch A] Begin
// [launch B] Begin
// [Coroutine] end
// [launch B] currThread: DefaultDispatcher-worker-1
// [launch B] end
// [launch A] currThread: DefaultDispatcher-worker-1
// [launch A] end
通过以上运行结果,可以看出 launch A
被挂起时,其对应的线程 DefaultDispatcher-worker-1
开始执行 launch B
相关任务。delay()
方法并未阻塞其对应的执行线程。
二、CoroutineScope 使用简述
前边说过 不建议直接使用 GlobalScope,GlobalScope是一个单例,其生命周期与Android应用生命周期相同,而且并未与Android生命周期组件(Activity、Service等进行关联),其声明周期需要研发人员自己管理
。
之前并未提及建议的协程使用方式,这一节给出对应的代码使用方式举例:
CoroutineScope 使用举例:
通过 CoroutineScope
来实现一个自己的协程作用域,通过launch
启动一个协程,通过调用 scope.cancel()
方法,可以取消该 scope 下所有正在进行的任务。
// ------------应用举例-----------
// CoroutineScope 执行异步网络任务,返回结果更新UI界面
//
class CoroutineScope01Activity : AppCompatActivity() {
// Job 对象
lateinit var scope: CoroutineScope
// Activity的onCreate方法
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建 CoroutineScope (用于管理CoroutineScope中的所有携程)
scope = CoroutineScope(Job() + Dispatchers.Main)
// 获取网络数据,更新UI
asyncCoroutine();
}
override fun onDestroy() {
super.onDestroy()
// 当 Activity 销毁的时候取消该 Scope 管理的所有协程。
scope.cancel()
}
/**
* CoroutineScope 使用
*/
private fun asyncCoroutine() {
// CoroutineScope 的 launch 方法
scope.launch(Dispatchers.Main) {
// TODO 执行主线程任务 // main thread
// 第一个异步网络请求
val taobaoData = async(Dispatchers.IO) { // IO thread
// TODO IO线程 网络请求
// 返回值为String的Http同步网络请求
HttpAgent.get_Sync(
"https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
String::class.java
)
}
// 第二个异步网络请求
val baiduData = async(Dispatchers.IO) { // IO thread
// TODO IO线程 网络请求
// 返回值为String的Http同步网络请求
HttpAgent.get_Sync(
"https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=18701636688", null, null,
String::class.java
)
}
// 待两个结果都返回后
val resultData: String = (taobaoData.await() + baiduData.await())
// 展示UI
textView?.text = resultData // main thread
}
}
}
Activity 实现 CoroutineScope 接口
// ------------应用举例-----------
// CoroutineScope 执行异步网络任务,返回结果更新UI界面
//
// Activity 实现 CoroutineScope 接口
class CoroutineScopeActivity : AppCompatActivity(), CoroutineScope {
// Job 对象
lateinit var job: Job;
// 重写 CoroutineScope 接口中的属性
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
// Activity的onCreate方法
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "---onCreate---")
// 创建 Job (用于管理CoroutineScope中的所有携程)
job = Job()
// 获取网络数据,更新UI
asyncCoroutine();
}
override fun onDestroy() {
super.onDestroy()
// 当 Activity 销毁的时候取消该 Scope 管理的 job。
// 这样该 Scope 内创建的子 Coroutine 都会被自动的取消。
job.cancel()
}
/**
* CoroutineScope 使用
*/
private fun asyncCoroutine() {
// CoroutineScope 的 launch 方法
job = launch(Dispatchers.Main) {
// TODO 执行主线程任务 // main thread
// 第一个异步网络请求
val taobaoData = async(Dispatchers.IO) { // IO thread
// TODO IO线程 网络请求
// 返回值为String的Http同步网络请求
HttpAgent.get_Sync(
"https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
String::class.java
)
}
// 第二个异步网络请求
val baiduData = async(Dispatchers.IO) { // IO thread
// TODO IO线程 网络请求
// 返回值为String的Http同步网络请求
HttpAgent.get_Sync(
"https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=18701636688", null, null,
String::class.java
)
}
// 待两个结果都返回后
val resultData: String = (taobaoData.await() + baiduData.await())
// 展示UI
textView01?.text = resultData // main thread
}
}
}
三、源码下载
源码下载地址如下:
https://download.csdn.net/download/aiwusheng/84095682
参考
developer kotlin coroutines:
https://developer.android.google.cn/kotlin/coroutines?hl=zh-cn#kts
Kotlin CoroutineScope:
http://blog.chengyunfeng.com/?p=1086
= THE END =
文章首发于公众号”CODING技术小馆“,如果文章对您有帮助,欢迎关注我的公众号。