kotlin
官方文档说:本质上,协程是轻量级的线程。
从 Android 开发者的角度去理解它们的关系:android
NetworkOnMainThreadException
,对于在主线程上的协程也不例外,这种场景使用协程仍是要切线程的。咱们学习Kotlin
中的协程,一开始确实能够从线程控制的角度来切入。由于在 Kotlin
中,协程的一个典型的使用场景就是线程控制。就像 Java 中的 Executor
和 Android 中的 AsyncTask
,Kotlin
中的协程也有对 Thread API 的封装,让咱们能够在写代码时,不用关注多线程就可以很方便地写出并发操做。数据库
小结:express
Kotlin
协程的话,就是封装好的线程池,也能够理解成一个线程框架。RxJava
能够解决回调问题,一样咱们能够用协程解决回调问题。网络
解释说明:多线程
Kotlin
版本: 1.3.+app/build.gradle
里添加 Kotlin
协程库的依赖以下所示。//kotlin 标准库 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" //依赖协程核心库 ,提供Android UI调度器 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1" //依赖当前平台所对应的平台库 (必须) implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
方式 | 做用 |
---|---|
launch:job |
建立一个不会阻塞当前线程、没有返回结果的 Coroutine,但会返回一个 Job 对象,能够用于控制这个 Coroutine 的执行和取消,返回值为Job。 |
runBlocking:T |
建立一个会阻塞当前线程的Coroutine,经常使用于单元测试的场景,开发中通常不会用到 |
async/await:Deferred |
async 返回的 Coroutine 多实现了 Deferred 接口,简单理解为带返回值的launch函数 |
实现方式一: GlobalScope.launch
,使用 GlobalScope 单例对象, 能够直接调用 launch 开启协程。并发
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_thread) loadData() } private fun loadData() { GlobalScope.launch(Dispatchers.IO) { //在IO线程开始 //IO 线程里拉取数据 val result = fetchData() //主线程里更新 UI withContext(Dispatchers.Main) { //执行结束后,自动切换到UI线程 tvShowContent.text = result } } } //关键词 suspend private suspend fun fetchData(): String { delay(2000) // delaying for 2 seconds to keep JVM alive return "content" }
咱们最经常使用的用于启动协程的方式,它最终返回一个Job类型的对象,这个Job类型的对象其实是一个接口,它包涵了许多咱们经常使用的方法。 该方式启动的协程任务是不会阻塞线程的* app
实现方式二:使用 runBlocking
顶层函数 框架
runBlocking {}
是建立一个新的协程同时阻塞当前线程,直到协程结束。这个不该该在协程中使用,主要是为main
函数和测试设计的 。异步
fun main(args: Array<String>) = runBlocking { // start main coroutine launch { // launch new coroutine in background and continue delay(1000L) println("World!") } println("Hello,") // main coroutine continues here immediately delay(2000L) // delaying for 2 seconds to keep JVM alive }
实现方式三:async
+await
async
private fun testAysnc() = GlobalScope.launch { val deferred = async(Dispatchers.IO) { delay(3000L) "Show Time" } // 此处获取耗时任务的结果,咱们挂起当前协程,并等待结果 val result = deferred.await() //挂起协程切换至UI线程 展现结果 withContext(Dispatchers.Main) { tvShowContent.text = result } }
那咱们平日里经常使用到的调度器有哪些?
Dispatchers 种类 |
做用 |
---|---|
Dispatchers.Default | 共享后台线程池里的线程(适合 CPU 密集型的任务,好比计算) |
Dispatchers.Main | Android中的主线程 |
Dispatchers.IO | 共享后台线程池里的线程(针对磁盘和网络 IO 进行了优化,适合 IO 密集型的任务,好比:读写文件,操做数据库以及网络请求) |
Dispatchers.Unconfined | 不限制,使用父Coroutine的现场 |
回到咱们的协程,它从 suspend
函数开始脱离启动它的线程,继续执行在 Dispatchers
所指定的 IO 线程。
紧接着在 suspend
函数执行完成以后,协程为咱们作的最爽的事就来了:会自动帮咱们把线程再切回来。
这个"切回来"是什么意思?
咱们的协程本来是运行在主线程的,当代码遇到 suspend 函数的时候,发生线程切换,根据 Dispatchers
切换到了 IO 线程;
当这个函数执行完毕后,线程又切了回来,"切回来"也就是协程会帮我再 post
一个 Runnable
,让我剩下的代码继续回到主线程去执行。
从相册中直接读取图片,这是一个典型的IO操做使用场景,操做不当,可能会出现ANR。
版本1.0实现方式
val mImageUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, mImageUri) imageView.setImageBitmap(bitmap)
版本2.0 咱们可能会引入Handler
或 AysnTask
来经过异步的方式实现
版本3.0 咱们能够这样用doAsync
实现 这种方式也不错
doAsync{ //后台执行 val mImageUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,mImageUri) //回到主线程 uiThread{ imageView.setImageBitmap(bitmap) } }
版本4.0 时咱们就能够用协程来实现。
val job = launch(Background) { val mImageUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,mImageUri) launch(UI) { imageView.setImageBitmap(bitmap) }
这里的参数Background是一个CoroutineContext对象,确保这个协程运行在一个后台线程,确保你的应用程序不会因耗时操做而阻塞和崩溃。你能够像下边这样定义一个CoroutineContext:
internal val Background = newFixedThreadPoolContext(2, "bg")
人个感受 最后两种方式均可取。
后面介绍的三种使用方式在实现前须要分别添加如下的依赖包
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc02' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc02' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc02'
为应用程序中的每一个ViewModel
定义ViewModelScope
。若是清除ViewModel
,则在此做用域中启动的任何协同程序都将自动取消。
当只有在ViewModel处于活动状态时才须要完成工做时,协程在这里很是有用。
例如,若是要为布局计算某些数据,则应将工做范围设置为ViewModel,以便在清除ViewModel时,自动取消工做以免消耗资源。
能够经过ViewModel的viewModelScope属性访问ViewModel的协同做用域,以下例所示:
class MyViewModel :ViewModel() { init { viewModelScope.launch { // Coroutine that will be canceled when the ViewModel is cleared. } } }
为每一个Lifecycle
定义LifecycleScope
。当 Lifecycle
销毁时,在此范围内启动的任何协同程序都将被取消。
您能够经过Lifecycle.CoroutineScope
或lifecycleOwner.lifecycleScope
属性访问Lifecycle
的 CoroutineScope
。
下面的示例演示如何使用lifecycleOwner.lifecycleScope
异步建立预计算文本:
class MyFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { val params = TextViewCompat.getTextMetricsParams(textView) val precomputedText = withContext(Dispatchers.Default) { PrecomputedTextCompat.create(longTextContent, params) } TextViewCompat.setPrecomputedText(textView, precomputedText) } } }
使用LiveData时,可能须要异步计算值。例如,您可能但愿检索用户的首选项并将其提供给您的UI。在这些状况下,可使用liveData builder函数调用suspend函数,将结果做为liveData对象提供。
在下面的示例中,loadUser()是在别处声明的挂起函数。使用liveData 构建函数异步调用loadUser(),而后使用emit()发出结果。
val user: LiveData<User> = liveData { val data = database.loadUser() // loadUser is a suspend function. emit(data) }
LiveData构建块充当协同路由和liveData之间的结构化并发原语。代码块在LiveData变为活动时开始执行,而且在LiveData变为非活动时通过可配置的超时后自动取消。若是在完成以前取消,则在LiveData再次激活时从新启动。若是在上一次运行中成功完成,则不会从新启动。请注意,只有在自动取消时才会从新启动。若是因为任何其余缘由(例如抛出异常CancelationException)而取消块,则不会从新启动它。
也能够从块中发射多个值。每次emit()调用都会暂停块的执行,直到在主线程上设置LiveData值。
val user: LiveData<Result> = liveData { emit(Result.loading()) try { emit(Result.success(fetchUser())) } catch(ioException: Exception) { emit(Result.error(ioException)) } }
咱们也能够和 LifeCycle
中的Transformations
结合使用,以下例所示:
class MyViewModel: ViewModel() { private val userId: LiveData<String> = MutableLiveData() val user = userId.switchMap { id -> liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { emit(database.loadUserById(id)) } } }
retrofit 2.6.0(2019-06-05)中的更新日志以下:
Support
suspend
modifier on functions for Kotlin! This allows you to express the asynchrony of HTTP requests in an idiomatic fashion for the language.
@GET("users/{id}") suspend fun user(@Path("id") id: Long): User
Behind the scenes this behaves as if defined asfun user(...): Call
and then invoked withCall.enqueue
. You can also returnResponse
for access to the response metadata.
在函数前加上 suspend
函数直接返回你须要对象类型不须要返回Call
对象
本文总结了kotlin中的协程的相关知识点,协程是值得深刻研究的。 将来的项目中运用是趋势所在,现将学习的心得总结于此,方便将来迭代中作为技术的储备。若有不足之处,欢迎留言讨论。
参考资料:
3.Kotlin 的协程用力瞥一眼 - 学不会协程?极可能由于你看过的教程都是错的
5.【码上开学】Kotlin 协程的挂起好神奇好难懂?今天我把它的皮给扒了