Kotlin 协程理解和实战
1.什么是协程
首先在讲协程之前,我们需要先搞清楚几个概念
1.1程序、进程、CPU、内存关系
如上图,平时我们打包好一个应用,放在磁盘上,此时我们称之为程序或者应用,是静态的,比我我们熟悉的apk文件。 当我们执行程序(比如点击某个App),OS 会将它加载进内存,CPU 从内存某个起始地址开始读取指令并执行程序。
程序从磁盘上加载到内存并被CPU运行期间,称之为进程。因此我们通常说某个应用是否还在存活,实际上说的是进程是否还在内存里;也会说某某程序CPU占用率太高,实际上说的是进程的CPU占用率。
而操作系统负责管理磁盘、内存、CPU等交互,可以说是大管家。
1.2 进程和线程之间的关系
现在我们用简单的图片展示一下他们之间的关系
上图是单核CPU的情况
1.3 线程和协程之间的关系
1.4 kotlin 协程的优势
1、协程是轻量级线程、比线程耗费资源少 这话虽然是官方说的,但我觉得有点误导的作用,协程是语言层面的东西,线程是系统层面的东西,两者没有可比性。 协程就是一段代码块,既然是代码那就离不开CPU的执行,而CPU调度的基本单位是线程。
2、协程是线程框架 协程解决了移步编程时过多回调的问题,既然是异步编程,那势必涉及到不同的线程。Kotlin 协程内部自己维护了线程池,与Java 线程池相比有些优化的地方。在使用协程过程中,无需关注线程的切换细节,只需指定想要执行的线程即可,从对线程的封装这方面来说这说话也没问题。
3、协程效率高于线程 与第一点类似,协程在运行方面的高效率其实换成回调方式也是能够达成同样的效果,实际上协程内部也是通过回调实现的,只是在编译阶段封装了回调的细节而已。因此,协程与线程没有可比性。
2.kotlin协程初认识
在Kotlin中,协程就是线程的封装,它提供了一套标准的API来帮助我们编写并发任务。
在java中实现多任务并发
//线程
new Thread(new Runnable() {
@Override
public void run() {
//耗时的工作
}
}).start();
//线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(new Runnable() {
@Override
public void run() {
//耗时的工作
}
});
android中实现多任务并发
在Android中,除了可以通过Java的方式,创建线程、使用线程池实现多任务并发之外,还可以AsyncTask
等方式来实现多个耗时任务的并发执行:
//AsyncTask
public abstract class AsyncTask<Params, Progress, Result> {
//线程池中执行,执行耗时任务
protected abstract Result doInBackground(Params... params);
//UI线程中执行,后台任务进度有变化则执行该方法
protected void onProgressUpdate(Progress... values) {}
//UI线程执行,耗时任务执行完成后,该方法会被调用,result是任务的返回值
protected void onPostExecute(Result result) {}
}
无论是Java还是Android提供的组件,都可以实现多任务并发的执行,但是上面的组件都或多或少存在一些问题:
- 耗时任务执行结束后,子线程要将结果传递回主线程,两者之间的通信不太方便。
-
AsyncTask
处理的回调方法比较多,当有多个任务时可能会出现回调嵌套。
使用协成实现多任务并发
我们还是使用AsyncTask举例
AsyncTask<String, Integer, String> task = new AsyncTask<String, Integer, String>() {
@Override
protected String doInBackground(String... strings) {
String userId = getUserId(); //获取userId
return userId;
}
@Override
protected void onPostExecute(final String userId) {
AsyncTask<String, Integer, String> task1 = new AsyncTask<String, Integer, String>() {
@Override
protected String doInBackground(String... strings) {
String name = getUserName(userId); //获取userName,需要用到userId
return name;
}
@Override
protected void onPostExecute(String name) {
textView.setText(name); //设置到TextView控件中
}
};
task1.execute(); //假设task1是一个耗时任务,去获取userName
}
};
task.execute(); //假设task是一个耗时任务,去获取userId
如果使用kotlin协程,上面的代码可以简化如下:
GlobalScope.launch(Dispatchers.Main) {
val userId = getUserId() //耗时任务,这里会切换到子线程
val userName = getUserName(userId) //耗时任务,这里会切换到子线程
textView.text = userName //设置到TextView控件中,切换到主线程
}
suspend fun getUserId(): String = withContext(Dispatchers.IO) {
//耗时操作,返回userId
}
suspend fun getUserName(userId: String): String = withContext(Dispatchers.IO) {
//耗时操作,返回userName
}
上面launch
函数的{}的逻辑,就是一个协程。
相比于AsyncTask
的写法,我们可以看到使用kotlin协程有以下好处:
- 协程将耗时任务和UI更新放在了上下三行处理,消除了
AsyncTask
的回调嵌套,使用起来更加方便、简洁。 - 协程通过挂起与恢复,将耗时任务的结果直接返回给调用方,使得主线程能直接使用子线程的结果,UI更新更加方便
3.kotlin协程的接入和使用
引入kotlin协程相关依赖
在模块的build.gradle
中加入以下依赖:
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
}
使用协程
Kotlin提供了三种方式来创建协程,如下所示:
//方式一
runBlocking { //runBlocking是一个顶级函数
...
}
//方式二
GlobalScope.launch { //GlobalScope是一个单例对象,直接使用launch开启协程
...
}
//方式三
val coroutineScope = CoroutineScope(context) //使用CoroutineContext创建CoroutineScope对象,通过launch开启协程
coroutineScope.launch {
...
}
-
方式一:它是线程阻塞的,它通常被用在单元测试和main函数中,平时的开发中我们一般不会用到它。
-
方式二:与方式一相比,它不会阻塞线程,但是它的生命周期和应用是一致的,而且无法做到取消(后面会讲到),所以也不推荐使用。
-
方式三:通过
CoroutineContext
来创建一个CoroutineScope
对象,通过CoroutineScope.launch
或CoroutineScope.async
可以开启协程,通过CoroutineContext
也可以控制协程的生命周期。在开发过程中,一般推荐使用这种方式开启协程。
作者:用户202091961859
链接:https://juejin.cn/post/7139715683390554120