协程解决了什么?
协程Coroutines作为Kotlin的一大特性,被Android官方所强推,主要用来简化Android中异步代码的编写,将开发者从异步代码的泥潭中解救出来。在Android开发中,协程Coroutines所解决的两个最大的问题是:
- 耗时任务:阻塞主线程
- 主线程安全:任何suspend方法都可以在主线程中被调用
我们来详细看看这两大问题,协程Coroutines都是如何去解决的。
耗时任务
从网络接口传递数据或是从数据库读写数据抑或是从文件中读写数据,都是我们日常工作中最常见的耗时任务。
在Android中,每个App都有一个main thread负责UI处理和用户交互。如果有太多的任务执行在main thread,App的运行速度就会变慢甚至卡顿,严重影响用户体验。因此,任何耗时任务都必须执行在其它线程,不能阻塞主线程。
在以往的处理方式中,我们通常使用回调的方式获取在其它线程中运行的耗时任务的执行结果,我们写出的代码可能像这样:
class ViewModel: ViewModel() {
fun fetchDocs() {
get("www.google.com") { result ->
show(result)
}
}
}
这样的处理方式,在面对简单的逻辑情况下还是游刃有余的。但是,一旦出现复杂的逻辑,例如多个请求联合处理时,多层回调往往令人头晕目眩,逻辑处理也变得极度不清晰,代码可读性很差。
使用协程Coroutines处理耗时任务
如果使用Coroutines来解决上面的例子,我们的代码又会变得如何呢?
// Main
suspend fun fetchDocs() {
// Main
val result = get("www.google.com")
// Main
show(result)
}
suspend fun get(url: String) = withContext(Dispachers.IO) {
...
}
这里,我们看到这段代码,可能会有下面几个疑惑。主线程被阻塞了吗?从get
中返回result是如何等待网络请求和阻塞的?
在协程代码中,通过添加了下面两个新操作来实现了上述代码的功能。
- suspend —— 字面意思是挂起,也就是暂停当前协程代码的执行,开始执行挂机函数
- resume —— 字面意思是恢复,也就是恢复到挂起的协程暂停的那个位置
注意:
你只能在suspend修饰的方法中调用suspend方法,或者使用
launch
关键字来启动一个新的协程来调用suspend方法。
在上面的例子中,get
会suspend(挂起)协程然后执行网络请求(当协程挂起的时候,主线程可继续执行其它任务而不会被阻塞),当网络请求完成后,我们不再需要耗时方法,而是resume回到协程的下一步代码执行show(result)
。
也就是说,我们可以非常直接的写出一段看起来是同步的代码,但其实把其中需要做异步处理的耗时任务放入suspend修饰的方法中,而不会阻塞主线程的运行。这样我们的代码会变得非常的干净易读,这里我举一个实际的数据获取案例可以体会一下:
class OrderViewModel @Inject constructor(private val orderRepository: OrderRepository): ViewModel() {
val data: MutableLiveData<OrderEntity>() = MutableLiveData<OrderEntity>()
fun loadData() {
launch {
val result = orderRepository.getOrderList();
data.value = result.data
}
}
}
class OrderRepository @Inject constructor(private val service: ApiService) {
// Get OrderList from net source
suspend fun getOrderList(): OrderEntity = service.getOrderList()
}
class OrderFragment : BaseFragment() {
...
override fun observeData() {
viewModel.data.observe(this, Observer {
// handle data
})
}
...
}
主线程安全
在上面的学习中,我们看到了使用suspend可以挂起协程来执行耗时任务;但是,我们是如何告诉Kotlin这段代码到底是运行在哪个线程的呢?因此我们就用到了Dispathcers
。
Dispatcher.Main | Dispatchers.IO | Dispatchers.Default |
---|---|---|
Android主线程,用于UI交互和处理轻量级任务 | 用于操作磁盘/网络 IO任务 | 针对CPU操作密集的任务 |
- Calling suspend functions<br />- Call UI functions<br />- Updating LiveData | - Database<br />- Reading/Writing files<br />- Networking* | - Sorting a list<br />- Parsing JSON<br />- DiffUtils |
总结
其实,我们可以通俗的将协程理解成一个线程框架,而不是将它看成一种语言特性。
协程就是为了解决复杂线程问题的一种简化方案,在协程的支持下,我们可以选择性的放弃RxJava这些学习曲线陡峭的异步框架,用一种更加简单优雅的方式,使我们的Android代码变得更加的优秀。