13518219792

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

Android进阶之Kotin协程原理和启动方式详细讲解(优雅使用协程)

[[415384]]

前言

kotlin的协程在初学者看来是一个很神奇的东西,居然能做到用同步的代码块实现异步的调用,其实深入了解你会发现kotlin协程本质上是通过函数式编程的风格对Java线程池的一种封装,这样会带来很多好处,首先是函数式+响应式编程风格避免了回调地狱,这也可以说是实现promise,future等语言(比如js)的进一步演进。其次是能够避免开发者的失误导致的线程切换过多的性能损失。

创新互联建站专注于玛纳斯企业网站建设,响应式网站开发,商城系统网站开发。玛纳斯网站建设公司,为玛纳斯等地区提供建站服务。全流程按需网站设计,专业设计,全程项目跟踪,创新互联建站专业和态度为您提供的服务

那么我们就来看看协程

一、协程(Coroutines)是什么

1、协程是轻量级线程

2、线程运行在内核态,协程运行在用户态

主要明白什么叫用户态,我们写的几乎所有代码,都执行在用户态,协程对于操作系统来说仅仅是第三方提供的库而已,当然运行在用户态。而线程是操作系统级别的东西,运行在内核态。

3、协程是一个线程框架

Kotlin的协程库可以指定协程运行的线程池,我们只需要操作协程,必要的线程切换操作交给库,从这个角度来说,协程就是一个线程框架

4、协程实现

协程,顾名思义,就是相互协作的子程序,多个子程序之间通过一定的机制相互关联、协作地完成某项任务。比如一个协程在执行上可以被分为多个子程序,每个子程序执行完成后主动挂起,等待合适的时机再恢复;一个协程被挂起时,线程可以执行其它子程序,从而达到线程高利用率的多任务处理目的——协程在一个线程上执行多个任务,而传统线程只能执行一个任务,从多任务执行的角度,协程自然比线程轻量;

5、协程解决的问题

同步的方式写异步代码。如果不使用协程,我们目前能够使用的API形式主要有三种:纯回调风格(如AIO)、RxJava、Promise/Future风格,他们普遍存在回调地狱问题,解回调地狱只能通过行数换层数,且对于不熟悉异步风格的程序员来说,能够看懂较为复杂的异步代码就比较费劲。

6、协程优点

二、协程使用

 
 
 
 
  1. 依赖 
  2. dependencies { 
  3.     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' 

协程需要运行在协程上下文环境,在非协程环境中凭空启动协程,有三种方式

1、runBlocking{}

启动一个新协程,并阻塞当前线程,直到其内部所有逻辑及子协程逻辑全部执行完成。

该方法的设计目的是让suspend风格编写的库能够在常规阻塞代码中使用,常在main方法和测试中使用。

 
 
 
 
  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     Log.e(TAG, "主线程id:${mainLooper.thread.id}") 
  5.     test() 
  6.     Log.e(TAG, "协程执行结束") 
  7. private fun test() = runBlocking { 
  8.     repeat(8) { 
  9.         Log.e(TAG, "协程执行$it 线程id:${Thread.currentThread().id}") 
  10.         delay(1000) 
  11.     } 

runBlocking启动的协程任务会阻断当前线程,直到该协程执行结束。当协程执行结束之后,页面才会被显示出来。

2、GlobalScope.launch{}

在应用范围内启动一个新协程,协程的生命周期与应用程序一致。这样启动的协程并不能使线程保活,就像守护线程。

由于这样启动的协程存在启动协程的组件已被销毁但协程还存在的情况,极限情况下可能导致资源耗尽,因此并不推荐这样启动,尤其是在客户端这种需要频繁创建销毁组件的场景。

 
 
 
 
  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     Log.e(TAG, "主线程id:${mainLooper.thread.id}") 
  5.     val job = GlobalScope.launch { 
  6.         delay(6000) 
  7.         Log.e(TAG, "协程执行结束 -- 线程id:${Thread.currentThread().id}") 
  8.     } 
  9.     Log.e(TAG, "主线程执行结束") 

 

 
 
 
 
  1. //Job中的方法 
  2. job.isActive 
  3. job.isCancelled 
  4. job.isCompleted 
  5. job.cancel() 
  6. jon.join() 

从执行结果看出,launch不会阻断主线程。

下面我们来总结launch

我们看一下launch方法的定义:

 
 
 
 
  1. public fun CoroutineScope.launch( 
  2.     context: CoroutineContext = EmptyCoroutineContext, 
  3.     start: CoroutineStart = CoroutineStart.DEFAULT, 
  4.     block: suspend CoroutineScope.() -> Unit 
  5. ): Job { 
  6.     val newContext = newCoroutineContext(context) 
  7.     val coroutine = if (start.isLazy) 
  8.         LazyStandaloneCoroutine(newContext, block) else 
  9.         StandaloneCoroutine(newContext, active = true) 
  10.     coroutine.start(start, coroutine, block) 
  11.     return coroutine 

从方法定义中可以看出,launch() 是CoroutineScope的一个扩展函数,CoroutineScope简单来说就是协程的作用范围。launch方法有三个参数:1.协程下上文;2.协程启动模式;3.协程体:block是一个带接收者的函数字面量,接收者是CoroutineScope

①.协程下上文

②.启动模式

在Kotlin协程当中,启动模式定义在一个枚举类中:

 
 
 
 
  1. public enum class CoroutineStart { 
  2.     DEFAULT, 
  3.     LAZY, 
  4.     @ExperimentalCoroutinesApi 
  5.     ATOMIC, 
  6.     @ExperimentalCoroutinesApi 
  7.     UNDISPATCHED; 

一共定义了4种启动模式,下表是含义介绍:

③.协程体

协程体是一个用suspend关键字修饰的一个无参,无返回值的函数类型。被suspend修饰的函数称为挂起函数,与之对应的是关键字resume(恢复),注意:挂起函数只能在协程中和其他挂起函数中调用,不能在其他地方使用。

suspend函数会将整个协程挂起,而不仅仅是这个suspend函数,也就是说一个协程中有多个挂起函数时,它们是顺序执行的。看下面的代码示例:

 
 
 
 
  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     GlobalScope.launch { 
  5.         val token = getToken() 
  6.         val userInfo = getUserInfo(token) 
  7.         setUserInfo(userInfo) 
  8.     } 
  9.     repeat(8){ 
  10.         Log.e(TAG,"主线程执行$it") 
  11.     } 
  12. private fun setUserInfo(userInfo: String) { 
  13.     Log.e(TAG, userInfo) 
  14. private suspend fun getToken(): String { 
  15.     delay(2000) 
  16.     return "token" 
  17. private suspend fun getUserInfo(token: String): String { 
  18.     delay(2000) 
  19.     return "$token - userInfo" 

getToken方法将协程挂起,协程中其后面的代码永远不会执行,只有等到getToken挂起结束恢复后才会执行。同时协程挂起后不会阻塞其他线程的执行。

3.async/await:Deferred

async跟launch的用法基本一样,区别在于:async的返回值是Deferred,将最后一个封装成了该对象。async可以支持并发,此时一般都跟await一起使用,看下面的例子。

async和await是两个函数,这两个函数在我们使用过程中一般都是成对出现的;

async用于启动一个异步的协程任务,await用于去得到协程任务结束时返回的结果,结果是通过一个Deferred对象返回的

 
 
 
 
  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     GlobalScope.launch { 
  5.         val result1 = GlobalScope.async { 
  6.             getResult1() 
  7.         } 
  8.         val result2 = GlobalScope.async { 
  9.             getResult2() 
  10.         } 
  11.         val result = result1.await() + result2.await() 
  12.         Log.e(TAG,"result = $result") 
  13.     } 
  14. private suspend fun getResult1(): Int { 
  15.     delay(3000) 
  16.     return 1 
  17. private suspend fun getResult2(): Int { 
  18.     delay(4000) 
  19.     return 2 

async是不阻塞线程的,也就是说getResult1和getResult2是同时进行的,所以获取到result的时间是4s,而不是7s。

三、协程异常

1、因协程取消,协程内部suspend方法抛出的CancellationException

2、常规异常,这类异常,有两种异常传播机制

例子讲解

 
 
 
 
  1. fun main() = runBlocking { 
  2.     val job = GlobalScope.launch { // root coroutine with launch 
  3.         println("Throwing exception from launch") 
  4.         throw IndexOutOfBoundsException() // 我们将在控制台打印 Thread.defaultUncaughtExceptionHandler 
  5.     } 
  6.     job.join() 
  7.     println("Joined failed job") 
  8.     val deferred = GlobalScope.async { // root coroutine with async 
  9.         println("Throwing exception from async") 
  10.         throw ArithmeticException() // 没有打印任何东西,依赖用户去调用等待 
  11.     } 
  12.     try { 
  13.         deferred.await() 
  14.         println("Unreached") 
  15.     } catch (e: ArithmeticException) { 
  16.         println("Caught ArithmeticException") 
  17.     } 

结果

 
 
 
 
  1. Throwing exception from launch 
  2. Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException 
  3. Joined failed job 
  4. Throwing exception from async 
  5. Caught ArithmeticException 

总结:

本文转载自微信公众号「 Android开发编程」,可以通过以下二维码关注。转载本文请联系 Android开发编程众号。


当前题目:Android进阶之Kotin协程原理和启动方式详细讲解(优雅使用协程)
当前链接:http://cdbrznjsb.com/article/dpcpdhj.html

其他资讯

让你的专属顾问为你服务