当前位置: 首页 > 科技观察

Android进阶Kotin协程原理及启动方法详解(协程的优雅使用)

时间:2023-03-16 01:16:48 科技观察

前言Kotlin的协程对于初学者来说是一个非常神奇的东西,它其实可以用同步代码块实现异步调用,其实如果你有深入理解,你会发现kotlin协程本质上是通过函数式编程风格对Java线程池的一种封装,会带来很多好处。首先,函数式+响应式编程风格避免了回调地狱。也可以说是promise、future(比如js)等语言的进一步进化。二是避免开发人员失误导致线程切换过多而带来的性能损失。那么我们来看看协程1.什么是协程?协程是封装到类似线程的API中的方法调用。方法调用当然比线程切换更轻;封装成类似线程的API后,看起来就像一个线程(可以手动启动,有多种运行状态,可以协同工作,可以并发执行)。所以从这个角度来说,它是一个轻量级的线程;当然,协程不仅仅是方法调用,因为方法调用不能在方法中间暂停,然后在原点恢复。这可以使用类似EventLoop的东西来实现。想象一下,在库级别将回调风格或Promise/Future风格的异步代码封装成同步风格,封装的结果非常接近协程;2.线程运行在内核态,协程运行在用户态主要理解什么叫用户态,我们写的代码几乎都是在用户态执行的。对于操作系统来说,协程只是第三方提供的一个库,当然是运行在用户态。线程是操作系统层面的东西,运行在内核态。3.Coroutine是一个线程框架。Kotlin的协程库可以指定协程运行的线程池。我们只需要对协程进行操作,必要的线程切换操作交给库即可。从这个角度来看,协程是一个线程框架。4.协程实现协程,顾名思义,就是相互协作的子程序。多个子程序相互关联,通过一定的机制协同完成某项任务。例如,一个协程在执行时可以分成多个子程序,每个子程序执行完后主动挂起,等待合适的时机再恢复;当一个协程挂起后,该线程可以执行其他子程序,从而达到该线程高利用率的多任务处理的目的——协程在一个线程上执行多个任务,而传统的线程只能执行一个任务。从多任务执行的角度来说,协程自然比线程要轻一些;5.Coroutinesolution用同步的方式写异步代码的问题。如果不使用协程,目前我们可以使用的API主要有三类:纯回调风格(如AIO)、RxJava、Promise/Future风格,它们一般都有回调地狱的问题,回调地狱只能通过改变行数来解决,而对于不熟悉异步风格的程序员来说,更难理解更复杂的异步代码。6、协程的优点轻量级:可以在一个线程上运行多个协程,因为协程支持挂起,不会阻塞运行协程的线程。与阻塞相比,挂起节省内存并支持多个并行操作。更少的内存泄漏:使用结构化并发在一个范围内执行多个操作。内置取消支持:取消会自动传播到整个正在运行的协程层次结构中。Jetpack集成:许多Jetpack库包含提供完整协程支持的扩展。一些库还提供了自己的协程作用域,您可以将其用于结构化并发;二、协程使用依赖{implementation'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'}coroutines需要在协程上下文中运行。在非协程环境中,可以通过三种方式凭空启动协程。1.runBlocking{}启动一个新的协程并阻塞当前线程,直到其所有内部逻辑和子协程逻辑全部执行完毕。结束。此方法旨在允许以挂起样式编写的库用于常规阻塞代码,通常用于主要方法和测试。overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Log.e(TAG,"mainthreadid:${mainLooper.thread.id}")test()Log.e(TAG,"协程执行结束")}privatefuntest()=runBlocking{repeat(8){Log.e(TAG,"协程执行$it线程id:${Thread.currentThread().id}")delay(1000)}}runBlocking启动的协程任务会阻塞当前线程,直到协程执行结束。直到协程执行结束才会显示页面。2.GlobalScope.launch{}在应用范围内启动一个新的协程,协程的生命周期与应用一致。以这种方式启动的协程不会使线程保持活动状态,就像守护线程一样。由于这种方式启动的协程虽然启动协程的组件被销毁了,但协程仍然存在,极端情况下可能会导致资源耗尽,所以不建议采用这种方式启动,尤其是在客户端需要频繁创建和销毁组件的场景。overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Log.e(TAG,"mainthreadid:${mainLooper.thread.id}")valjob=GlobalScope.launch{delay(6000)Log.e(TAG,"协程执行结束--线程id:${Thread.currentThread().id}")}Log.e(TAG,"主线程执行结束")}//Job方法job.isActivejob.isCancelledjob.isCompletedjob.cancel()jon.join()从执行结果可以看出launch不会阻塞主线程。总结一下launch,再看一下launch方法的定义:context)valcoroutine=if(start.isLazy)LazyStandaloneCoroutine(newContext,block)elseStandaloneCoroutine(newContext,active=true)coroutine.start(start,coroutine,block)returncoroutine}从方法定义可以看出,launch()是一个CoroutineScope函数的扩展,CoroutineScope只是协程的作用域。launch方法有3个参数:1.协程的上下文;2.协程的启动方式;3、协程体:block是一个带有接收者的函数字面量,接收者为CoroutineScope①。协程的上下文可以有很多功能,包括携带参数、拦截协程执行等,大多数情况下我们不需要自己实现上下文,直接使用现成的就可以了。上下文的一个重要作用是线程切换。Kotlin协程使用调度程序来确定哪些线程用于协程执行。Kotlin提供了一个调度器供我们使用:Dispatchers.Main:使用这个调度器来运行一个Android主线程Coroutine。可用于更新UI。在UI线程中执行Dispatchers.IO:此调度程序经过优化以在主线程之外执行磁盘或网络I/O。在线程池中执行Dispatchers.Default:此调度程序经过优化,可以在主线程之外执行CPU密集型工作。示例包括排序列表和解析JSON。在线程池中执行。Dispatchers.Unconfined:直接在调用线程中执行。调度器实现了CoroutineContext接口。②.启动模式在Kotlin协程中,启动模式定义在一个枚举类中:publicenumclassCoroutineStart{DEFAULT,LAZY,@ExperimentalCoroutinesApiATOMIC,@ExperimentalCoroutinesApiUNDISPATCHED;}一共定义了4种启动模式,下表为含义介绍:DEFAULT:默认模式,立即执行协程体LAZY:只在需要时运行ATOMIC:立即执行协程体,但在开始运行前不能取消UNDISPATCHED:立即在当前线程中执行协程体,直到第一次挂起调用③.Coroutinebody协程体是无参数无返回值的函数类型,用suspend关键字修饰。suspend修饰的函数称为suspend函数,对应关键字resume(恢复),注意:suspend函数只能在协程和其他suspend函数中调用,不能在其他地方使用。suspend函数会挂起整个协程,而不仅仅是这个suspend函数,也就是说当一个协程中有多个suspend函数时,它们是顺序执行的。请参见以下代码示例:8){Log.e(TAG,"mainthreadexecutes$it")}}privatefunsetUserInfo(userInfo:String){Log.e(TAG,userInfo)}privatesuspendfungetToken():String{delay(2000)return"token"}privatesuspendfungetUserInfo(token:String):String{delay(2000)return"$token-userInfo"}getToken方法会挂起协程,协程中后面的代码永远不会执行,只有在getToken被调用后才会执行暂停和恢复。同时协程挂起后不会阻塞其他线程的执行。3.async/await:Deferredasync和launch基本一样,不同的是:async的返回值是Deferred的,最后一个封装到这个对象中。异步可以支持并发。这时候一般和await一起使用。请参见下面的示例。async和await是两个函数,这两个函数在我们使用过程中一般都是成对出现的;async用于启动异步协程任务,await用于获取协程任务结束时返回的结果。结果是通过延迟对象覆盖返回的{getResult2()}valresult=result1.await()+result2.await()Log.e(TAG,"result=$result")}}privatesuspendfungetResult1():Int{delay(3000)return1}privatesuspendfungetResult2():Int{delay(4000)return2}async不会阻塞线程,也就是说getResult1和getResult2是同时执行的,所以得到结果的时间是4s而不是7s。三、协程异常1、由于协程取消,协程内部的suspend方法抛出CancellationException2、常规异常。此类异常启动有两种异常传播机制:自动将异常抛给父协程会导致父协程异步退出:将异常暴露给用户(通过捕获deffer.await()抛出的异常)示例解释funmain()=runBlocking{valjob=GlobalScope.launch{//rootcoroutinewithlaunchprintln("Throwingexceptionfromlaunch")throwIndexOutOfBoundsException()//我们会在控制台打印Thread.defaultUncaughtExceptionHandler}job.join()println("Joinedfailedjob")valdeferred=GlobalScope.async{//rootcoroutinewithasyncprintln("Throwingexceptionfromasync")throwArithmeticException()//什么都不打印,依赖用户调用等}try{deferred.await()println("Unreached")}catch(e:ArithmeticException){println("CaughtArithmeticException")}}结果ThrowingexceptionfromlaunchExceptioninthread"DefaultDispatcher-worker-2@coroutine#2"java.lang.IndexOutOfBoundsExceptionJoinedfailedjobThrowingexceptionfromasyncCaughtArithmeticException总结:协程可以取消超时,可以合并挂起的函数。协程中运行环境的规范,即线程的切换,是协程最常用的功能。并发,而并发的典型场景就是多线程。协程设计的初衷是为了解决并发问题,更容易实现协同多任务。如果简单理解Kotlin协程,就是封装好的线程池,也可以理解为线程框架。本文转载自微信公众号“Android开发与编程”,可通过以下二维码关注。转载本文请联系Android开发编程账号。