序文
この記事では、Flowの基本的な使い方を中心に、
Flowを使ってネットワークデータをリクエストする方法について紹介します
。
フローとは
フローとは「流れ」を意味します。
たとえば、自然界では、一般的な水は
高いところから低いところへ流れます。
コンピュータの世界では、いわゆる「フロー」は
実際にはデータの流れ、つまり
高いところから低いところへのデータの流れを指します。生データ、処理、および最終使用のプロセス。
たとえば、json を取得し、それを Bean に変換し
、スクリーニングとフィルタリングを行って
最終的に使用するデータを取得します。
このプロセスはデータ フローと呼ばれます
以下に示すように、
このプロセスを処理するには、
フロー ツールを使用できます。
Flowフローの使用
使い方が簡単
フローを使用するには、まずコルーチン関連のツール クラスをインポートする必要があります。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7-mpp-dev-11'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-dev-11'
最も単純なフローの使用法:
suspend fun flow1() {
flow<Int> {
(0..4).forEach {
emit(it)//生产者发送数据
}
}.collect {
Log.d("flow1", "it:$it")//消费者处理数据
}
}
このコードを分析してみましょう:
1: (0...4) は 0-4 のリスト、これが元のデータです
2: Emit は実際に元のデータを送信します
3: Collect は送信された元のデータを収集するために使用され、内部的には受信したデータを出力します
4: フロー { } 関数でラップされたコード ブロックはデータの送信を担当します。この関数はフロー オブジェクトを返します。
注: フロー フローは「コールド フロー」です。
つまり、フロー内のメソッド本体は呼び出されたときに、collect
メソッドが存在しない場合は、どのように出力してもデータは送信されません。
ストリーム演算子
フローは、データ操作用の一連の API を提供します。
これを「オペレーター」と呼びます
。これらは通常、「フロー ビルダー」、「中間オペレーター」、および「ターミナル オペレーター」に分類されます
。フロー ビルダーは通常、フローを構築するために使用されます。ストリーム オブジェクトの演算子は、フィルタリング、変換など、
ストリーム上の一部の操作を事前定義するだけであり、アクションの実行を積極的にトリガーしません。ターミナル オペレータは、ストリームの最終処理です。たとえば、collect はターミナル オペレータです。 .ここではいくつかの演算子を示します。
ストリームビルダー
流れ
flowOf 内の可変長パラメータを 1 つずつ出力できます
flowOf(1, 2, 5, 4).collect {
println(it)
}
asFlow
flowOf はコレクションをフロー放出に変換できます
suspend fun asFlowM(){
listOf(1,2,9,0,8).asFlow().collect{
println(it)
}
}
中間演算子
地図
マップ内でいくつかの遷移操作を実行できます。
たとえば、この例では、プロデューサーによって送信されたデータは *9 であり、その後、コンシューマーに送信されます
。マップ内で非同期操作を実行できることに注目してください
。この地図と コレクションでも構わない 惑わされないで
suspend fun mapM(){
(1..9).asFlow().map {
it*9
}.collect{
println(it)
}
}
変身
変換は主に型変換を重視します
(1..3).asFlow() // 一个请求流
//transform中的泛型<Int,String> 表示将Int类型转换为String后,继续发射
.transform<Int, String> {
request ->
emit("transform Int to String $request")
}
.collect {
response -> println(response) }
取る
長さ制限演算子は、消費するデータの量を制限できます。コードを参照してください。
(1..9).asFlow().take(3).collect {
println(it)
}
混同する
プロデューサがコンシューマよりも速くデータを送信する場合、コンシューマはプロデューサによって送信された最新のデータしか取得できません。
suspend fun conflate(){
flow<Int> {
(1..9).forEach {
delay(100)
emit(it)
}
}.conflate().collect {
delay(300)
println(it)
}
}
たとえば、上記のコードでは、conflate が存在するため、出力は次のようになります。
1
3
6
9
conflate が存在しない場合、出力は次のようになります。
1
2
3
4
5
6
7
8
9
2 つを比較すると、conflate を使用した例では、すぐに処理できない多くのデータが無視されていることが明らかです。
収集最後
この演算子の意味: プロデューサ データが送信され、コンシューマが以前のデータの処理を完了していない場合、以前のデータの処理を直接停止し、最新のデータを直接処理します。
suspend fun collectLastM(){
flow<Int> {
(1..9).forEach {
delay(100)
emit(it)
}
}.collectLatest {
delay(800)
println(it)
}
}
たとえば、この例の出力は 9 です。
ジップ
zip オペレーターは、2 つのストリームを 1 つのストリームにマージし、zip メソッドで 2 つのストリームによって出力されたデータを処理して結合してから、コンシューマーに送信できます。2 つのストリームの長
さが一致しない場合は、短いストリームが処理されます。 :
1. 2 つのストリームの長さは同じで、どちらも 3 です。
suspend fun zipM(){
val flow1 = (1..3).asFlow()
val flow2 = flowOf("李白","杜甫","安安安安卓")
flow1.zip(flow2){
a,b->
"$a : $b"
}.collect {
println(it)
}
}
出力:
1 : 李白
2 : 杜甫
3 : 安安安安卓
上記のコードを変更して、flow1 の長さを 5 に変更してみましょう
val flow1 = (1..5).asFlow()
出力を表示します。
1 : 李白
2 : 杜甫
3 : 安安安安卓
したがって、最初の結論を検証するために、長さの異なる 2 つのストリームが zip マージされ、コンシューマーによって出力されるデータ長は短い方のストリームの長さになります。
組み合わせる
前のセクションで zip の欠点、つまり 2 つのストリームの長さが等しくない場合、長いストリームの後半部分を出力できないことは明らかでした。
次に、zip の欠点を解決するために結合が使用されます (欠点と言うのは難しいですが、アプリケーションのシナリオが異なるだけです。欠点として考えることができます)。
suspend fun combineM(){
val flowA = (1..5).asFlow()
val flowB = flowOf("李白","杜甫","安安安安卓")
flowA.combine(flowB){
a,b->
"$a : $b"
}.collect {
println(it)
}
}
出力ログ:
1 : 李白
2 : 李白
2 : 杜甫
3 : 杜甫
3 : 安安安安卓
4 : 安安安安卓
5 : 安安安安卓
2 つのストリーム、数値ストリームの長さは 5、文字列ストリームの長さは 3 です。
達成された効果の単純な論理分析:
flow发射1,flow2发射 ”李白“ ,打印:1 : 李白
flow发射2,flow2未发射数据 ,打印:2 : 李白
flow未发射,flow2发射 ”杜甫“ ,2 : 杜甫
flow发射3,flow2未发射 ,打印:3 : 杜甫
flow未发射,flow2发射 ”安安安安卓“ ,打印:3 : 安安安安卓
flow发射4,flow2发射完成 ,打印:4 : 安安安安卓
flow发射5,flow2发射完成 ,打印:5 : 安安安安卓
完了時に
onCompletion を使用して、ストリームが完了したときに値を送信します。
flowOf(1, 23, 5, 3, 4).onCompletion {
println("流操作完成")
emit(12344)//这里不返回值也没关系
}.collect {
println(it)
}
出力:
1
23
5
3
4
流操作完成
12344
端末オペレーター
リストへ
データはリストリストに消費されます
suspend fun toList():List<Int> {
return (1..9).asFlow().filter {
it % 2 == 0 }.toList()
}
設定する
とリスト
ファースト
最初の要素を取得する
suspend fun firstM(): Int {
return (2..9).asFlow().filter {
it % 2 == 1 }.first()
}
減らす
reduce のラムダ式は計算のための演算式を提供します。
Reduceのラムダ式では、現在消費する値と前回計算した値を計算して新しい値を取得して返すことができます。すべての値が消費された後に、最終的な値が返されます。
suspend fun reduceM():Int {
return (1..9).asFlow().reduce {
accumulator, value ->
println("$accumulator : $value")
accumulator + value
}
}
バッファ
バッファーはプロデューサー データをキャッシュでき、コンシューマーによってブロックされません。
suspend fun bufferM() {
val startMillis = System.currentTimeMillis()
flow<Int> {
(1..3).forEach {
delay(300)
emit(it)
}
}.buffer(4)
.collect {
delay(400)
println(it)
println("时间已经过了${
System.currentTimeMillis() - startMillis}")
}
}
コード実行の印刷ログ:
1
时间已经过了745
2
时间已经过了1148
3
时间已经过了1552
バッファを使用しない場合、合計期間は 2100 ミリ秒である必要があります。
バッファを使用する場合、合計期間は: 1552=300+400*3 です
。したがって、バッファを使用する場合、プロデューサーは、消費者。
フロー例外
try/catch を使用してストリームをラップします。try
/catch を使用してストリーム例外を収集できますが、この方法はお勧めできません。
ストリームを処理するには、flow の catch 演算子を使用します。flow
の catch 演算子を使用して、より洗練された方法を実行します。ただし、catch にはプロデューサー例外のみをキャッチ
でき、コンシューマ例外はキャッチできないという欠点もあります。
suspend fun trycatch() {
flow<Int> {
(1..3).forEach {
if (it == 2) {
//故意抛出一个异常
throw NullPointerException("强行空指针,嘿嘿嘿嘿")
}
emit(it)
}
}.catch {
e->
e.printStackTrace()
emit(-1)//异常的情况下发射一个-1
}.collect{
println(it)
}
}
コンシューマ例外を処理する方法:
コンシューマで例外をスローして、それがキャッチできるかどうかを確認してください。
flow<Int> {
for (i in 1..3) {
emit(i)
}
}.catch {
emit(-1)
}.collect {
if(it==2){
//在消费者中抛出数据
throw IllegalArgumentException("数据不合法")
}
println(it)
}
出力:
1
Exception in thread "main" java.lang.IllegalArgumentException: 数据不合法
at HahaKt$consumerCatch$$inlined$collect$1.emit(Collect.kt:138)
例外をキャッチするには onEach に例外コードを入れます
suspend fun consumerCatch() {
flow<Int> {
for (i in 1..3) {
emit(i)
}
}.onEach {
if (it == 2) {
//与上面的不同,在消费之前先用onEach处理一下
throw IllegalArgumentException("数据不合法")
}
}.catch {
emit(-1)
}.collect {
println(it)
}
}
出力:
1
-1
フローはネットワーク データを要求します
上記は、Flow がデータを処理する方法を説明しています。
プロジェクトで Retrofit とともに使用できます
。たとえば、最初に戻り値の型が Flow データ フローであるリクエストを定義します。
@POST(ServerName.BS_API + "test/url")
fun getMessage(): Flow<List<Bean>>
viewModelScope と viewmodel ライフサイクル バインディングを使用し
、返されたデータをフィルターして、最終的に必要なデータを取得します。
viewModelScope.launch {
repository.getMessage()
?.filter {
it.isNotEmpty()
}
?.collect {
//得到数据
}
}
このように、フローを使用して、返されたデータに対してさまざまな複雑な処理を実行できます。
もちろん、これらはまだ基本的な使用法ですが、実際のビジネスの状況に応じて、さらに多くのカプセル化を行うことができます。
関連情報
Kotlin Flowの詳しい説明 Kotlin
Flow、どこに流すの?
公式フロー アドレスでは、
Kotlin Flow を使用してデータ フロー「パイプライン」を構築します。