背景:上家公司干了三年,开发语言主要用的JAVA和flutter来开发的,新的公司全是kotlin,所以又要把上上家公司用的kotlin要回顾下了,主要还是协程和Jetpack相关。
基础概念
协程
协程并不是一个新的概念,它并不是 Kotlin 发明的。它们已经存在了几十年,并且在 Go 等其他一些编程语言中很受欢迎。
官方的描述是:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
对比其他几种异步编程的方式:线程、回调、Rxjava:
- 线程 线程的确很好用,也方便用线程池管理,不过线程的上下文切换是消耗性能的,不过这是可以优化的,可以使用CAS或者优化锁抢占等来优化。
- 回调 一个函数作为参数传递给另一个函数,在处理完后回调用此函数。确实比较好用,但是业务比较复杂,要小心嵌套陷阱和阻塞。
- Rxjava 这类响应式编程确实很方便,也不存在多层级嵌套。一切皆是流,并且它还是可观察的。一种比较完美的解决方案,学习成本也比较高。
协程:是一种可以挂起和恢复的编程模型,可以让我们异步逻辑同步化,决绝回调地狱。看个小例子:
fun postBean(bean: Bean) {
launch {
val token = preparePost()
val post = submitPost(token, bean)
processPost(post)
}
}
suspend fun preparePost(): Token {
return suspendCoroutine { }
}
preparePost函数是个挂起函数,是异步的,但是代码逻辑确是同步化的。
挂起和恢复
协程的核心点就是,函数或者一段程序能够挂起,稍后在挂起的位置恢复。 协程添加了suspend 和resume 关键字来操作:
- suspend 挂起 ,用于暂停执行当前协程,并保存所有局部变量
- resume 用于让暂停的协程从挂起处继续执行
需要注意的是:挂起函数(被suspend修饰的)只能在协程体内或其他挂起函数内调用。
挂起和阻塞
- 相同点 都是异步操作
- 区别 挂起是不会占用CPU的,但是会保存状态,在某个时间恢复执行。而阻塞一般是进程等待资源是发生,会占用系统资源。
调度器
所有的协程必须在调度器中运行,即使在主线程也是一样。
Dispatchers.Main
Android上的主线程,用于处理UI交互和一些轻量级任务,eg:调用UI函数、调用suspend函数、更新LiveData。
Dispatchers.IO
非主线程,专为磁盘和网络IO进行了优化,eg:数据库、文件读写、网络请求。
Dispatchers.Default
非主线程,协程开启的默认调度器,专为CPU密集型任务进行了优化,eg:数组排序、JSON解析、复杂逻辑处理、计算处理。
eg:一个简单的调度器上下文切换代码:
GlobalScope.launch(Dispatchers.IO){
val user = getUser()
withContext(Dispatchers.Main){
binding.testText.text = user.name
}
}
结构化并发-CoroutineScope
一个场景:当某个协程任务丢失,无法追踪,会导致内存、CPU等资源浪费,也行会发送一个网络请求或者缓存读取任务,但是没有接受,这种场景称为任务泄漏。 所以kotlin又加入了结构化并发,跟之前学过的Rxjava定义生命周期也是一样的,其实就是作用域的限定和追踪。
定义协程必须指定起CoroutineScope,它会追踪所有协程,同样它还可以取消它所启动的协程。
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
官方解释:这个范围的上下文。上下文由范围封装,并用于作为范围扩展的协程构建器的实现。除了在高级用法中访问Job实例外,不建议出于任何目的访问通用代码中的此属性。 按照约定,应该包含一个作业实例来强制结构化并发。
定义新协程的作用域。每个协程构建器(比如launch, async等)都是CoroutineScope的扩展,并继承它的coroutineContext来自动传播它的所有元素和取消。
获得独立作用域实例的最佳方法是CoroutineScope()和MainScope()工厂函数。可以使用加号操作符将其他上下文元素附加到作用域。 结构化并发规范: 不建议手动实现此接口,建议采用委托实现。按照约定,作用域的上下文应该包含一个作业实例,以通过取消的传播来强制结构化并发原则。
每个协程构建器(如launch, async等)和每个作用域函数(如coroutineScope, withContext等)都提供了自己的作用域,并将自己的Job实例放入它运行的代码内部块中。按照惯例,它们都要等待它们块中的所有协程完成,然后才能完成它们自己,从而实现结构化并发。 Android使用: Android在所有具有生命周期的实体中都支持协程作用域。
相关API:
- GlobalScope 生命周期是进程级别的,即使activity和fragment已经销毁,协程依然执行
GlobalScope.launch {
}
- MainScope 在Activity中使用,可以在onDestory(). 中取消协程
MainScope().launch {
}
- viewModelScope 只能在ViewModel中使用,绑定ViewModel的生命周期
viewModelScope.launch {
}
- lifecycleScope 只能在Activity、Fragment中使用,会绑定Activity和Fragment的生命周期
lifecycleScope.launch {
}
|