wordpress修改网站名称,建个网站需要多少钱?,做电商网站一般多少钱,提供网站建设备案公司前言
关于Kotlin基础和高阶函数又不熟悉的可以先参考文章#xff1a;
Android Kotlin 基础详解_袁震的博客-CSDN博客
Android Kotlin 高阶详解_袁震的博客-CSDN博客
什么是协程#xff1f;要理解协程#xff0c;就要将它和线程联系起来理解。
线程是什么#xff1f;我…前言
关于Kotlin基础和高阶函数又不熟悉的可以先参考文章
Android Kotlin 基础详解_袁震的博客-CSDN博客
Android Kotlin 高阶详解_袁震的博客-CSDN博客
什么是协程要理解协程就要将它和线程联系起来理解。
线程是什么我想大家都清楚而协程它比线程更加轻量级一个线程上面可以有多个协程。
如果我们应用开启一万个线程可能就崩溃了但是如果我们开启10万个协程对应用的性能也不会有太大的影响。
协程是可挂起计算的实例它需要一个代码块运行并具有类似的生命周期可以被创建、启动和取消它不绑定到任何特定的线程可以在一个线程中挂起其执行并在另一个线程中恢复它在完结时可能伴随着某种结果值或异常
协程的主要作用
1处理耗时任务这种任务可能会阻塞主线程
2.保证线程安全
一协程的基本使用
Kotlin并没有将协程纳入标准库的API当中而是以依赖库的形式提供的。所以如果我们想要使用协程功能需要先在app/build.gradle文件当中添加如下依赖库
implementation org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1
implementation org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1
fun main() {GlobalScope.launch {//开启一个协程println(开启一个协程)}
}
注意这段代码是没有输出的。后面会解释原因。
二协程的作用域 CoroutineScope
协程的作用域主要包括三种
顶级作用域 没有父协程的协程所在的作用域为顶级作用域。协同作用域 协程中启动新的协程新协程为所在协程的子协程这种情况下子协程所在的作用域默认为协同作用域。此时子协程抛出的未捕获异常都将传递给父协程处理父协程同时也会被取消。主从作用域 与协同作用域在协程的父子关系上一致区别在于处于该作用域下的协程出现未捕获的异常时不会将异常向上传递给父协程。
除了三种作用域中提到的行为以外父子协程之间还存在以下规则父协程被取消则所有子协程均被取消。由于协同作用域和主从作用域中都存在父子协程关系因此此条规则都适用。 父协程需要等待子协程执行完毕之后才会最终进入完成状态不管父协程自身的协程体是否已经执行完。 子协程会继承父协程的协程上下文中的元素如果自身有相同key的成员则覆盖对应的key覆盖的效果仅限自身范围内有效。
在kotlin中所有的作用域都是CoroutineScope 的子类下面是四种常用的作用域
GlobalScope全局范围不会自动结束执行。MainScope主线程的作用域全局范围lifecycleScope生命周期范围用于activity等有生命周期的组件在Desroyed的时候会自动结束。viewModeScopeViewModel范围用于ViewModel中在ViewModel被回收时会自动结束
①GlobalScope: GlobalScope是全局的作用域并且是无法取消的因为
public object GlobalScope : CoroutineScope {override val coroutineContext: CoroutineContextget() EmptyCoroutineContext
}
从GlibalScope的源码可以看出他的上下文对象是EmptyCorountineContext 并没有Job对象所以我们无法通过Job对象去cancle协程。
fun main() {GlobalScope.launch {//开启一个协程println(开启一个协程)}
}
这段代码为什么没有输出呢 因为GlibalScope 是不阻塞线程的主线程执行完了此协程也会跟着结束所以没有输出。
fun main() {GlobalScope.launch {//开启一个协程println(开启一个协程)}Thread.sleep(1000)
}
那如果我这样写就会有输出打印
②MainScope
MainScope也是全局的作用域但是它是可以取消的。
public fun MainScope(): CoroutineScope ContextScope(SupervisorJob() Dispatchers.Main)
它的上下文是 SupervisorJob()和主线程调度器构成的所以它是可以取消的全局主线程协程。
MainScope是一个全局函数我们可以在任何地方调用它ActivityFragmentDialogViewModel等但是需要注意在页面销毁的时候需要手动cancle。
class MainActivity : AppCompatActivity() {var mScopeMainScope()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)mScope.launch { println(执行了协程)}}override fun onDestroy() {super.onDestroy()mScope.cancel()}
}
③ViewModelScope
ViewModelScope 是一个CloseableCoroutineScope,它的上下文由 SupervisorJob() Dispatchers.Main.immediate构成所以它也是可以取消的主线程协程
private const val JOB_KEY androidx.lifecycle.ViewModelCoroutineScope.JOB_KEYval ViewModel.viewModelScope: CoroutineScopeget() {val scope: CoroutineScope? this.getTag(JOB_KEY)if (scope ! null) {return scope}return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() Dispatchers.Main.immediate))}internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {override val coroutineContext: CoroutineContext contextoverride fun close() {coroutineContext.cancel()}
}
可以看到 ViewModelScope是 ViewModel类的 扩展属性假如这个 ViewModel 是 Activity 的那么在 Activity 退出的时候ViewModel 的 clear() 方法就会被调用而 clear() 方法中会扫描当前 ViewModel 的成员 mBagsOfTags(一个Map对象中保存的所有的 Closeable 的 object 对象也就是上面的 CloseableCoroutineScope并调用其 close() 方法。 所以当它用在ViewModel里面的时候我们不用主动去回收它它会自动回收。
④LifecycleScope:
LifecycleScope的实例是LifecycleCoroutineScopeImpl它的上下文也是由SupervisorJob()Dispatchers.Main.immediate构成所以它也是可以取消的主线程协程
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScopeget() lifecycle.coroutineScopepublic val Lifecycle.coroutineScope: LifecycleCoroutineScopeget() {while (true) {val existing mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?if (existing ! null) {return existing}val newScope LifecycleCoroutineScopeImpl(this,SupervisorJob() Dispatchers.Main.immediate)if (mInternalScopeRef.compareAndSet(null, newScope)) {newScope.register()return newScope}}}
lifecycleScope是LifecycleOwner的扩展属性因此它只能在Activity、Fragment中使用会绑定Activity和Fragment的生命周期。 它的基本使用和 viewModelScope 是一样的。但是它多了生命周期的一些感知。它也是通过 LifecycleController 中为 Lifecycle注册 观察者接口, 来感知 onResume的状态然后进行调用的。
public fun launchWhenCreated(block: suspend CoroutineScope.() - Unit): Job launch {lifecycle.whenCreated(block)
}
public fun launchWhenStarted(block: suspend CoroutineScope.() - Unit): Job launch {lifecycle.whenStarted(block)
}
public fun launchWhenResumed(block: suspend CoroutineScope.() - Unit): Job launch { lifecycle.whenResumed(block)
} 三启动协程的方式
协程的启动主要有五种方式
①调用 xxxScope.launch{...} 启动一个协程块 launch方法启动的协程不会将结果返回给调用方。
GlobalScope.launch {//开启一个协程println(开启一个GlobalScope协程)
} ②在 xxxScope {...} 中调用 async{...} 创建一个子协程 async会返回一个 Deferred对象随后可以调用 Deferred对象的 await()方法来启动该协程。
fun main() {GlobalScope.launch {//开启一个协程println(开启一个GlobalScope协程)val result async {println(async)10}println(result:${result.await()})}Thread.sleep(1000)
}
这里需要注意一下async函数必须在协程作用域当中才能调用await()是一个挂起函数只能在挂起作用域内调用。所以通常不用async{}来创建最外层的协程因为非挂起作用域无法调用await()函数获取协程的返回值。所以返回值没有意义这样的话async()的返回值Deferred就是普通的Job所以完全可以使用launch{}代替async{} ③withContext(){...} 一个 suspend方法在给定的上下文中执行并返回结果它的目的不在于启动子协程主要用于 线程切换将长耗时操作从UI线程切走完事再切回来。用它执行的挂起块中的上下文是当前协程的上下文和由它执行的上下文的合并结果。
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)mScope.launch {println(执行了协程)val result withContext(Dispatchers.IO){println(切换到子线程)20}println(result)withContext(Dispatchers.Main){println(切换到主线程)}}
}
withContext()是顶级函数可以直接调用不需要创建协程对象。但是withContext()是一个suspend挂起函数它只能在协程或其他挂起函数中调用(必须先有协程)
调用withContext()函数之后会立即执行代码块中的代码同时将外部协程挂起。当代码块中的代码全部执行完之后会将最后一行的执行结果作为withContext()函数的返回值返回
withContext()函数强制要求我们指定一个线程参数这个参数就是调度器下面会讲 ④coroutineScope{...} supervisorScope{...}创建一个新的作用域并在该作用域内执行指定代码块它并不启动协程。其存在的目的是进行符合结构化并发的并行分解。
private fun request() {lifecycleScope.launch {coroutineScope { // 协同作用域抛出未捕获异常时会取消父协程launch { }}supervisorScope { // 主从作用域抛出未捕获异常时不会取消父协程launch { }}}
}
coroutineScope 表示 协同作用域 内部的协程 出现异常 会向外部传播子协程未捕获的异常会向上传递给父协程 子协程 可以挂掉外部协程 外部协程挂掉也会挂掉子协程即 双向传播 。 任何一个子协程异常退出会导致整体的退出。supervisorScope 表示 主从作用域会继承父协程的上下文它的特点就是子协程的异常不会影响父协程内部的 子协程挂掉 不会影响外部的父协程和兄弟协程的继续运行它就像一道防火墙隔离了异常保证程序健壮但是如果外部协程挂掉还是可以取消子协程的即 单向传播。它的设计应用场景多用于 子协程为独立对等的任务实体的时候比如一个下载器每一个子协程都是一个下载任务当一个下载任务异常时它不应该影响其他的下载任务。
⑤runBlocking{...} 创建一个协程并阻塞当前线程直到协程执行完毕。
fun main() {runBlocking {println(开启一个runBlocking协程)}
} 一般的开发中我们尽量不使用这种方式它通常用于main函数或者其他测试用例中因为在main函数中启动一个协程去执行耗时任务如果不阻塞main函数的线程main函数执行完jvm就退出了为了避免jvm退出通常在最后需要Thread.sleep(Long.MAX_VALUE)让主线程休眠来等待协程执行完毕。但是如果使用runBlocking{}创建协程就不会出现jvm提前退出的问题。如在前面提到的不打印问题
四 协程启动模式
在创建协程时一般是有四种启动模式如果我们不写的话一般是默认的DEFAULT模式
①CoroutineStart.DEFAULT 协程创建后立即开始调度但 有可能在执行前被取消。在调度前如果协程被取消其将直接进入取消响应的状态。
mScope.launch (start CoroutineStart.DEFAULT){println(执行了协程)val result withContext(Dispatchers.IO){println(切换到子线程)20}println(result)withContext(Dispatchers.Main){println(切换到主线程)}
}
②CoroutineStart.ATOMIC 协程创建后立即开始调度 协程执行到第一个挂起点之前不响应取消。其将调度和执行两个步骤合二为一就像它的名字一样其保证调度和执行是原子操作因此协程也 一定会执行。
mScope.launch (start CoroutineStart.ATOMIC){println(执行了协程)val result withContext(Dispatchers.IO){println(切换到子线程)20}println(result)withContext(Dispatchers.Main){println(切换到主线程)}
}
③CoroutineStart.LAZY 只要协程被需要时主动调用该协程的 start、 join、 await等函数时 才会开始调度如果调度前就被取消协程将直接进入异常结束状态。
mScope.launch (start CoroutineStart.LAZY){println(执行了协程)val result withContext(Dispatchers.IO){println(切换到子线程)20}println(result)withContext(Dispatchers.Main){println(切换到主线程)}
}
④CoroutineStart.UNDISPATCHED 协程创建后立即在当前线程中执行直到遇到第一个真正挂起的点。是立即执行因此协程 一定会执行。
mScope.launch (start CoroutineStart.UNDISPATCHED){println(执行了协程)val result withContext(Dispatchers.IO){println(切换到子线程)20}println(result)withContext(Dispatchers.Main){println(切换到主线程)} 五协程调度器
官方框架中预置了 4 个调度器我们可以通过 Dispatchers 对象访问它们
①Default: 默认调度器 适合处理后台计算其是一个 CPU 密集型任务调度器
②IO: IO 调度器适合执行 IO 相关操作其是 IO 密集型任务调度器
③Main: UI 调度器根据平台不同会被初始化为对应的 UI 线程的调度器, 例如在Android 平台上它会将协程调度到 UI 事件循环中执行即通常在 主线程上执行。
④Unconfined:“无所谓“调度器不要求协程执行在特定线程上。协程的调度器如果是 Unconfined, 那么它在挂起点恢复执行时会在恢复所在的线程上直接执行当然 如果嵌套创建以它为调度器的协程那么这些协程会在启动时被调度到协程框架内部的事件循环上以避免出StackOverflow。
如果创建 Coroutine的时候未指定调度器或者使用未指定的调度器的上下文的 Scope通过 launch或 async启动一个协程则默认是使用 Dispatchers.Default调度器 。
由于 子协程会默认继承 父协程的 context上下文所以一般我们可以直接为 父协程的 context上设置一个 Dispatcher这样所有的子协程就自动使用这个 Dispatcher当某个子协程有特殊需要的时候再其指定特定的 Dispatcher。
Default 和 IO 这两个调度器背后实际上是 同一个线程池。为什么二者在使用上会存在差异呢由于 IO 任务通常会阻塞实际执行任务的线程在阻塞过程中线程虽然不占用 CPU, 但却占用了大量内存这段时间内被 IO 任务占据线程实际上是资源使用不合理的表现因此 IO 调度器对于 IO 任务的并发做了限制 避免过多的 IO 任务并发占用过多的系统资源同时在调度时为任务打上 PROBABLY BLOCKING 标签以方便线程池在执行任务调度时对阻塞任务和非阻塞任务区别对待。
六suspend
在Kotlin协程中被suspend修饰的函数是一个挂起函数可以调用和使用协程库里的方法仅能被suspend修饰的方法或lambda闭包使用在其余地方使用会编译报错因为被suspend修饰的函数再编译成java后会增加一个Continuation的参数这个函数用于回调协程执行的结果所以说协程的异步调用本质上就是一次异步调用。
当在协程中调用到挂起函数时协程就会在当前线程主线程中被挂起这就是协程中著名的非阻塞式挂起主线程暂时停止执行这个协程中剩余的代码注意暂停并不是阻塞等待否则会ANR而是主线程暂时从这个协程中被释放出来去处理其他Handler消息比如响应用户操作、绘制View等等。那挂起函数谁执行这得看挂起函数内部是否有切换线程如果没有切换线程当然就是主线程当前线程执行了所以挂起函数不一定就是在子线程中执行的但是通常在定义挂起函数时都会为它指定其他线程这样挂起才有意义。
fun main(args: ArrayString) {runBlocking {println(Thread.currentThread().name)var html getHtml()println(Thread.currentThread().name)}
}suspend fun getHtml(): String {return GlobalScope.async {println(Thread.currentThread().name)delay(1000)URL(https://www.baidu.com).readText()}.await()
}
比如上面的例子在主线程默认线程中启动了一个协程当执行到getHtml()时切换到了GlobalScope协程中默认运行在工作线程中去执行此时主线程中的代码将被暂时挂起。当getHtml中的方法执行完并返回后主线程中的协程才会继续运行这叫做协程恢复如果遇到了其他挂起函数还会重复这个过程。