Kotlin Coroutines在Android中的实践

Coroutines在Android中的实践

前面两篇文章讲了协程的基础知识和协程的通信.
见:

Kotlin Coroutines不复杂, 我来帮你理一理

Kotlin协程通信机制: Channel
举的例子可能离实际的应用代码比较遥远.

这篇我们就从Android应用的角度, 看看实践中都有哪些地方可以用到协程.

Coroutines的用途

Coroutines在Android中可以帮我们做什么:

取代callbacks, 简化代码, 改善可读性.

保证Main safety.

结构化管理和取消任务, 避免泄漏.

这有一个例子:

suspend fun fetchDocs() { // Dispatchers.Main val result = get("developer.android.com") // Dispatchers.Main show(result) // Dispatchers.Main } suspend fun get(url: String) = // Dispatchers.Main withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block) /* perform network IO here */ // Dispatchers.IO (main-safety block) } // Dispatchers.Main }

这里get是一个suspend方法, 只能在另一个suspend方法或者在一个协程中调用.

get方法在主线程被调用, 它在开始请求之前suspend了协程, 当请求返回, 这个方法会resume协程, 回到主线程. 网络请求不会block主线程.

main-safety是如何保证的呢?

dispatcher决定了协程在什么线程上执行. 每个协程都有dispatcher. 协程suspend自己, dispatcher负责resume它们.

Dispatchers.Main: 主线程: UI交互, 更新LiveData, 调用suspend方法等.

Dispatchers.IO: IO操作, 数据库操作, 读写文件, 网路请求.

Dispatchers.Default: 主线程之外的计算任务(CPU-intensive work), 排序, 解析JSON等.

一个好的实践是使用withContext()来确保每个方法都是main-safe的, 调用者可以在主线程随意调用, 不用关心里面的代码到底是哪个线程的.

管理协程

之前讲Scope和Structured Concurrency的时候提过, scope最典型的应用就是按照对象的生命周期, 自动管理其中的协程, 及时取消, 避免泄漏和冗余操作.

在协程之中再启动新的协程, 父子协程是共享scope的, 也即scope会track其中所有的协程.

协程被取消会抛出CancellationException.

coroutineScope和supervisorScope可以用来在suspend方法中启动协程. Structured concurrency保证: 当一个suspend函数返回时, 它的所有工作都执行完毕.

它们两者的区别是: 当子协程发生错误的时候, coroutineScope会取消scope中的所有的子协程, 而supervisorScope不会取消没有发生错误的其他子协程.

Activity/Fragment & Coroutines

在Android中, 可以把一个屏幕(Activity/Fragment)和一个CoroutineScope关联, 这样在Activity或Fragment生命周期结束的时候, 可以取消这个scope下的所有协程, 好避免协程泄漏.

利用CoroutineScope来做这件事有两种方法: 创建一个CoroutineScope对象和activity的生命周期绑定, 或者让activity实现CoroutineScope接口.

方法1: 持有scope引用:

class Activity { private val mainScope = MainScope() fun destroy() { mainScope.cancel() } }

方法2: 实现接口:

class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) { fun destroy() { cancel() // Extension on CoroutineScope } }

默认线程可以根据实际的需要指定.
Fragment的实现类似, 这里不再举例.

ViewModel & Coroutines

Google目前推广的MVVM模式, 由ViewModel来处理逻辑, 在ViewModel中使用协程, 同样也是利用scope来做管理.

ViewModel在屏幕旋转的时候并不会重建, 所以不用担心协程在这个过程中被取消和重新开始.

方法1: 自己创建scope private val viewModelJob = Job() private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

默认是在UI线程.
CoroutineScope的参数是CoroutineContext, 是一个配置属性的集合. 这里指定了dispatcher和job.

在ViewModel被销毁的时候:

override fun onCleared() { super.onCleared() viewModelJob.cancel() }

这里viewModelJob是uiScope的job, 取消了viewModelJob, 所有这个scope下的协程都会被取消.

一般CoroutineScope创建的时候会有一个默认的job, 可以这样取消:

uiScope.coroutineContext.cancel() 方法2: 利用viewModelScope

如果我们用上面的方法, 我们需要给每个ViewModel都这样写. 为了避免这些boilerplate code, 我们可以用viewModelScope.

注: 要使用viewModelScope需要添加相应的KTX依赖.

For ViewModelScope, use androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01 or higher.

viewModelScope绑定的是Dispatchers.Main, 会自动在ViewModel clear的时候自动取消.

用的时候直接用就可以了:

class MainViewModel : ViewModel() { // Make a network request without blocking the UI thread private fun makeNetworkRequest() { // launch a coroutine in viewModelScope viewModelScope.launch(Dispatchers.IO) { // slowFetch() } } // No need to override onCleared() }

所有的setting up和clearing工作都是库完成的.

LifecycleScope & Coroutines

每一个Lifecycle对象都有一个LifecycleScope.

同样也需要添加依赖:

For LifecycleScope, use androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01 or higher.

要访问CoroutineScope可以用lifecycle.coroutineScope或者lifecycleOwner.lifecycleScope属性.

比如:

activity.lifecycleScope.launch {} fragment.lifecycleScope.launch {} fragment.viewLifecycleOwner.launch {}

lifecycleScope可以启动协程, 当Lifecycle结束的时候, 任何这个scope中启动的协程都会被取消.

这比较适合于处理一些带delay的UI操作, 比如需要用handler.postDelayed的更新UI的操作, 有多个操作的时候嵌套难看, 还容易有泄漏问题.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwwfzg.html