Kotlin 协程 ‘素质双连’
第一弹、基本使用 与 基本原理
先上示例
val repos = api.listRepos("getUserInfo") //后台线程
textView.text=repos[0].name // 前台线程
使用上下两行的连续调用的方式 SO 酷!
Android 下引入:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-M2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.40"
什么是协程、Kotlin协程?
- 协程是一种在程序中处理并发任务的方案;也是这种方案的一个组件 ;
- 协程和线程属于一个层级的概念
- 协程中不存在线程,也不存在并行 – 「并行」不是 「并发」!
- Kotlin 的协程不需要关心这个( 讨论的范围是 Kotlin for JVM,在JVM中 kotlin协程 还是基于线程的!JVM 只认识线程 )
- 所以Kotlin for Java 的协程并不属于广义的协程,而是一个线程框架。
- 使用看起来同步的代码,实现异步的操作。
开启一个线程
GlobalScope 是一个全局的 Scope ,可以跨应用所有地方,
所有 自定义Scope 都是在 GlobalScope 下管理的
GlobalScope.launch {
//在大括号中就是 协程本身了
}
比如,有两个方法我们希望它们 通过协程先后执行
先要给 io 线程的方法 加上 suspend (挂起 标记/提醒 )并不会真正的切线程,
suspend 告诉程序 它是一个耗时的挂起函数,请在协程里取执行它!
需要在内部,执行真正的 切线程方法。withContext()
withContext(Dispatchers.IO){
}
private suspend fun io1Code(){
withContext(Dispatchers.IO){
println("camp io1Code ${Thread.currentThread().name}")
}
}
private fun ui1Code(){
println("camp ui1Code ${Thread.currentThread().name}")
}
//指定 切回来的线程(主线程)
GlobalScope.launch(Dispatchers.Main) {
io1Code()
ui1Code()
}
协程的代码怎么写
-
用launch()来开启一段协程
一般需要指定 Dispatchers.Main
-
把在后台工作的函数,写成 suspend 函数
需要在内部调用其他suspend 函数来真正切线程
-
按照一条线写下来,线程会自动切换
额外的天然优势:性能
- 程序什么时候需要切线程
- 工作比较耗时:放在后台
- 调用者无法精准的判断 函数是否 耗时
- 工作比较特殊:放在前台线程 —— 一般来说,是主线程
- 工作比较耗时:放在后台
- 不用协程可以么?
- 不可以,因为它不能 自动切回来
第二弹、Kotlin 协程 深层原理
并发 Concurrency ? 并行 Parallelism ?
- 协程和线程分别是什么?
- 线程:就是线程;
- 协程:线程框架;
- 协程和线程哪个更容易使用?
- 协程更容易使用
- 比 Execute 也更容易使用
- 协程相比线程的优势和劣势?
- 那和Handler 相比?
- 不是一个维度的东西,Handle 是一个特殊场景化的 Execute
Retrofit 对协程的支持
直接上代码吧,在Retrofit API原有基础上,加上 suspend 关键字,尾部的返回值
Call 也可以简化成 PostInfoBean
interface Api {
//KT 协程
@GET("/webapp/shop/findPostDetail")
suspend fun getPostDetailKt (@Query("postId") mPostId:String,@Query("userId") userId:String): PostInfoBean
}
使用的地方
val retrofit = Retrofit.Builder()
.baseUrl("http://app.xxxxx.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val api = retrofit.create(Api::class.java)
//使用‘协程’包裹,并且指定回来的线程是主线程
GlobalScope.launch (Dispatchers.Main){
val postDetailKt = api.getPostDetailKt("922", "0")
//可以使用 postDetailKt 中的值了
}
Kotlin 的协程和 RxJava的对比
-
RxJava 是啥?
-
RxJava 好在哪?
- 可以通过链式操作符,来回切线程
- 使用操作符完成,完成一些特殊操作
/** RxJava **/ /** Zip 操作符 合并两个请求 **/ Single.zip<PostInfoBean,PostInfoBean,Boolean>( api.getPostDetailRxJava("922", "0"), api.getPostDetailRxJava("922", "0"), BiFunction{postBean1,postBean2-> postBean1.entity.postDetailList.get(0).circleName==postBean2.entity.postDetailList.get(0).circleName} ).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object : SingleObserver<Boolean> { override fun onSuccess(t: Boolean) { println(t.toString()) } override fun onSubscribe(d: Disposable) { } override fun onError(e: Throwable) { } })
-
通过 KT 协程 实现 上面 ZIP 的工作
- isSame 就是返回 前台需要的值;
- one.await() ---- await() 也是一个挂起函数
GlobalScope.launch (Dispatchers.Main){ // one 和 two 会同时进行 val one = async { api.getPostDetailKt("922", "0") } val two = async { api.getPostDetailKt("922", "0") } //希望他们去后台等着,返回的结果在前台给我 就行了 var isSame = one.await().entity.postDetailList.get(0).circleName == two.await().entity.postDetailList.get(0).circleName //进行业务操作 println(isSame.toString()) }
-
协程跟它相比有什么不同?
协程会稍微简单点,性能 >= RxJava
协程在异常处理上,目前有一些问题(跟进中。。。)
CoroutineScope
上面看惯了 GlobalScope,下面我们来介绍一下 CoroutineScope 吧
Coroutine Leak 协程泄漏
本质上就是 ‘线程泄漏 ’
在RxJava,执行连续请求操作,但是操作没完成,我们就退出页面了,怎么处理?
在退出页面之前,终止 Disposable 的任务,防止出现 ‘线程还在被使用 ,没有释放掉 ’ 而引发 ‘线程泄漏 ’ 。
var disposable: CompositeDisposable = CompositeDisposable()
...
在 onSubscribe 回调中 把 Disposable 加进去
override fun onSubscribe(d: Disposable){
disposable.add(d)
}
...
退出页面前 执行 disposable.dispose()
override fun onDestroy(){
disposable.dispose()
super.onDestroy()
}
在来看看,Kotlin 协程 遇到这样的问题怎么解决
比如: 执行到 b 方法的时候,用户退出了怎么办
GlobalScope.launch{
a()
b() <- 执行到这里,用户退出了
c()
}
解决:(一)创建的 mainScope 相当 这个‘协程;活在这个 scope 里面
在退出前cancel 掉它就好了 。从而解决Coroutine Leak 协程泄漏
var mainScope: CoroutineScope = MainScope() // 主线程的 Scope
mainScope.launch (Dispatchers.Main){
a()
b() <- 执行到这里,用户退出了
c()
}
...
override fun onDestroy(){
mainScope.cancel()
super.onDestroy()
}
解决:(二)
引入 ‘androidx.lifecycle:lifecycle-runtime-ktx:2.2.0’
直接使用 lifecycleScope ,是 androidx 团队帮我们实现的,看名字就能猜到
“生命周期” 这事就交给它了 哈哈!
lifecycleScope.launch {
...
}
那么RxJava 可以按顺序执行,A、B、C 个方法,Kotlin 协程怎么做呢?
可以这样写: launch {} 也是一个 协程
/**比如:需要先执行 ioAcode() 和 ioBcode() 这两个耗时操作之后
再去执行 uiCcode,
使用 coroutineScope ,会将整个 mainScope.launch 挂起,等它里 面的 “ 小协程 ”(launch{})都执行完毕了才会
在往下执行**/
mainScope.launch {
coroutineScope {
launch {
ioAcode()
}
launch {
ioBcode()
}
}
uiCcode()
}
Kotlin 协程 与 ViewModel 、LiveData 的结合使用?
- 写 YuViewModel.kt 文件
class YuViewModel: ViewModel() {
val postInfo = liveData<PostInfoBean>{
emit(getPostInfo())
}
private suspend fun getPostInfo():PostInfoBean{
val retrofit = Retrofit.Builder()
.baseUrl("http://app.xxxxxx.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
val api = retrofit.create(Api::class.java)
return api.getPostDetailKt("922", "0")
}
}
- 具体使用
//var yuViewModel : YuViewModel by viewModels()
var yuViewModel = YuViewModel()
yuViewModel.postInfo.observe(this, androidx.lifecycle.Observer {
it-> println(it.entity.postDetailList[0].circleName)
})
KT协程 ‘本质探秘’
-
协程是怎么切线程的?
-
协程为什么可以【挂起】,却不卡主线程?
-
主线程在执行的时候,执行到 协程的部分 会将其推到后台,然后继续往下执行
-
其原理 和 下面代码一样
thread { Thread.sleep(3000) runOnUiThread { textName.text = "你好" } }
-
-
Thread.sleep() 和 协程的 delay()
-
delay() 性能更好?并不一定更好
一个是 Blocking Thread (阻塞的) vs Suspending Coroutines (挂起的)
lifecycleScope.launch {
delay(1000)
textView.text = "1000毫秒以后了"
}
它的效果是,主界面丝毫不卡的情况下,1秒后更新文字。
- 这么看来 delay() 是 ‘真香’ 啊
总结
-
协程是什么?—— 线程框架
-
怎么写?—— launch() + withContext(), 以及其他一系列函数 (async delay)
-
协程性能更高吗?——本质上不会,甚至可能更差;但实际上会的
因为 suspend 的标记功能 (它会将一些小小的耗时功能 放到后台执行 )
-
CoroutineScope ——结构化管理协程—— 避免泄漏
-
协程的本质——还是线程、线程、线程!一切魔法的背后,都是线程!
delay(1000)
textView.text = "1000毫秒以后了"
}
它的效果是,主界面丝毫不卡的情况下,1秒后更新文字。
- 这么看来 delay() 是 ‘真香’ 啊
总结
-
协程是什么?—— 线程框架
-
怎么写?—— launch() + withContext(), 以及其他一系列函数 (async delay)
-
协程性能更高吗?——本质上不会,甚至可能更差;但实际上会的
因为 suspend 的标记功能 (它会将一些小小的耗时功能 放到后台执行 )
-
CoroutineScope ——结构化管理协程—— 避免泄漏
-
协程的本质——还是线程、线程、线程!一切魔法的背后,都是线程!