协程是什么? 协程基于线程,是轻量级的线程.
- 处理耗时任务(这种任务常常会阻塞主线程)
- 保证主线程安全(确保安全的从主线程调用任何的suspend函数).
// 模拟耗时操作,并更新ui.
lifecycleScope.launch {
delay(2000) // 挂起当前协程2秒钟
binding.tvText.setText(data)
}
在这个实例中,我们使用了kotlin协程库,通过调用launch 函数创建了一个协程.并传入一个lambda表达式作为子线程中执行的代码块. 通过调用delay()函数模拟一个耗时操作,之后更新ui.
在协程中为什么可以直接更新ui?
在协程中更新ui的能力源于两个特征.
- 挂起函数
协程支持挂起函数执行耗时操作,而不会阻塞线程.当执行挂起函数时,协程会挂起当前的执行,将线程还给调度器,让其他任务继续执行. 一旦挂起函数执行完毕,协程会自动恢复执行.并且在合适的时机将结果返回主线程. 这样可以避免了阻塞主线程,使得ui可以保持响应性.
- 自动线程切换.
协程提供了自动的线程切换机制,可以方便在不同线程之间切换,通过合适的调度器配置,协程可以在需要的时候自动切换到主线程(比如使用Dispatchers.Main 调度器),以便在主线程更新ui. 这样耗时的操作可以在后台执行,并在完成之后自动切换到主线程进行ui更新,无需手动编写线程切换的逻辑.
lifecycleScope.launch() {
withContext(Dispatchers.IO){
Log.e("---","请求接口数据切换到IO线程---${Thread.currentThread().name}")
"serviceData.."
}.let {
// 切换主线程,更新ui.
withContext(Dispatchers.Main){
binding.tvText.setText(it);
}
}
}
上面例子通过lifecycleScope.launch()开启了一个协程,模拟了耗时操作,并更新ui的过程.在第一个withContext代码块中,将调度器制定为IO,此时lambda代码中所有的操作都是在io线程中执行的. 拿到服务器数据之后,再次通过withContext代码块将调度器置定位Main,此时lambda代码就是在主线程中.
在第一个例子中,没有手动的执行withContext()函数为什么可以实现线程切换自由?这是因为 delay(),withContext(), suspendCancellableCoroutine(),都是挂起函数被 suspend 关键字修饰.