1.什么是协程?
是一套基于线程的API框架,重点:还是基于线程。
2.协程有什么用?
可以灵活地切换线程,用同步的方式写出异步代码,解决回调地狱问题,重点:不用回调就可以处理异步任务返回。
3.协程作用域是什么?
以下代码,GlobalScope.launch大括号内就是协程作用域。
GlobalScope.launch {//作用域
Log.d("test", "1")
}
Log.d("test", "2")
4.如何使用协程?
4.1 GlobalScope.launch
创建顶级协程作用域,因为GlobalScope是单例,所以这样的协程生命周期跟应用一致,只有应用进程结束,协程才会结束,使用时需要注意内存泄露问题。
以下代码可以很轻松敌将for循环任务切换到另外的线程执行。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
GlobalScope.launch {//作用域
for (index in 1..1000) {//切换到另外的线程
Log.d("test", "$index")
}
}
}
解决内存泄露问题
GlobalScope.launch会返回Job对象,在onDestroy方法中调用其cancel,可以使协程结束。协程内需要增加isActive判断或者try-catch处理,根据信息结束代码执行,这样协程才会结束,这个跟中断线程类似。
class MainActivity : AppCompatActivity() {
var job: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
job = GlobalScope.launch {//作用域
for (index in 1..1000) {
if (isActive) {//判断是否被cancel
delay(200)
Log.d("test", "$index")
}
}
}
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
}
4.2 launch
创建子协程,也就是在其他协程作用域内创建,例如在GlobalScope.launch内。子协程是不会影响父协程的执行,可以说这两个协程是并发的。
GlobalScope.launch {
launch {
Log.d("test", "launch")
}
Log.d("test", " GlobalScope.launch")//不受被子协程的影响
}
4.3 withContext
同样是创建子协程,与launch不同的是:
4.3.1 withContext可以返回执行结果,而launch不行;
4.3.2 withContext是一个suspend挂起函数,会影响父协程的执行;
以下代码,从打印日志可以看出,子协程执行完父协程才会继续执行,因为父协程执行到withContext会将自身挂起,等待其执行结束。
GlobalScope.launch {
val result = withContext(Dispatchers.IO) {//指定协切换到IO线程执行
delay(1000)
Log.d("test", "launch")
1//最后一行代码把1返回,接受参数类型会自动推导
}
Log.d("test", "$result")
}
//打印结果
2021-05-25 19:19:25.662 812-6239/com.example.myapplication D/test: launch
2021-05-25 19:19:25.665 812-6241/com.example.myapplication D/test: 1
4.4 runBlocking
跟GlobalScope.launch一样,创建顶级协程,不过runBlocking可以保证作用域代码在线程结束前一定执行完。
以下代码,在runBlocking内代码没有执行完,线程最后一行打印是不会执行的。
Thread {
Log.i("----test", "thread start")
runBlocking {
delay(5 * 1000)
Log.i("----test", "runBlocking")
}
Log.i("----test", "thread end")
}.start()
4.4 async
创建子协程,与withContext相同,作用域最后一行代码可以作为返回值,不同的是,async可以做并发处理,而withContext不行,因为其是挂起函数会影响父协程的执行。
以下代码,任务1和任务2的打印交替进行。注意:如果任务1以及任务2 的await不是同时执行,那么任务将不是并发的,大家可以试试。
val result1 = async(Dispatchers.IO) {//任务1
for (index in 0 until 10 * 1000) {
delay(500)
Log.i("test", "result2 $index")
}
}
val result2 = async(Dispatchers.IO) {//任务2
for (index in 0 until 10 * 1000) {
delay(500)
Log.i("test", "result2 $index")
}
}
result1.await()
result2.await()
以下业务场景很适合使用async并发网络请求,合并请求做后续业务处理。
GlobalScope.launch(Dispatchers.Main) {
val asyn1 = async(Dispatchers.IO) {
//网络请求1
1
}
val asyn2 = async(Dispatchers.IO) {
val result1 = async(Dispatchers.IO) {
//网络请求2
2
}
}
val result1 = asyn1.await()
val result2 = asyn2.await()
//并发拿到请求1 请求2结果 切换到主线程去做其他业务处理
}
5.挂起函数
5.1 什么是挂起?
简单的说,就是切线程,将函数切换到另外的线程执行,保存当前状态,以便恢复。
5.2 到底挂起的是什么?
挂起的不是线程,不是函数,而是协程,也就是协程作用域内的代码。当遇到挂起,那么suspend函数后的协程代码需要等待函数返回才会执行。
5.2 suspend的作用?
只是起一个提醒作用,没错,只是提醒作用。提醒什么呢?提醒函数的调用者,这个函数是耗时函数,必须在协程内执行,另外还提醒函数的创建者,函数必须执行耗时任务或者其他suspend函数,如果没有包含此内容,那么函数放在协程内执行就显得多余,编译器会提示suspend描述多余。
6.delay函数
也是一个挂起函数,将协程挂起,该函数后的代码将延迟执行。
delay是非阻塞式挂起,不会阻塞线程。
Thread.sleep()同样也有延迟执行的作用,但它会阻塞线程。
以下代码,在主线程做测试,Dispatchers.Main指定协程在主线程执行,delay延迟10秒,但是协程外同样是在主线程的for循环任务以及点击事件不受影响。
如果换成 Thread.sleep(10 * 1000),那么主线程的绘制、for循环任务以及点击事件都会收到影响。
GlobalScope.launch(Dispatchers.Main) {
delay(10 * 1000)//挂起线程,延迟执行后续操场
if (Looper.getMainLooper() == Looper.myLooper()) {
Log.i("test", "主线程");
}
Log.i("test", "Dispatchers.Main");
}
for (index in 0 until 1000) {//任务不会被堵塞
Log.i("test", "$index");
}
testTv.setOnClickListener {//点击事件不会被堵塞
Log.i("test", "setOnClickListener");
}
7.总结
创建协程的方法有很多,这里只列举了部分,
7.1 GlobalScope.launch创建顶级协程,返回值是Job,可以用于取消协程,其生命周期跟应用一致,注意内存泄露问题;
7.2 launch创建子协程,返回值是Job,可以用于取消协程;
7.3 withContext创建子协程,是挂起函数,会影响父协程,可以有返回结果;
7.4 async创建子协程,可以有返回结果,可以处理并发;
7.5 以上方法创建的协程都可以指定执行的线程;
以上分析有不对的地方,请指出,互相学习,谢谢哦!