前言
最近在研究 Kotlin 协程, 发现功能真的超级强大, 很有用, 而且很好学, 如果你正在或计划使用 Kotlin 开发 Android, 那么 Kotlin 协程你一定不能错过!
协程是什么?
我们平常接触的都是进程, 线程, 在开发中使用最多的就是线程, 比如主线程, 子线程, 而且操作系统里最小可操作的单元就是线程, 那协程又是什么? 协程是比线程更小的单位, 但并不是说在操作系统里最小可操作单元就从线程变成了协程, 而是协程依然运行在线程上, 协程是在语言上实现的比线程更小的单位.
那么你可能有疑问, 既然协程还是运行在线程上, 那直接使用线程不就好了吗? 但问题是往往我们用不好线程, 首先创建一个线程的成本很高, 在 Android 中创建一个线程, 大约要消耗 1M 的内存, 而且, 如果使用线程池, 线程间数据的同步也是一个非常麻烦复杂的事情, 所以就有了协程:
可以看作是轻量级线程, 创建一个协程的成本很低
可以轻松的挂起和恢复操作
支持阻塞线程的协程和不阻塞线程的协程
可以更好的实现异步和并发
Kotlin 协程库: Kotlin.coroutines
实现协程的库是 Kotlin.coroutines, 点击查看 Kotlin.coroutines https://github.com/Kotlin/kotlinx.coroutines 在 GitHub 上的源码.
Kotlin 是一门支持 多平台的语言, 所以 Kotlin.coroutines 也是支持多平台的, 包括:
Kotlin/JS
Kotlin/Native 包括 PC 和 Android
我们使用 Kotlin.coroutines 的 Android 版本.
给 Android 工程添加 Kotlin 协程库
要使用协程, Kotlin 的版本必须在 1.3 以上.
升级 Kotlin 到 最新版本 1.3.+
在 Android Studio 中选择 Android Stuido -> Preference... -> Languages & Framewroks -> Kotlin
在这里升级 Kotlin
创建使用 Kotlin 开发的 Android 工程
在 Android Studio 中选择 File -> New -> New Project...
在这个界面里选中 Include Kotlin support , 剩下的和创建一般 Android 工程是一样的.
配置 Kotlin 协程库的依赖
在 App/build.gradle 里添加 Kotlin 协程库的依赖:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
创建 Coroutine(协程):Coroutine Builder
创建 Coroutine 需要使用 Coroutine Builder 函数, 包括:
作用 | |
---|---|
launch | 创建一个不会阻塞当前线程、没有返回结果的 Coroutine, 但会返回一个 Job 对象,可以用于控制这个 Coroutine 的执行和取消 |
runBlocking | 创建一个会阻塞当前线程的 Coroutine |
其实不止这两个, 但本篇先介绍这两个.
launch
使用 GlobalScope.launch 来创建协程, 使用方法如下:
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- var job = GlobalScope.launch(Dispatchers.Main) {
- var content = fetchData()
- Log.d("Coroutine",content)
- }
- }
- suspend fun fetchData():String{
- delay(2000)
- return "content"
- }
Activity 的 onCreate() 里, 用 GlobalScope.launch 创建一个协程, 在协程里我模拟了一个请求, 去获取数据, 然后把数据打印出来.
因为 GlobalScope.launch 是无阻塞的, 所以不会阻塞 UI 线程.
这里 GlobalScope.launch 创建之后, 会返回一个 Job 对象, Job 对象可以这么使用:
job.cancel() : 取消协程
job.join() : 让协程运行完
runBlocking
使用 runBlocking 来创建协程, 使用方法如下:
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- runBlocking {
- var content = fetchData()
- Log.d("Coroutine",content)
- }
- }
- suspend fun fetchData():String{
- delay(2000)
- return "content"
- }
功能和上一个例子一样, 但是这里协程创建改成了 runBlocking(), 但是 runBlocking() 是会阻塞线程的, 所以这里会阻塞 UI 线程, 这里是一个错误用例的示范 (Orz...)
suspend 方法
在前面介绍协程的代码里, 有个不起眼的函数:
- suspend fun fetchData():String{
- delay(2000)
- return "content"
- }
suspend 方法是协程里的特有方法.
suspend 方法的定义
suspend 方法的声明很简单, 只需在方法 或 Lambda 定义前面加 suspend 关键字即可.
suspend 方法的使用限制
suspend 方法使用由限制, 只能有两个地方允许使用 suspend 方法:
在协程内部使用
在另一个 suspend 方法里使用
如果你在一个普通方法内存使用 suspend 方法, 是会报语法错误的.
suspend 方法的功能
suspend 方法能够使协程执行暂停, 等执行完毕后在返回结果, 同时不会阻塞线程.
是不是很神奇, 只暂停协程, 但不阻塞线程.
而且暂停协程里方法的执行, 直到方法返回结果, 这样也不用写 Callback 来取结果, 可以使用同步的方式来写异步代码, 真是漂亮啊.
Coroutine context 与 Coroutine dispatchers
想要使用协程, 还有两个重要的元素:
Coroutine context: 协程上下文
Coroutine dispatchers : 协程调度
Coroutine context: 协程上下文
协程上下文里是各种元素的集合. 具体的之后的文章在讲.
Coroutine dispatchers : 协程调度
我们已经知道协程是运行在线程上的, 我们获取数据后要更新 UI , 但是在 Android 里更新 UI 只能在主线程, 所以我们要在子线程里获取数据, 然后在主线程里更新 UI. 这就需要改变协程的运行线程, 这就是 Coroutine dispatchers 的功能了.
Coroutine dispatchers 可以指定协程运行在 Android 的哪个线程里.
我们先看下 dispatchers 有哪些种类:
作用 | |
---|---|
Dispatchers.Default | 共享后台线程池里的线程 |
Dispatchers.Main | Android 主线程 |
Dispatchers.IO | 共享后台线程池里的线程 |
Dispatchers.Unconfined | 不限制,使用父 Coroutine 的现场 |
newSingleThreadContext | 使用新的线程 |
在看前面的代码里, 细心的你肯定发现:
- var job = GlobalScope.launch(Dispatchers.Main) {
- var content = fetchData()
- Log.d("Coroutine",content)
- }
GlobalScope.launch 后面的 Dispatchers.Main 就是指定协程在 Android 主线程里运行.
那么, 如何切换线程呢? 如下:
- GlobalScope.launch(Dispatchers.IO) {
- ...
- withContext(Dispatchers.Main) {
- ...
- }
- }
使用 withContext 切换协程, 上面的例子就是先在 IO 线程里执行, 然后切换到主线程.
Android 开发中使用 协程
讲完协程的基本用法, 你还是不知道改如何用到自己的代码里, 这里给出一个最基本的用法, 后续的使用方法会不断补充.
首先 MainActivity 要 实现 CoroutineScope 这个接口, CoroutineScope 的实现教由代理类 MainScope, 所以是这样子的:
class MainActivity : AppCompatActivity(),CoroutineScope by MainScope()
这样 MainActivity 就是一个协程, 那么要获取数据, 并展示在 UI 上, 就可以这么写:
- class MainActivity : AppCompatActivity(),CoroutineScope by MainScope() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- setSupportActionBar(toolbar)
- // 加载并显示数据
- loadDataAndShow()
- }
- fun loadDataAndShow(){
- GlobalScope.launch(Dispatchers.IO) {
- //IO 线程里拉取数据
- var result = fetchData()
- withContext(Dispatchers.Main){
- // 主线程里更新 UI
- text.setText(result)
- }
- }
- }
- suspend fun fetchData():String{
- delay(2000)
- return "content"
- }
- override fun onDestroy() {
- super.onDestroy()
- // 取消掉所有协程内容
- cancel()
- }
- }
完全不用担心会阻塞主线程
用同步的方式来写异步代码
而且不用担心内存泄露的问题
Kotlin 协程, 你有没有心动?
未完待续...
来源: https://juejin.im/post/5c959264f265da60df410c0e