协程和Android应用中的流程

When working on an Android app, we usually want to isolate ugly, Android specific stuff. Clean architecture helps us here. Application specific business rules are in the center of the project, not having anything to do with the used technology. Everything else is an implementation detail.

在Android应用程序上工作时,我们通常希望隔离难看的Android特定内容。 干净的建筑在这里为我们提供了帮助。 特定于应用程序的业务规则位于项目的中心,与所使用的技术无关。 其他所有内容都是实现细节。

Image for post

As this onion architecture has a lot of layers, it’s important to make it easy for the data to flow throughout the layers. For this, Android developers usually use Rx — a really powerful tool for reactive programming. Unfortunatelly, it’s often used only to switch something to the background thread. Operations that were previously started with Singles and Completables (one-time actions where Rx is used only for easy thread switching) can indeed be replaced with coroutines. When it comes to Rx Observables, or Flowables, (true reactive programming, where data is observed for some period of time), Jetbrains came with the alternative — Flow.

由于此洋葱体系结构具有许多层,因此使数据易于在整个层中流动很重要。 为此,Android开发人员通常使用Rx-一种非常强大的React式编程工具。 不幸的是,它通常仅用于将某些内容切换到后台线程。 确实可以用协程代替以前用Singles和Completables(一次性操作,其中Rx仅用于简单的线程切换)开始的操作。 当涉及到Rx Observables或Flowables(真正的React性编程,在一段时间内观察数据)时,Jetbrains附带了替代方案– Flow。

There were a lot of discussions among the Android developers about “coroutines vs Rx”, which is wrong — they are made for different usages. We distinguish between one time actions when we use coroutines and observing data when we use Flow.

Android开发人员中有很多关于“协程与Rx”的讨论,这是错误的-它们是为不同的用途而设计的。 当我们使用协程时,我们区分一次动作 ,而当使用Flow时,则观察数据

一次性动作 (One-time actions)

Kotlin provides coroutine support at language level. It’s a really easy way to multitask, suspend and resume tasks.

Kotlin在语言级别提供协程支持。 这是执行多任务,暂停和恢复任务的一种非常简单的方法。

Scopes make it easy to avoid shooting yourself in the foot. When starting a coroutine, start it in scope, and all the tasks will be canceled when canceling the scope. Android Architecture Components define ViewModelScope and LifecycleScope, but you can also make your own scope. ViewModelScope will be canceled when ViewModel is cleared, and LifecycleScope is tied to the lifecycle owner, like Fragment:

瞄准镜可以轻松避免自己被脚踩死。 启动协程时,请在范围内启动它,并且在取消范围时将取消所有任务。 Android体系结构组件定义了ViewModelScopeLifecycleScope ,但是您也可以创建自己的范围。 清除ViewModel并将LifecycleScope绑定到生命周期所有者(如Fragment)后,ViewModelScope将被取消。

viewModelScope.launch {
val user = getUserUseCase.execute(userId)
}

ViewModelScope.launch will be run on the main thread by default, so don’t forget to switch long running tasks to different dispatchers in repository or use case. These functions should be marked with a suspend keyword. Every suspend function can change the dispatcher with withContext(dispatcher):

默认情况下, ViewModelScope.launch将在主线程上运行,因此不要忘记将长期运行的任务切换到存储库或用例中的其他调度程序。 这些功能应使用suspend关键字标记。 每个暂停函数都可以使用withContext(dispatcher)更改调度程序

override suspend fun execute(userId: String): User? =
withContext(Dispatchers.Default) {
// calculation
}

Suspending functions are very powerful. Function execute will be run on Default dispatcher. Assigning the result to user value will be done after the calculation, but the main thread (on which getting the user is started) will not be blocked, but rather suspended. The main thread will be available for other stuff until the user is returned.

暂停功能非常强大。 函数execute将在默认调度程序上运行。 将结果分配给用户值将在计算之后完成,但是主线程(启动用户的线程)不会被阻塞,而是被挂起。 在返回用户之前,主线程将可用于其他内容。

Functionality withContext() can also be used just inside the viewModelScope.launch, but the view model shouldn’t know about the implementation of fetching the user. It’s better that suspend functions in domain and data layer decide how and on which dispatcher the value is fetched or calculated. This way every suspend function takes care about dispatcher for its own code.

withContext()的功能也可以仅在viewModelScope.launch内部使用,但是视图模型不应该知道获取用户的实现。 最好由域和数据层中的挂起函数决定如何以及在哪个调度程序上获取或计算值。 这样,每个暂停函数都会为自己的代码分配调度程序。

Dispatchers.IO is usually used for fetching the data from network, database, etc. — operations that are not CPU intensive. It is backed by a shared pool of 64 threads.

Dispatchers.IO通常用于从网络,数据库等中获取数据-这些操作不会占用大量CPU。 它由64个线程的共享池支持。

Dispatchers.Default should be used for calculations, or CPU intensive tasks. It’s limited by the shared pool of threads the size of the CPU cores count.

Dispatchers.Default应该用于计算或CPU密集型任务。 它受CPU核心数量的共享线程池的限制。

You can also return to Dispatchers.Main (Android main thread), or use Dispatchers.Unconfined which doesn’t change the thread. It starts on the current thread, but if this function starts another suspend function which changes the Dispatcher, it will just resume on that Dispatcher.

您还可以返回Dispatchers.Main (Android主线程),或使用Dispatchers.Unconfined (不会更改线程)。 它在当前线程上启动,但是如果此函数启动另一个更改Dispatcher的暂停函数,则它将仅在该Dispatcher上恢复。

If you need multiple results from different sources, you can start multiple coroutines with async:

如果您需要来自不同来源的多个结果,则可以使用异步启动多个协程:

value1Deferred = viewModelScope.async {
// …
}
value2Deferred = viewModelScope.async {
// …
}val user = User(value1Deferred.await(), value2Deferred.await())

This will start 2 coroutines, one for each value fetch. If coroutines are switched each to its thread (and not left on Dispatchers.Main), the creation of the user object will take as much time as it’s needed for the longest value fetch operation. E.g., if value1 fetch takes 3 seconds, and value2 fetch takes 5 seconds, User will be created after about 5 seconds. If both operations would have been started in one coroutine, it would take 8 seconds!

这将启动2个协程,每个值获取一个。 如果将协程分别切换到其线程(而不是留在Dispatchers.Main上 ),则创建用户对象所花费的时间将与最长值获取操作所需的时间相同。 例如,如果取值1需要3秒钟,取值2需要5秒钟,则将在大约5秒钟后创建用户 。 如果两个操作都将在一个协同程序中启动,则将需要8秒钟!

Every coroutine can “split” itself this way, without the coroutine starter’s knowledge.

在不需要协程初学者的情况下,每个协程都可以以这种方式“分裂”自己。

Both launch and async return Job with which you can check if it’s active, completed or cancelled. You can use this if you don’t want to start some coroutine with some other coroutine active.

启动异步都返回Job ,您可以使用该Job检查它是处于活动状态,已完成还是已取消。 如果您不希望在其他协程活跃的情况下启动协程,则可以使用此功能。

观察 (Observing)

Coroutines are nice for one-time actions, when you need to fetch or calculate something only once, and that’s it! What about observing?

当您只需要获取或计算一次内容时,协程对于一次性操作非常有用,仅此而已! 观察呢?

Although Kotlin Flow lacks some of the powerful set of Rx operators, it’s harder to leave something unmanaged. Let’s take a look at the example:

尽管Kotlin Flow缺少一些功能强大的Rx运算符,但是很难将某些内容置于不受管理的状态。 让我们看一个例子:

getUserUseCase.execute()
.debounce(DEBOUNCE_TIME)
.collectLatest { user->
doSomethingWithUser(user)
}

Function execute() will return Flow<User>. We can apply multiple operators on top of that, like debounce, which will filter out values that are followed by newer values in said time. Most of the lacking operators can easily be written as Flow extension functions and can be found as open-source functions on github or blogs.

函数execute()将返回Flow <User> 。 我们可以在此之上应用多个运算符,例如debounce ,它将过滤掉在所述时间内后面跟着新值的值。 大多数缺少的运算符可以轻松地编写为Flow扩展功能,也可以在github或博客上找到为开源功能。

After putting all the operators we want, we can subscribe to events with collect() or collectLatest(). The latter will cancel the previous value block when the new value comes. Now, if you put this piece of code just in view model or presenter method, it will not compile. The reason is that collect() is a suspend function and it will force you to start the Flow tied to the CoroutineScope. This way, you will stay subscribed just as long as the scope is alive — e.g., just as long as view model is alive. No babysitting:

在放置了我们想要的所有运算符之后,我们可以使用collect()collectLatest()订阅事件。 当新值出现时,后者将取消前一个值块。 现在,如果将这段代码仅放在视图模型或presenter方法中,则将无法编译。 原因是collect()是一个暂停函数,它将迫使您启动绑定到CoroutineScope的Flow。 这样,只要范围处于活动状态(例如,只要视图模型处于活动状态),您就将保持订阅状态。 没有保姆:

viewModelScope.launch {
getUserUseCase.execute()
.debounce(DEBOUNCE_TIME)
.collectLatest { user ->
doSomethingWithUser(user)
}
}

Just like with coroutines, viewModelScope.launch() will launch Flow on main dispatcher, and just like with coroutines, let’s switch it to wanted Dispatcher in domain or data layer:

就像协程一样, viewModelScope.launch()将在主调度程序上启动Flow,就像协程一样,让我们​​在域或数据层将其切换为所需的Dispatcher:

buildFlow()
.map { mapToSomething(it) }
.flowOn(Dispatchers.Default)

FlowOn will affect all the preceding operators until another .flowOn, so this function can again define the dispatcher for its operators, and buildFlow() can have it’s own .flowOn for it’s set of operators. If we put it in one method, it would work like this:

FlowOn会影响所有前面的运算符,直到另一个.flowOn为止 ,因此此函数可以再次为其运算符定义调度程序,并且buildFlow()对其运算符可以拥有自己的.flowOn 。 如果我们将其放在一种方法中,它将像这样工作:

.filter { … }               // Will be executed in IO
.flowOn(Dispatchers.IO)
.map { mapToSomething(it) } // Will be executed in Default
.flowOn(Dispatchers.Default)

So where do Flows originate from? How to “make” them?

那么流从何而来呢? 如何“制作”它们?

Well, there are few ways.

好吧,有几种方法。

You can use flow builders:

您可以使用流构建器:

flowOf(1, 2, 3)

You can also use channels, which are similar to Subjects, or Processors, in RxJava:

您还可以使用渠道 ,这是类似主题,或处理器,在RxJava:

private val usernameChannel = ConflatedBroadcastChannel<String>()usernameChannel
.asFlow()
.collect{}

Some libraries also allow you to observe data via Flow — like Room, where you can write something like this in Dao interface:

一些库还允许您通过Flow观察数据-例如Room,您可以在Dao界面中编写如下内容:

@Query("SELECT * FROM user")
fun getUsers(): Flow<List<User>>

结论 (Conclusion)

In conclusion, coroutines and Flow seem like very powerful, yet easy to use tools. If you are coming from Rx world, you’ll need some time to adapt to a little bit different way of thinking. If you are unfamiliar with both Rx and coroutines/Flow, the latter seems a lot easier to grasp.

总之,协程和Flow看起来非常强大,但易于使用。 如果您来自Rx世界,则需要一些时间来适应一些不同的思维方式。 如果您不熟悉Rx和协程/流程,后者似乎很容易掌握。

If you already have Rx in your project, you can start working with coroutines and Flow on the new screens. You can also transform existing Rx use cases to Flow use cases with Publisher<T>.asFlow() extension function available in kotlinx.coroutines.reactive. Use cases can now return Flow without touching the bottom Rx layers.

如果您的项目中已经有Rx,则可以在新屏幕上开始使用协程和Flow。 您也可以将现有的Rx用例流程的用例与出版商kotlinx.coroutines.reactive可用<T> .asFlow()扩展功能。 用例现在可以返回Flow而不接触底部的Rx层。

翻译自: https://blog.trikoder.net/coroutines-and-flow-in-android-apps-deedd59a5f40

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值