(Original) Verwendung des Flow-Datenflusses

Vorwort

In diesem Artikel werden hauptsächlich einige grundlegende Verwendungsmethoden von Flow vorgestellt.
Außerdem wird erläutert, wie Flow zum Anfordern von Netzwerkdaten verwendet wird.
Fangen wir an!

Was ist Flow?

Übersetzt bedeutet „Fluss“ „Fluss“.
In der Natur fließt beispielsweise gewöhnliches Wasser
von hoch nach niedrig.
In der Computerwelt bezieht sich der sogenannte „Fluss“
eigentlich auf den Datenfluss, also den Datenfluss
von hoch nach oben zu niedrig. Der Prozess der Rohdaten, der Verarbeitung und der endgültigen Verwendung.
Beispielsweise das Abrufen eines JSON, das Konvertieren in eine Bean
und das anschließende Screening und Filtern,
um die endgültigen zu verwendenden Daten zu erhalten .
Dieser Prozess wird als Datenfluss bezeichnet
, wie unten gezeigt:
Fügen Sie hier eine Bildbeschreibung ein
Um diesen Prozess zu verarbeiten,
können wir das Tool Flow verwenden

Verwendung von Flow Flow

Einfach zu benutzen

Um Flow verwenden zu können, müssen Sie zunächst Coroutine-bezogene Werkzeugklassen importieren:

    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'

Die einfachste Flow-Nutzung:

suspend fun flow1() {
    
    
    flow<Int> {
    
    
        (0..4).forEach {
    
    
            emit(it)//生产者发送数据
        }
    }.collect {
    
    
        Log.d("flow1", "it:$it")//消费者处理数据
    }
}

Lassen Sie uns diesen Code analysieren:
1: (0...4) ist eine Liste von 0-4, dies sind die Originaldaten
2: Emit sendet tatsächlich die Originaldaten aus
3: Collect wird zum Sammeln der gesendeten Originaldaten und intern verwendet Gedruckte empfangene Daten
4: Der von der Flow-Funktion {} umschlossene Codeblock ist für das Senden von Daten verantwortlich. Diese Funktion gibt ein Flow-Objekt zurück.
Hinweis: Der Flow-Flow ist ein „kalter Flow“,
was bedeutet, dass der Methodenkörper im Flow dies tut Wird erst generiert, wenn Collect aufgerufen wird.
Wenn beim Aufruf keine Collect-Methode vorhanden ist, werden die Daten unabhängig von der Ausgabemethode nicht gesendet.

Stream-Betreiber

Flow stellt uns eine Reihe von APIs für Datenoperationen zur Verfügung,
die wir „Operatoren“ nennen
. Sie sind im Allgemeinen in „Flow Builder“, „Zwischenoperatoren“ und „Terminaloperatoren“ unterteilt.
Flow Builder werden im Allgemeinen zum Erstellen von Flow verwendet. Das Zwischenprodukt Operatoren von Stream-Objekten
definieren nur einige Vorgänge im Stream vor,
z. B. Filterung, Konvertierung usw.
, und lösen die Aktionsausführung nicht aktiv aus.
Der Terminaloperator ist die Endverarbeitung des Streams
. Sammeln ist beispielsweise der Terminaloperator .
Hier sind einige Operatoren.

Stream-Builder

Fluss von

Sie können die Parameter variabler Länge einzeln in flowOf ausgeben

flowOf(1, 2, 5, 4).collect {
    
    
        println(it)
}

asFlow

flowOf kann eine Sammlung in eine Flussemission umwandeln

suspend fun asFlowM(){
    
    
    listOf(1,2,9,0,8).asFlow().collect{
    
    
        println(it)
    }
}

Zwischenbetreiber

Karte

Wir können einige Übergangsoperationen in der Karte ausführen.
In diesem Beispiel sind die vom Produzenten gesendeten Daten beispielsweise *9 und werden dann an den Verbraucher übertragen
. Es ist erwähnenswert, dass wir asynchrone Operationen in der Karte ausführen können
. Beachten Sie das Diese Karte und es spielt keine Rolle, ob es sich um eine Sammlung handelt, lassen Sie sich nicht irreführen

suspend fun mapM(){
    
    
    (1..9).asFlow().map {
    
    
        it*9
    }.collect{
    
    
        println(it)
    }
}

verwandeln

Bei der Transformation liegt der Schwerpunkt hauptsächlich auf der Typkonvertierung

(1..3).asFlow() // 一个请求流
        //transform中的泛型<Int,String> 表示将Int类型转换为String后,继续发射
        .transform<Int, String> {
    
     request ->
            emit("transform Int to String $request")
        }
        .collect {
    
     response -> println(response) }

nehmen

Der Längenbegrenzungsoperator take kann die Datenmenge begrenzen, die wir verbrauchen möchten, siehe Code

(1..9).asFlow().take(3).collect {
    
    
        println(it)
}

verschmelzen

Wenn der Produzent Daten schneller überträgt als der Konsument, kann der Konsument nur die neuesten vom Produzenten übermittelten Daten erhalten.

suspend fun conflate(){
    
    
    flow<Int> {
    
    
        (1..9).forEach {
    
    
            delay(100)
            emit(it)
        }
    }.conflate().collect {
    
    
        delay(300)
        println(it)
    }
}

Der obige Code sieht beispielsweise aufgrund der Existenz von conflate wie folgt aus:

1
3
6
9

Wenn keine Zusammenführung vorhanden ist, sieht die Ausgabe wie folgt aus:

1
2
3
4
5
6
7
8
9

Beim Vergleich der beiden ist es offensichtlich, dass im Beispiel der Verwendung von Conflate viele Daten ignoriert wurden, die nicht sofort verarbeitet werden können.

CollectLast

Die Bedeutung dieses Operators: Wenn die Herstellerdaten übertragen wurden und der Verbraucher die Verarbeitung der vorherigen Daten noch nicht abgeschlossen hat, stoppt er direkt die Verarbeitung der vorherigen Daten und verarbeitet direkt die neuesten Daten.


suspend fun collectLastM(){
    
    
    flow<Int> {
    
    
        (1..9).forEach {
    
    
            delay(100)
            emit(it)
        }
    }.collectLatest {
    
    
        delay(800)
        println(it)
    }
}

Die Ausgabe dieses Beispiels ist beispielsweise 9

Reißverschluss

Der ZIP-Operator kann zwei Streams zu einem Stream zusammenführen und dann die von den beiden Streams in der ZIP-Methode ausgegebenen Daten verarbeiten und kombinieren, bevor er sie an den Verbraucher sendet.
Wenn die Längen der beiden Streams inkonsistent sind, wird der kürzere Stream verarbeitet :
1. Die Länge der beiden Streams ist gleich, beide sind 3

suspend fun zipM(){
    
    
    val flow1 = (1..3).asFlow()
    val flow2 = flowOf("李白","杜甫","安安安安卓")
    flow1.zip(flow2){
    
    a,b->
        "$a : $b"
    }.collect {
    
    
        println(it)
    }
}

Ausgabe:

1 : 李白
2 : 杜甫
3 : 安安安安卓

Ändern wir den obigen Code und ändern wir die Länge von flow1 auf 5

val flow1 = (1..5).asFlow()

Sehen Sie sich die Ausgabe an:

1 : 李白
2 : 杜甫
3 : 安安安安卓

Um unsere anfängliche Schlussfolgerung zu überprüfen, werden zwei Streams mit unterschiedlichen Längen per Zip zusammengeführt, und die vom Verbraucher ausgegebene Datenlänge ist die Länge des kürzeren Streams.

kombinieren

Wir haben uns im vorherigen Abschnitt über die Mängel von zip im Klaren, das heißt, wenn die Längen der beiden Streams nicht gleich sind, kann der spätere Teil des längeren Streams nicht ausgegeben werden.

Dann wird Combine verwendet, um die Mängel von Zip zu beheben (es ist schwer zu sagen, dass es sich um einen Mangel handelt, nur die Anwendungsszenarien sind unterschiedlich, man kann es sich als Mangel vorstellen).


suspend fun combineM(){
    
    
    val flowA = (1..5).asFlow()
    val flowB = flowOf("李白","杜甫","安安安安卓")
    flowA.combine(flowB){
    
    a,b->
        "$a : $b"
    }.collect {
    
    
        println(it)
    }
}

Ausgabeprotokoll:

1 : 李白
2 : 李白
2 : 杜甫
3 : 杜甫
3 : 安安安安卓
4 : 安安安安卓
5 : 安安安安卓

Bei unseren beiden Streams beträgt die Länge des numerischen Streams 5 und die Länge des String-Streams 3.

Einfache logische Analyse der erzielten Wirkung:

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 : 安安安安卓

bei Fertigstellung

Verwenden Sie onCompletion, um einen Wert zu senden, wenn der Stream abgeschlossen ist.

 flowOf(1, 23, 5, 3, 4).onCompletion {
    
    
        println("流操作完成")
        emit(12344)//这里不返回值也没关系
    }.collect {
    
    
        println(it)
    }

Ausgabe:

1
23
5
3
4
流操作完成
12344

Terminalbetreiber

auflisten

Die Daten werden in einer Listenliste verarbeitet

suspend fun toList():List<Int> {
    
    
   return (1..9).asFlow().filter {
    
     it % 2 == 0 }.toList()
}

toSet

同toList

zuerst

Holen Sie sich das erste Element

suspend fun firstM(): Int {
    
    
    return (2..9).asFlow().filter {
    
     it % 2 == 1 }.first()
}

reduzieren

Der Lambda-Ausdruck von Reduce liefert die Operationsformel für die Berechnung.

Im Lambda-Ausdruck von Reduce können der aktuell zu verbrauchende Wert und der zuvor berechnete Wert berechnet werden, um einen neuen Wert zu erhalten und ihn zurückzugeben. Der endgültige Wert wird zurückgegeben, nachdem alle Werte verbraucht wurden.

suspend fun reduceM():Int {
    
    
    return (1..9).asFlow().reduce {
    
     accumulator, value ->
        println("$accumulator : $value")
        accumulator + value
    }
}

Puffer

Der Puffer kann Produzentendaten zwischenspeichern und wird nicht von Verbrauchern blockiert.

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}")
        }
}

Druckprotokoll zur Codeausführung:

1
时间已经过了745
2
时间已经过了1148
3
时间已经过了1552

Wenn wir den Puffer nicht verwenden, sollte die Gesamtdauer 2100 ms betragen. Wenn
wir den Puffer verwenden, beträgt die Gesamtdauer: 1552=300+400*3
. Daher kann der Produzent bei Verwendung des Puffers Daten gleichzeitig übertragen, ohne durch ihn blockiert zu werden Verbraucher.

Flow-Ausnahme

Verwenden Sie try/catch, um den Stream zu umschließen.
Wir können try/catch verwenden, um Stream-Ausnahmen zu sammeln, diese Methode wird jedoch nicht empfohlen.
Verwenden Sie den Catch-Operator von flow, um den Stream zu verarbeiten. Es
ist eleganter, den Catch-Operator von flow to zu verwenden Behandeln Sie Ausnahmen.
Catch weist jedoch auch Mängel auf: Es können nur Produzentenausnahmen abgefangen werden, Verbraucherausnahmen jedoch nicht.


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)
    }
}

So gehen Sie mit Verbraucherausnahmen um.
Versuchen Sie, eine Ausnahme im Verbraucher auszulösen, um zu sehen, ob sie abgefangen werden kann.

 flow<Int> {
    
    
        for (i in 1..3) {
    
    

            emit(i)
        }
    }.catch {
    
    
        emit(-1)
    }.collect {
    
    
        if(it==2){
    
    //在消费者中抛出数据
            throw IllegalArgumentException("数据不合法")
        }
        println(it)
    }

Ausgabe:

1
Exception in thread "main" java.lang.IllegalArgumentException: 数据不合法
	at HahaKt$consumerCatch$$inlined$collect$1.emit(Collect.kt:138)

Fügen Sie den Ausnahmecode in onEach ein, um die Ausnahme abzufangen

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)
    }
}

Ausgabe:

1
-1

Flow fordert Netzwerkdaten an

Oben wird beschrieben, wie Flow Daten verarbeitet.
Wir können es mit Retrofit in unseren Projekten verwenden
. Beispielsweise definieren wir zunächst eine Anfrage mit dem Rückgabetyp „Flow-Datenfluss“.

    @POST(ServerName.BS_API + "test/url")
	fun getMessage(): Flow<List<Bean>>

Verwenden Sie viewModelScope und die Viewmodel-Lebenszyklusbindung
und filtern Sie dann die zurückgegebenen Daten, um die endgültigen erforderlichen Daten zu erhalten:

        viewModelScope.launch {
    
    
            repository.getMessage()
                ?.filter {
    
    
                    it.isNotEmpty()
                }
                ?.collect {
    
    
                    //得到数据
                }
        }

Auf diese Weise können wir Flow verwenden, um verschiedene komplexe Verarbeitungen der zurückgegebenen Daten durchzuführen.
Dies sind natürlich immer noch grundlegende Verwendungszwecke. Sie können weitere Kapselungen basierend auf den tatsächlichen Geschäftsbedingungen vornehmen.

Relevante Information

Detaillierte Erklärung von Kotlin Flow Kotlin
Flow, wohin werden Sie fließen?
Die offizielle Flow-Adresse
verwendet Kotlin Flow, um eine Datenfluss-„Pipeline“ zu erstellen.

Supongo que te gusta

Origin blog.csdn.net/Android_xiong_st/article/details/128571004
Recomendado
Clasificación