Kotlin 协程 ‘素质双连’

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 ——结构化管理协程—— 避免泄漏

  • 协程的本质——还是线程、线程、线程!一切魔法的背后,都是线程!

关于协程知识点还有很多,后面会持续更新的滴~~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值