笔记仅做自己学习用,方便自己复习知识。若正好可以帮助到Viewer,万分欣喜~

若博客侵权,扔物线大大不允许放上面,麻烦告知


本文是扔物线Kotlin第二期协程训练营的第二篇文章

没看过第一篇文章的可以先看第一篇:https://blog.csdn.net/bluerheaven/article/details/106969835

目录

一、Retrofit对协程的支持

二、Retrofit和RxJava的结合使用

三、合并网络请求

1. 自己实现合并网络请求

2. 用Rxjava实现合并网络请求

3. 用Kotlin协程实现合并网络请求

四、协程和Jetpack架构组件

1. 协程泄漏

2. Jetpack组件对协程的支持

五、协程和线程

六、本质探秘

1. 协程是怎么切线程的?

2. 协程为什么可以从主线程“挂起”,却不卡主线程?

3. 协程的delay()和Thread.sleep()


一、Retrofit对协程的支持

先来看一下Kotlin使用Retrofit的方式,不适用协程:

    @GET("users/{user}/repos")fun listRepos(@Path("user") user: String): Call<List<Repo>>
        val retrofit = Retrofit.Builder().baseUrl("https://api.github.com/").addConverterFactory(GsonConverterFactory.create()).build()val api = retrofit.create(Api::class.java)api.listRepos("rengwuxian").enqueue(object:Callback<List<Repo>?>{override fun onFailure(call: Call<List<Repo>?>, t: Throwable) {}override fun onResponse(call: Call<List<Repo>?>, response: Response<List<Repo>?>) {textView.text = response.body()?.get(0).name}})

第一段代码定义Api.kt接口类,第二段代码,创建Retrofit对象,并动态代理给val api,通过api调用执行网络请求。

再来看看使用Kotlin协程的写法:

@GET("users/{user}/repos") suspend fun listReposKt(@Path("user") user: String): List<Repo>
        val retrofit = Retrofit.Builder().baseUrl("https://api.github.com/").addConverterFactory(GsonConverterFactory.create()).build()val api = retrofit.create(Api::class.java)GlobalScope.launch(Dispatchers.Main) {try {val repos = api.listReposKt("rengwuxian") // 后台textView.text = repos[0].name // 前台} catch (e: Exception) {textView.text = e.message // 出错} }

第一段代码统一定义了接口,第二段代码同样创建了Retrofit对象,但是在使用时就不一样了。使用Kotlin协程不再需要回调,直接把前台后台代码按顺序写下来就可以了。(这里我想吐槽:onFailure用try catch替代了,瞬间感觉有点low...)

训练营的第一篇文章说过,suspend只是起到标记和提醒的作用,并不能切换线程,那么这里是怎么切线程的?答案是,Retrofit动态帮助我们创建了代理类,切换了线程。(Retrofit对Kotlin的支持还真是强大)

这里还有个坑:如果不写try catch代码,也不会报错,因为Kotlin没有checked Exception。而Java是有的。(如果在Java里写可能抛出异常的代码,而没有try catch,编译器会报错)

二、Retrofit和RxJava的结合使用

    @GET("users/{user}/repos")fun listReposRx(@Path("user") user: String): Single<List<Repo>>
        val retrofit = Retrofit.Builder().baseUrl("https://api.github.com/").addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())).build()val api = retrofit.create(Api::class.java)api.listReposRx("rengwuxian").observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<List<Repo>?> {override fun onSuccess(t: List<Repo>) {}override fun onSubscribe(d: Disposable) {}override fun onError(e: Throwable) {}})

同样是先定义Api.kt接口列,再创建Retrofit实例后,通过api动态代理进行网络请求。

这里使用了RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())让网络请求在线程中执行。.observeOn(AndroidSchedulers.mainThread())又保证了结果在主线程进行处理。

这里有个快捷键的小技巧,在写sucribe里的回调时,可以使用快捷键Alt + shift + 空格调出都有哪些subscribe参数可用。我的快捷键是Eclipse的,快捷键名称是smart type。

对比第一节的Kotlib与Retrofit结合的代码,两者好像都可以...

三、合并网络请求

1. 自己实现合并网络请求

先看代码:

        var name = 0var respo1: String? = nullvar respo2: String? = nullapi.listRepos("bluerheaven").enqueue(object : Callback<List<Repo>?> {override fun onFailure(call: Call<List<Repo>?>, t: Throwable) {name = name or 0b1000callback.onError(t.toString())}override fun onResponse(call: Call<List<Repo>?>, response: Response<List<Repo>?>) {respo1 = response.body()?.get(0)?.namename = name or 0b0001if (name == 0b0011) {callback.success("$respo1 + $respo2")}}})api.listRepos("google").enqueue(object : Callback<List<Repo>?> {override fun onFailure(call: Call<List<Repo>?>, t: Throwable) {name = name or 0b1000callback.onError(t.toString())}override fun onResponse(call: Call<List<Repo>?>, response: Response<List<Repo>?>) {respo2 = response.body()?.get(0)?.namename = name or 0b0010if (name == 0b0011) {callback.success("$respo1 + $respo2")}}})

这里通过状态位来判断两个接口的情况,有任何一个接口失败,就回调失败。当两个接口同时成功后,回调成功。实现了合并请求。这里Retrofit保证了name的操作都是在主线程进行,如果不能保证都在主线程运行,需要加锁后再操作做。

这样的代码逻辑,虽然也能实现功能,但逻辑复杂。

先来看看RxJava的合并网络请求实现

2. 用Rxjava实现合并网络请求

熟悉RxJava的人都知道,可以用zip操作符来进行网络请求的合并,两个接口都成功后,返回结果。代码如下:

        Single.zip<List<Repo>, List<Repo>, String>(api.listReposRx("bluerheaven"),api.listReposRx("google"),BiFunction{repos1, repos2 -> "${repos1[0].name} - ${repos2[0].name}"}).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<String?> {override fun onSuccess(combined: String) {textView.text = combined}override fun onSubscribe(d: Disposable) {}override fun onError(e: Throwable) {textView.text = e.message}})

3. 用Kotlin协程实现合并网络请求

这里要使用到协程的async函数,它和launch一样,都是开启一个协程。async的好处是,我们可以在以后通过await方法来获取代码的运行结果。如果async代码执行完成后调用await获取值,就会立即得到结果,如果在async执行中调用await,就会挂起,等待async返回结果。两个async代码块在协程里,会并发执行,第二个async并不会等待第一个async执行完成。

来看看使用Kotlin实现合并网络请求的代码:

        GlobalScope.launch(Dispatchers.Main) { try {val bluerheaven = async { api.listReposKt("bluerheaven") }val google = async { api.listReposKt("google") }textView.text = "${bluerheaven.await()[0].name} + ${google.await()[0].name}"}catch (e: Exception) {textView.text = e.message}}

是不是更简单了~~~

对比一下协程和RxJava:

1. 功能应用场景很接近

2. 协程写法更简单一点

3. Rxjava性能上要比协程好一些

四、协程和Jetpack架构组件

1. 协程泄漏

什么是协程泄漏?协程泄漏就是当你已经不需要这个协程了,但这个协程还在工作。

举个栗子,使用协程做以下工作: 从网络上取图片 -> 裁剪、放缩图片 -> 圆角图片,加边框 -> 显示

如果在第一步从网络上获取图片时,用户已经退出了Activity,那么这个协程会继续工作。这就是协程泄漏。

协程泄漏的本质还是线程泄漏。

那退出页面时,怎样取消协程呢?答案是通过协程的cancel方法取消:

        val job = GlobalScope.launch(Dispatchers.Main) {...}job.cancel()

有没有办法统一取消所有的协程呢?有的,它是CoroutineScope -- “结构化并发”。其实每一个协程(包括上面的GlobalScope)都在运行一个CoroutineScope,可以理解为它是对多个协程的集中管理地。

    val scope = MainScope()init {scope.launch {// todo something}scope.launch {// todo something}scope.cancel()  //取消所有以scope开启的协程}

可以自定义一个MainScope,表示协程主要在主线程,以这个scope开启的所有协程,当调用scope.cancel时,都会统一取消。

2. Jetpack组件对协程的支持

先在app下的build.gradle中添加ktx -- KoTlin eXtention 的依赖:

    implementation 'androidx.activity:activity-ktx:1.1.0'implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

就可以这样用:

        lifeCycleScope.launch{}lifeCycleScope.launchWhenResume{}viewModelScope.launch {}

通过lifeCycleScope启动协程,就不需要再onDestroy里取消,因为会自动做这件事。lifeCycleScope的launchWhenResume意思就是它本身的意思,就是在调用Activity的onResume时启动这个协程。viewModelScope同样可以启动一个协程。

五、协程和线程

1. 协程和线程分别是什么?

线程就是线程,协程是一个线程库

2. 协程和线程哪个更容易使用?

当然是协程了,你作为一个上层库,还没原型好使,那要你何用?

所以应该问:协程和Executor,哪个容易使用?

一般来说还是协程,这东西实在有点太突破了,关键就是它的“消除回调”

3. 协程相比线程的优势和劣势?

优势就是好用,强大;劣势呢?上手太难了

那么...同样问一下,和Executor相比呢,有什么劣势?

一样,难上手。没办法,它太新了。

4.  那和Handler相比呢?
首先,其实没法比,它俩也不是一个维度的东西。Handler相当于一个“只负责Android中切线程”的特殊场景化的Executor,在Android中你要想让协程切刀主线程,还是得用Handler。

如果我就是要强行对比协程和Handler,它有什么劣势?

我们要真是从易用性上面来说,你用协程来往主线程切,还真的是比直接用Handler更好些、更方便的。这个...应该也算是个比较强行的优势?

六、本质探秘

1. 协程是怎么切线程的?

这里来追一下这段源码:

        GlobalScope.launch(Dispatchers.Main) {}

点进launch

public fun CoroutineScope.launch(context: CoroutineContext = EmptyCoroutineContext,start: CoroutineStart = CoroutineStart.DEFAULT,block: suspend CoroutineScope.() -> Unit
): Job {val newContext = newCoroutineContext(context)val coroutine = if (start.isLazy)LazyStandaloneCoroutine(newContext, block) elseStandaloneCoroutine(newContext, active = true)coroutine.start(start, coroutine, block)return coroutine
}

进入coroutine.start

    public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {initParentJob()start(block, receiver, this)}

start其实这里是一个函数对象的调用,start并非调用了本身,而是调用了它的invoke函数,跟进CoroutineStart的三个参数的invoke函数:

    @InternalCoroutinesApipublic operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>) =when (this) {CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion)CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)CoroutineStart.LAZY -> Unit // will start lazily}

这里有4个,经常用的就是CoroutineStart.DEFAULT,跟进startCoroutineCancellable:

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =runSafely(completion) {createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellable(Unit)}

再进入resumeCancellable

internal fun <T> Continuation<T>.resumeCancellable(value: T) = when (this) {is DispatchedContinuation -> resumeCancellable(value)else -> resume(value)
}

再进入resumeCancellable

    @Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stackinline fun resumeCancellable(value: T) {if (dispatcher.isDispatchNeeded(context)) {_state = valueresumeMode = MODE_CANCELLABLEdispatcher.dispatch(context, this)} else {executeUnconfined(value, MODE_CANCELLABLE) {if (!resumeCancelled()) {resumeUndispatched(value)}}}}

这里调用了dispatcher.dispatch,但是里面是一个抽象的实现,那么它的具体实现是啥,就不追了。来看一下dispatcher的一个具体实现HandlerDispatcher的dispatch函数:

    override fun dispatch(context: CoroutineContext, block: Runnable) {handler.post(block)}

看到这里就明白了,最终调用了handler的post函数,对这个再熟悉不过了。Java里切线程底层用的就是它。

结论:协程切线程的底层也是调用handler.post()

2. 协程为什么可以从主线程“挂起”,却不卡主线程?

因为所谓的“从主线程挂起”,其实是结束了再主线程的执行,把后面的代码放在了后台线程执行,以及在后台线程的工作做完后,再把更靠后的代码通过Handler.post()又抛回主线程。

3. 协程的delay()和Thread.sleep()

delay()性能更好吗?并没有

那它为什么不卡线程?它只是不卡当前线程,而去卡了别的线程。

扔物线--Kotlin协程训练营2期-2相关推荐

  1. 扔物线--Kotlin协程训练营2期-1

    笔记仅做自己学习用,方便自己复习知识.若正好可以帮助到Viewer,万分欣喜~ 若博客侵权,扔物线大大不允许放上面,麻烦告知 本文是扔物线Kotlin第二期协程训练营的第一篇文章 目录 一.Kotli ...

  2. 扔物线Kotlin讲解学习(一)----Kotlin的权限修饰符详解

    Kotlin 中有四种可见性修饰符: public:公开,可见性最大,哪里都可以引用. private:私有,可见性最小,根据声明位置不同可分为类中可见和文件中可见. protected:保护,相当于 ...

  3. 扔物线第一次公开演讲 Kotlin 的协程,就在这周六

    今天发这篇文,是有三件重要的事要大家讲: 之前转发过北京 GDG 的一篇文章,很多人应该都知道,本周六在北京有一场 Kotlin / everywhere 的技术大会,是由北京 GDG 社区与 Kot ...

  4. 扔物线 298 元协程集训课程,限时免费送!

    Kotlin 的协程从一推出就受到大量的关注,国内外很多团队纷纷尝试,但真正能完全正确地使用协程的人并不多,甚至有很多人和团队到现在还学不会怎么使用协程. 然而,协程却是每个已经在使用 Kotlin ...

  5. 谷歌开发者大会扔物线演讲原稿整理:Jetpack Compose

    大家好,我是扔物线朱凯.前两天,我在 GDG DevFest 2020 的 Android Day 做了一次面向全国 Android 工程师的技术分享直播,主题是 Android 最新的 UI 框架 ...

  6. Kotlin协程简介

    1.  什么是协程 关于协程的定义有很多,在Kotlin语言中,协程比较合理的定义应该是一个线程框架(扔物线)或者说是一种并发设计模式(官方).它是由官方设计的一套API方便开发者进行多线程开发. 2 ...

  7. Kotlin 协程是个什么东西?

    相关阅读:一个90后员工猝死的全过程 协程是什么 根据维基百科的定义,协程(Coroutine)是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复. 协程(Coroutine) ...

  8. pdf 深入理解kotlin协程_Kotlin协程实现原理:挂起与恢复

    今天我们来聊聊Kotlin的协程Coroutine. 如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine? 如果你已经接触过协程,但对协程的原理存 ...

  9. Kotlin协程实现原理

    前言 本篇解析Kotlin/JVM中的协程的实现原理. 初看suspend关键字 下面的例子模拟一个网络请求: class Temp {suspend fun fetchData(argument: ...

最新文章

  1. vue 不能监测数组长度变化length的原因
  2. 一个权限的设置,你会混淆么
  3. 2022.2.18自制玉米凉粉
  4. hibernate之工具类
  5. jvm线程分析命令_JVM:如何分析线程转储
  6. 【原生JS组件】javascript 运动框架
  7. mybatis 三级缓存查询循序_MyBatis手把手跟我做系列(五) --- 一级缓存与二级缓存
  8. python开发图片_python实现简单的图片隐写术
  9. 投资五大基本法则,助你在理财投资路上走得更平稳顺利
  10. Vue.js(8)- 父组件给子组件传值
  11. 最有效的Safari的广告拦截插件
  12. Python图像(字母数字)识别
  13. 翻译狗文档免费下载手册(补充版)
  14. C语言求金蝉素数,回文数 - 寂寞暴走伤的个人空间 - OSCHINA - 中文开源技术交流社区...
  15. 第一课 初识计算机ppt,第一课 初识Powerpoint.ppt
  16. 安迪的第一个字典(c++)
  17. revit二次开发之程序调试
  18. java闹钟程序_java 闹钟程序
  19. 用python画动态皮卡丘_如何利用python绘制可爱皮卡丘?
  20. 安卓神秘事件之点击事件不响应

热门文章

  1. 李沐windows深度学习环境配置
  2. 学计算机选香港哪个大学,香港知名大学计算机专业相关推荐
  3. BZOJ4422[Cerc2015]Cow Confinement(扫描线+线段树)
  4. 计算机网络技术入学要求,诺森比亚大学计算机网络技术理科硕士入学条件及实习就业...
  5. Centos 7.6安装LXC
  6. 中山大学21年计算机学院考研情况 专硕最高分442!
  7. 简易代理IP池的搭建
  8. SheetJS js-xlsx简介
  9. python学习笔记(十四) 邮件与短信收发
  10. BIM来了,结构工程师就不用画图了吧?