一、 协程是什么?
- 协程是基于Java的线程框架
- 可以用看起来同步的代码写出实质上是异步的操作。(在协程里的每一行前后台按顺序切换)
- 关键亮点一:耗时函数自动在后台执行,从而提高性能
- 关键亮点二:线程的【自动切回】
- 协程是一种在程序中处理并发任务的方案;也是这种方案的一个组件;类似于:Adapter是界面和数据对接的方案,Adapter也相当于一种组件。
- 协程和线程属于一个层级的概念; 并发是相对宏观一点的概念,有多件事要做,并且是一整套事的不同部分;并行是同一时间两件事有没有同时发生。协程不存在线程,也不存在并行;kotlin协程是基于jvm,底层是线程机制
二、协程好处
如果要执行后台任务,接着执行前台任务,再执行前台任务,再前台...,如果这样频繁切换线程,就会非常麻烦,就需要用到协程了
{
GlobalScope.launch {
ioCode1()//运行在后台 ,挂起函数需要在协程里执行或者在另外一个挂起函数执行
}
uiCode1()//运行在前台
}
private fun uiCode1() {
println(">>>>>>>>>>uiCode1 : ${Thread.currentThread().name}")
}
private suspend fun ioCode1() {//suspend :挂起,把函数标记为挂起函数,执行在后台
println(">>>>>>>>>>ioCode1 : ${Thread.currentThread().name}")
}
>>>>>>>>>>uiCode1 : main
>>>>>>>>>>ioCode1 : DefaultDispatcher-worker-1
从打印的日志可以看出来,先执行的uiCode1,再执行的ioCode1,这样的顺序显然是不对的,不符合需求,这就需要把方法都移到协程里,协程控制不了外面的代码
GlobalScope.launch {
ioCode1()//运行在后台 ,挂起函数需要在协程里执行或者在另外一个挂起函数执行
uiCode1()//运行在前台
}
>>>>>>>>>>ioCode1 : DefaultDispatcher-worker-1
>>>>>>>>>>uiCode1 : DefaultDispatcher-worker-1
从打印的日志可以看出,两个都成后台线程了,因为协程就是在后台执行的,每一个协程都会提供一个线程池,线程池可能是后台,可能是主线程,都有可能,协程就是创建了一个线程池环境
- 可以保证协程里的代码都在线程环境里面执行
- 当挂起函数把线程切走之后,后面的代码会回到线程环境继续执行。 当协程创建的环境是主线程环境,执行完后台环境后,会自动回到主线程环境进行UI更新。
GlobalScope.launch (Dispatchers.Main){//默认是后台线程,Dispatchers.Main主线程环境
ioCode1()//运行在后台
uiCode1()//运行在前台
}
>>>>>>>>>>ioCode1 : main
>>>>>>>>>>uiCode1 : main
出错了,两个都成主线程了!!,suspend是一个标记作用,他本身是不切线程的,需要手动切到主线程。
private suspend fun ioCode1() {//suspend :挂起,把函数标记为挂起函数,执行在后台,suspend会检查这个函数里有没有别的挂起函数
withContext(Dispatchers.IO){//手动切换到后台线程
println(">>>>>>>>>>ioCode1 : ${Thread.currentThread().name}")
}
}
三、协程的代码怎么写
- 用launch()来开启一段协程,一般需要指定Dispatchers.Main。
- 把要在后台工作的函数,写成suspend函数,需要在内部调用其他suspend函数来真正切线程。
- 按照一条线写下来,线程自动会切换
四、协程的额外天然优势:性能
协程是自动提高软件的性能,挂起函数,自动切回,协程是链式的
- 程序什么时候会需要切线程?
- 工作比较耗时:放在后台,界面不会卡顿
- 工作比较特殊:放在指定线程-----一般来说,是主线程
五、suspend
- 并不是用来切换函数的
- 关键作用:标记和提醒(提醒是挂起函数,需要在协程里执行)
六、Retrofit对协程的支持
对协程的suspend关键字的支持。 对网络请求加上suspend关键字,就会自动在后台执行。 kotlin 中取消了强制try catch
七、Kotlin的协程和RxJava
interface Api{
@GET("users/{user}/repos")
fun listRepos(@Path("user")user : String): Call<List<Repo>>
@GET("users/{user}/repos")
suspend fun listReposKt(@Path("user")user : String): List<Repo>
@GET("users/{user}/repos")
fun listReposRx(@Path("user")user : String): Single<List<Repo>>//Single单事件流
}
data class Repo(
val name: String)
val retrofit = Retrofit.Builder()
.baseUrl("https://xxx.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.createWithScheduler(Schedulers.io()))//网络请求放在后台处理
.build()
val api = retrofit.create(Api::class.java)
//retrofit 普通请求
api.listRepos("xxx")
.enqueue(object : Callback<List<Repo>>{
override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
Log.e(">>>>>请求结果",">>>: ${response.body()?.get(0)?.name}")
binding.tvText.text = response.body()?.get(0)?.name
}
override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
Log.e(">>>>>>请求错误",">>>>>>${t.message}")
}
})
//协程请求
GlobalScope.launch {
try {
var repos = api.listReposKt("xxx")
binding.tvText.text = repos[0]?.name
}catch (e:Exception){
binding.tvText.text = e.message
}
}
//通过RxJava请求
api.listReposRx("xxx")
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<List<Repo>>{
override fun onSubscribe(d: Disposable) {
}
override fun onSuccess(t: List<Repo>) {
binding.tvText.text = t[0].name
}
override fun onError(e: Throwable) {
binding.tvText.text = e.message
}
})
RxJava是什么?
- 管理并发任务
- 管理事件流 RxJava好在哪?
- 都可以切线程
- 都不需要嵌套
- Rxjava需要回调,协程不需要回调 flatmap
如果同时进行两个请求,并把结果合并
Single.zip(
api.listReposRx("xxx"),
api.listReposRx("xxx"),
{ list1, list2 -> "${list1[0].name} - ${list2[0].name}" }
).observeOn(AndroidSchedulers.mainThread())
.subscribe { result ->
binding.tvText.text = result
}
//通过协程
GlobalScope.launch { //launch 会创建一个协程,async也会创建一个协程出来,async是一个有结果的协程,通过await返回
val one = async { api.listReposKt("xxx") }
val two = async { api.listReposKt("xxx") }
binding.tvText.text = "${one.await()[0].name} -> ${two.await()[0].name}"
}
- 协程泄漏
正常情况下网络请求图片,并对请求到的图片进行裁切等后续操作,但是如果刚请求网络图片,用户就把界面关了,协程还会把后续的裁切等一系列操作完,就造成了协程泄漏,洁面关闭的时候,协程内部的线程海在运转,就造成了泄露。
- 解决方案:调用协程对象的cancel()
var job = GlobalScope.launch { }
job.cancel()
CoroutineScope //管理多个协程的集中地,他一取消,他管理的协程都取消了
lifecycleScope//lifecycleScope随Activity的生命周期走,不用手动取消,会自动在onDestory中取消
- 内存泄露 例如:asyncTask 为什么会内存泄露 因为AsyncTask内部有一个活跃的线程,持有外部的Activity,垃圾回收会不定时出现,活跃的线程是被保护的,不会被垃圾回收器回收
- 协程是怎么切线程的? 协程是通过handler来切换线程
- 协程为什么可以【挂起】却不卡主线程? 协程底层用的handler.post()
- 协程的delay()和Thread.sleep()