朋友国企干了5年安卓,居然不知道Kotlin协程是什么?
lipiwang 2024-11-01 14:09 7 浏览 0 评论
什么是协程?
官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
协程很重要的一点就是当它挂起的时候,它不会阻塞其他线程。协程底层库也是异步处理阻塞任务,但是这些复杂的操作被底层库封装起来,协程代码的程序流是顺序的,不再需要一堆的回调函数,就像同步代码一样,也便于理解、调试和开发。它是可控的,线程的执行和结束是由操作系统调度的,而协程可以手动控制它的执行和结束。
使用
首先需要添加依赖:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
1.runBlocking:T
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e(TAG, "主线程id:${mainLooper.thread.id}")
test()
Log.e(TAG, "协程执行结束")
}
private fun test() = runBlocking {
repeat(8) {
Log.e(TAG, "协程执行$it 线程id:${Thread.currentThread().id}")
delay(1000)
}
}
runBlocking启动的协程任务会阻断当前线程,直到该协程执行结束。当协程执行结束之后,页面才会被显示出来。
2.launch:Job
这是最常用的用于启动协程的方式,它最终返回一个Job类型的对象,这个Job类型的对象实际上是一个接口,它包含了许多我们常用的方法。下面先看一下简单的使用:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e(TAG, "主线程id:${mainLooper.thread.id}")
val job = GlobalScope.launch {
delay(6000)
Log.e(TAG, "协程执行结束 -- 线程id:${Thread.currentThread().id}")
}
Log.e(TAG, "主线程执行结束")
}
//Job中的方法
job.isActive
job.isCancelled
job.isCompleted
job.cancel()
jon.join()
从执行结果看出,launch不会阻断主线程。
我们看一下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) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
从方法定义中可以看出,launch() 是CoroutineScope的一个扩展函数,CoroutineScope简单来说就是协程的作用范围。launch方法有三个参数:1.协程下上文;2.协程启动模式;3.协程体:block是一个带接收者的函数字面量,接收者是CoroutineScope
1.协程下上文
上下文可以有很多作用,包括携带参数,拦截协程执行等等,多数情况下我们不需要自己去实现上下文,只需要使用现成的就好。上下文有一个重要的作用就是线程切换,Kotlin协程使用调度器来确定哪些线程用于协程执行,Kotlin提供了调度器给我们使用:
- Dispatchers.Main:使用这个调度器在 Android 主线程上运行一个协程。可以用来更新UI 。在UI线程中执行
- Dispatchers.IO:这个调度器被优化在主线程之外执行磁盘或网络 I/O。在线程池中执行
- Dispatchers.Default:这个调度器经过优化,可以在主线程之外执行 cpu 密集型的工作。例如对列表进行排序和解析 JSON。在线程池中执行。
- Dispatchers.Unconfined:在调用的线程直接执行。
调度器实现了CoroutineContext接口。
2.启动模式
在Kotlin协程当中,启动模式定义在一个枚举类中:
public enum class CoroutineStart {
DEFAULT,
LAZY,
@ExperimentalCoroutinesApi
ATOMIC,
@ExperimentalCoroutinesApi
UNDISPATCHED;
}
一共定义了4种启动模式,下表是含义介绍:
启动模式 作用 DEFAULT 默认的模式,立即执行协程体 LAZY 只有在需要的情况下运行 ATOMIC 立即执行协程体,但在开始运行之前无法取消 UNDISPATCHED 立即在当前线程执行协程体,直到第一个 suspend 调用
2.协程体
协程体是一个用suspend关键字修饰的一个无参,无返回值的函数类型。被suspend修饰的函数称为挂起函数,与之对应的是关键字resume(恢复),注意:挂起函数只能在协程中和其他挂起函数中调用,不能在其他地方使用。
suspend函数会将整个协程挂起,而不仅仅是这个suspend函数,也就是说一个协程中有多个挂起函数时,它们是顺序执行的。看下面的代码示例:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
val token = getToken()
val userInfo = getUserInfo(token)
setUserInfo(userInfo)
}
repeat(8){
Log.e(TAG,"主线程执行$it")
}
}
private fun setUserInfo(userInfo: String) {
Log.e(TAG, userInfo)
}
private suspend fun getToken(): String {
delay(2000)
return "token"
}
private suspend fun getUserInfo(token: String): String {
delay(2000)
return "$token - userInfo"
}
getToken方法将协程挂起,协程中其后面的代码永远不会执行,只有等到getToken挂起结束恢复后才会执行。同时协程挂起后不会阻塞其他线程的执行。
3.async
async跟launch的用法基本一样,区别在于:async的返回值是Deferred,将最后一个封装成了该对象。async可以支持并发,此时一般都跟await一起使用,看下面的例子。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
val result1 = GlobalScope.async {
getResult1()
}
val result2 = GlobalScope.async {
getResult2()
}
val result = result1.await() + result2.await()
Log.e(TAG,"result = $result")
}
}
private suspend fun getResult1(): Int {
delay(3000)
return 1
}
private suspend fun getResult2(): Int {
delay(4000)
return 2
}
async是不阻塞线程的,也就是说getResult1和getResult2是同时进行的,所以获取到result的时间是4s,而不是7s。
应用
项目中的网络请求框架大部分都是基于RxJava + Retrofit + Okhttp封装的,RxJava可是很好的实现线程之间的切换,如果只是网络框架中用到了RxJava,那就是“大材小用”了,毕竟RxJava的功能还是很强大的。Retrofit从2.6.0开始已经支持协程了:可以定义成一个挂起函数。
interface Api {
@POST("user/login")
suspend fun login(): Call<User>
}
下面的例子是使用协程来代替RxJava实现线程切换。
1.首先定义一个请求相关的支持DSL语法的接收者。
class RetrofitCoroutineDSL<T> {
var api: (Call<Result<T>>)? = null
internal var onSuccess: ((T) -> Unit)? = null
private set
internal var onFail: ((msg: String, errorCode: Int) -> Unit)? = null
private set
internal var onComplete: (() -> Unit)? = null
private set
/**
* 获取数据成功
* @param block (T) -> Unit
*/
fun onSuccess(block: (T) -> Unit) {
this.onSuccess = block
}
/**
* 获取数据失败
* @param block (msg: String, errorCode: Int) -> Unit
*/
fun onFail(block: (msg: String, errorCode: Int) -> Unit) {
this.onFail = block
}
/**
* 访问完成
* @param block () -> Unit
*/
fun onComplete(block: () -> Unit) {
this.onComplete = block
}
internal fun clean() {
onSuccess = null
onComplete = null
onFail = null
}
}
2.然后给协程定义一个扩展方法,用于Retrofit网络请求。
fun <T> CoroutineScope.retrofit(dsl: RetrofitCoroutineDSL<T>.() -> Unit) {
//在主线程中开启协程
this.launch(Dispatchers.Main) {
val coroutine = RetrofitCoroutineDSL<T>().apply(dsl)
coroutine.api?.let { call ->
//async 并发执行 在IO线程中
val deferred = async(Dispatchers.IO) {
try {
call.execute() //已经在io线程中了,所以调用Retrofit的同步方法
} catch (e: ConnectException) {
coroutine.onFail?.invoke("网络连接出错", -1)
null
} catch (e: IOException) {
coroutine.onFail?.invoke("未知网络错误", -1)
null
}
}
//当协程取消的时候,取消网络请求
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
coroutine.clean()
}
}
//await 等待异步执行的结果
val response = deferred.await()
if (response == null) {
coroutine.onFail?.invoke("返回为空", -1)
} else {
response.let {
if (response.isSuccessful) {
//访问接口成功
if (response.body()?.status == 1) {
//判断status 为1 表示获取数据成功
coroutine.onSuccess?.invoke(response.body()!!.data)
} else {
coroutine.onFail?.invoke(response.body()?.msg ?: "返回数据为空", response.code())
}
} else {
coroutine.onFail?.invoke(response.errorBody().toString(), response.code())
}
}
}
coroutine.onComplete?.invoke()
}
}
}
在上面的代码中,比较难理解的是下面的代码:
val coroutine = RetrofitCoroutineDSL<T>().apply(dsl)
dsl是带接收者的函数字面量,接收者是RetrofitCoroutineDSL,所有先创建一个接收者对象,然后将传入的实参dsl赋值给该对象。还可以写成下面的样子:
val coroutine = RetrofitCoroutineDsl<T>()
coroutine.dsl()
上面的写法是直接调用函数字面量。为了方便里面,把上述代码翻译成对应的Java代码:
RetrofitCoroutineDsl<T> coroutine = new RetrofitCoroutineDsl<T>();
dsl.invoke(coroutine);
调用函数dsl并传入coroutine,其实就是把dsl赋值给coroutine。
3.最后一步,让BaseActivity实现接口CoroutineScope,这样在页面中的上下文就是协程下上文
open class BaseActivity : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
// 关闭页面后,结束所有协程任务
job.cancel()
}
}
+是CoroutineContext中的运算符重载,包含两者的上下文:
//Returns a context containing elements from this context and elements from other [context].
//The elements from this context with the same key as in the other one are dropped.
public operator fun plus(context: CoroutineContext): CoroutineContext
在Activity中可以直接调用扩展函数retrofit来调用网络请求:
retrofit<User> {
api = RetrofitCreater.create(Api::class.java).login()
onSuccess {
Log.e(TAG, "result = ${it?.avatar}")
}
onFailed { msg, _ ->
Log.e(TAG, "onFailed = $msg")
}
}
如果不需要处理访问失败的情况,可以写成下面的样子:
retrofit<User> {
api = RetrofitCreater.create(Api::class.java).login()
onSuccess {
Log.e(TAG, "result = ${it?.avatar}")
}
}
使用协程可以更好的控制任务的执行,并且比线程更加的节省资源,更加的高效。结合DSL的代码风格,可以让我们的程序更加直观易懂、简洁优雅。
Kotlin实战:https://github.com/guofudong/KotlinAndroid
Android核心知识笔记:https://github.com/AndroidCot/Android
相关推荐
- ubuntu单机安装open-falcon极度详细操作
-
备注:以下操作均由本人实际操作并得到验证,喜欢的同学可尝试操作安装。步骤一1.1环境准备(使用系统:ubuntu18.04)1.1.1安装redisubuntu下安装(参考借鉴:https://...
- Linux搭建promtail、loki、grafana轻量日志监控系统
-
一:简介日志监控告警系统,较为主流的是ELK(Elasticsearch、Logstash和Kibana核心套件构成),虽然优点是功能丰富,允许复杂的操作。但是,这些方案往往规模复杂,资源占用高,...
- 一文搞懂,WAF阻止恶意攻击的8种方法
-
WAF(Web应用程序防火墙)是应用程序和互联网流量之间的第一道防线,它监视和过滤Internet流量以阻止不良流量和恶意请求,WAF是确保Web服务的可用性和完整性的重要安全解决方案。它...
- 14配置appvolume(ios14.6配置文件)
-
使用AppVolumes应用程序功能,您可以管理应用程序的整个生命周期,包括打包、更新和停用应用程序。您还可以自定义应用程序分配,以向最终用户提供应用程序的特定版本14.1安装appvolume...
- 目前流行的缺陷管理工具(缺陷管理方式存在的优缺点)
-
摘自:https://blog.csdn.net/jasonteststudy/article/details/7090127?utm_medium=distribute.pc_relevant.no...
- 开源数字货币交易所开发学习笔记(2)——SpringCloud
-
前言码云(Gitee)上开源数字货币交易所源码CoinExchange的整体架构用了SpringCloud,对于经验丰富的Java程序员来说,可能很简单,但是对于我这种入门级程序员,还是有学习的必要的...
- 开发JAX-RPC Web Services for WebSphere(下)
-
在开发JAX-RPCWebServicesforWebSphere(上)一文中,小编为大家介绍了如何创建一个Web服务项目、如何创建一个服务类和Web服务,以及部署项目等内容。接下来小编将为大...
- CXF学习笔记1(cxf client)
-
webservice是发布服务的简单并实用的一种技术了,个人学习了CXF这个框架,也比较简单,发布了一些笔记,希望对笔友收藏并有些作用哦1.什么是webServicewebService让一个程序可...
- 分布式RPC最全详解(图文全面总结)
-
分布式通信RPC是非常重要的分布式系统组件,大厂经常考察的Dubbo等RPC框架,下面我就全面来详解分布式通信RPC@mikechen本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集》...
- Oracle WebLogic远程命令执行0day漏洞(CVE-2019-2725补丁绕过)预警
-
概述近日,奇安信天眼与安服团队通过数据监控发现,野外出现OracleWebLogic远程命令执行漏洞最新利用代码,此攻击利用绕过了厂商今年4月底所发布的最新安全补丁(CVE-2019-2725)。由...
- Spring IoC Container 原理解析(spring中ioc三种实现原理)
-
IoC、DI基础概念关于IoC和DI大家都不陌生,我们直接上martinfowler的原文,里面已经有DI的例子和spring的使用示例《InversionofControlContainer...
- Arthas线上服务器问题排查(arthas部署)
-
1Arthas(阿尔萨斯)能为你做什么?这个类从哪个jar包加载的?为什么会报各种类相关的Exception?我改的代码为什么没有执行到?难道是我没commit?分支搞错了?遇到问题无法在...
- 工具篇之IDEA功能插件HTTP_CLENT(idea2021插件)
-
工具描述:Java开发人员通用的开发者工具IDEA集成了HTTPClient功能,之后可以无需单独安装使用PostMan用来模拟http请求。创建方式:1)简易模式Tools->HTTPCl...
- RPC、Web Service等几种远程监控通信方式对比
-
几种远程监控通信方式的介绍一.RPCRPC使用C/S方式,采用http协议,发送请求到服务器,等待服务器返回结果。这个请求包括一个参数集和一个文本集,通常形成“classname.meth...
- 《github精选系列》——SpringBoot 全家桶
-
1简单总结1SpringBoot全家桶简介2项目简介3子项目列表4环境5运行6后续计划7问题反馈gitee地址:https://gitee.com/yidao620/springbo...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)