Hurrikan, die chemische Reaktion von Lifecycle, Coroutine und Flow

Vorwort

Artikel der Coroutine-Reihe:

Ursprünglich war der erste Teil der Coroutine-Serie zu Ende, und einige Freunde schlugen später vor, dass wir über die tatsächliche Verwendung sprechen können. Ich habe das Gefühl, dass ich nicht aufhören kann, also lasst uns es mit ein paar weiteren Artikeln beenden. Wir wissen, dass ein wichtiges Thema, an dem man in der Android-Entwicklung nicht vorbeikommt, der Lebenszyklus ist: Wie sollen die beiden nach der Einführung von Coroutinen zusammenarbeiten?
Durch diesen Artikel erfahren Sie:

  1. Vergangenheit und Gegenwart des Lebenszyklus
  2. Die Kombination von Aktivität und Coroutine
  3. Die Zusammenarbeit von ViewModel und Coroutine
  4. Die Anwendung erstellt einen globalen Koroutinenbereich
  5. Die Dreiecksbeziehung von Flow, Coroutine und Lebenszyklus

1. Vergangenheit und Gegenwart des Lebenszyklus

Kurze Beschreibung des Lebenszyklus

Das aktuelle Systemdesign konzentriert sich mehr auf die Trennung von UI und Daten.Welche Daten für die aktuelle UI-Anzeige benötigt werden und wann die Daten angezeigt werden, muss von den Entwicklern selbst gesteuert werden. Wenn es nicht richtig gesteuert wird, können Speicherlecks und Ressourcenverschwendung auftreten.
Android bietet vier Hauptkomponenten, von denen Activity zur Anzeige der Benutzeroberfläche verwendet wird. Ihre Erstellung bis zur Zerstörung stellt ihren vollständigen Lebenszyklus dar. Unter den vier Hauptkomponenten schenken wir dem Lebenszyklus von Activity und Service mehr Aufmerksamkeit, insbesondere Activity ist die größte wichtig Das Wichtigste, und der Lebenszyklus von Fragment hängt von Aktivität ab. Solange Sie also den Lebenszyklus von Aktivität verstehen, sind andere Dinge kein Problem.

Bedenken hinsichtlich des Aktivitätslebenszyklus

Aktivitätsspeicherleck

Nehmen Sie als Beispiel die typische Datenerfassung im Hintergrund und Toast auf die Benutzeroberfläche:

        binding.btnStartLifecycle.setOnClickListener {
    
    
            thread {
    
    
                //模拟网络获取数据
                Thread.sleep(5000)
                runOnUiThread {
    
    
                    //线程持有Activity实例
                    Toast.makeText(this@ThirdActivity, "hello world", Toast.LENGTH_SHORT).show()
                }
            }
        }

Starten Sie den Thread im Hintergrund, simulieren Sie die Netzwerkanfrage, warten Sie 5 Sekunden und öffnen Sie den Toast.
In normalen Szenarien stellt dies kein Problem dar. Was passiert, wenn Sie die Aktivität beenden, bevor der Toast zu diesem Zeitpunkt angezeigt wird?
Offensichtlich wird es natürlich zu einem Speicherleck kommen , da die Aktivitätsinstanz vom Thread gehalten wird und nicht wiederverwendet werden kann und die Aktivität leckt.

Verschwendung von Ressourcen

Nehmen Sie die aus dem Hintergrund erhaltenen Daten und zeigen Sie sie als Beispiel auf der Aktivität an:

        binding.btnStartGetInfo.setOnClickListener {
    
    
            thread {
    
    
                //模拟获取数据
                var count = 0
                while (true) {
    
    
                    Thread.sleep(2000)
                    runOnUiThread {
    
    
                        binding.count.text = "计算值:${
      
      count++}"
                        println("${
      
      binding.count.text}")
                    }
                }
            }
        }

Starten Sie den Thread im Hintergrund, simulieren Sie die Netzwerkanfrage und aktualisieren Sie die TextView, nachdem Sie 5 Sekunden gewartet haben.
In normalen Szenarien stellt dies kein Problem dar. Wenn wir zu diesem Zeitpunkt zum Desktop zurückkehren oder zu anderen Apps wechseln, müssen wir die Benutzeroberfläche nicht aktualisieren und keine Netzwerkdaten abrufen. In diesem Fall wird dies der Fall sein eine Verschwendung von Ressourcen sein, und diese Methode sollte vermieden werden.

Die beiden oben genannten Phänomene treten auf, weil der Lebenszyklus der Aktivität bei der Implementierung der Funktion nicht beachtet wird. Kurz gesagt, wir achten auf den Lebenszyklus der Aktivität, um zwei Arten von Problemen zu lösen:
Bild.png

Die Lösung ist auch sehr einfach: Unabhängig davon, ob die Aktivität beendet wird oder in den Hintergrund zurückkehrt, es gibt Callbacks für jede Phase des Lebenszyklus. Solange der Aktivitätszyklus überwacht wird, können die oben genannten Probleme daher gelöst werden, indem an der entsprechenden Stelle ein Schutz durchgeführt wird.
Einzelheiten finden Sie unter: Ausführliche Erklärung und Überwachung des Lebenszyklus von Android-Aktivitäten

2. Die Kombination von Aktivität und Coroutine

Verwendung von Coroutinen ohne zugeordneten Lebenszyklus

Schauen Sie sich zuerst die Demo an:

        val scope = CoroutineScope(Job())
        binding.btnStartUnlifecyleCoroutine.setOnClickListener {
    
    
            scope.launch {
    
    
                delay(5000)
                scope.launch(Dispatchers.Main) {
    
    
                    Toast.makeText(this@ThirdActivity, "协程还在运行中", Toast.LENGTH_SHORT).show()
                }
            }
        }

Wie oben wird der Koroutinenbereich konstruiert, und die Koroutine wird durch ihn gestartet, und sie wird nach 5 Sekunden im Hintergrund ausgegeben.
Wenn auf die Schaltfläche geklickt wird, beenden wir die Aktivität und stellen schließlich fest, dass Toast weiterhin angezeigt wird, was darauf hinweist, dass ein Leck aufgetreten ist.

Die Verwendung von Coroutinen mit zugehörigen Lebenszyklen

das Leck beheben

Das Auftauchen von Coroutinen vereinfacht unsere Programmierstruktur, aber solange eine Beziehung zu Aktivität besteht, kommen wir nicht darum herum, auf ihren Lebenszyklus zu achten.
Glücklicherweise ist die Coroutine aktiv mit dem Lebenszyklus verknüpft, und Entwickler müssen sie nicht manuell bearbeiten. Sehen wir uns an, wie sie verwendet wird.

        binding.btnStartWithlifecyleCoroutine.setOnClickListener {
    
    
            lifecycleScope.launch {
    
    
                delay(5000)
                lifecycleScope.launch(Dispatchers.Main) {
    
    
                    Toast.makeText(this@ThirdActivity, "协程还在运行中", Toast.LENGTH_SHORT).show()
                }
                //假设有网络请求
                println("协程还在运行中")
            }
        }

Der Unterschied zur vorherigen Demo besteht in der Wahl des Gültigkeitsbereichs der Coroutine: Dieses Mal wird lifecycleScope verwendet, ein erweitertes Attribut von LifecycleOwner.
Nachdem Sie auf die Schaltfläche geklickt haben, beenden Sie die Aktivität.Zu diesem Zeitpunkt können Sie den Toast nicht sehen, und Sie können den Druck nicht sehen.Das bedeutet, dass der Coroutine-Bereich erkennt, dass die Aktivität beendet und zerstört wird, und die Aktivitätsinstanz wird dies tun nicht referenziert werden. Natürlich ist das Speicherleckproblem gelöst. .

Ressourcenverschwendung vermeiden

Wenn Sie vorsichtig sind, haben Sie vielleicht bemerkt: Wenn Sie auf die Schaltfläche klicken und zu diesem Zeitpunkt zum Desktop zurückkehren, stellen Sie fest, dass der Druckvorgang noch läuft.Tatsächlich möchten wir nicht, dass sie weiter ausgeführt werden, um zu speichern Ressourcen Was sollen wir tun?
Coroutinen berücksichtigen natürlich auch dieses Szenario und stellen einige praktische Funktionen bereit.

        binding.btnStartPauseLifecyleCoroutine.setOnClickListener {
    
    
            lifecycleScope.launchWhenResumed {
    
    
                delay(5000)
                lifecycleScope.launch(Dispatchers.Main) {
    
    
                    Toast.makeText(this@ThirdActivity, "协程还在运行中", Toast.LENGTH_SHORT).show()
                }
                println("协程还在运行中")
            }
        }

Nachdem Sie auf die Schaltfläche geklickt haben, kehren Sie zum Desktop zurück, und nachdem Sie einige Sekunden gewartet haben, wurde kein Druck gefunden. Nachdem ich vom Desktop zur App zurückgekehrt bin, habe ich festgestellt, dass sowohl Toast als auch Drucken angezeigt wurden.
Dies entspricht auch unseren Anforderungen: Wenn die App im Vordergrund ist, funktioniert die Coroutine, und wenn die App im Hintergrund läuft, funktioniert die Coroutine nicht mehr, wodurch unnötige Ressourcenverschwendung vermieden wird.
Die Funktion launchWhenResumed() aktiviert, wie der Name schon sagt, die Coroutine, wenn sich die Aktivität im Fortsetzen-Zustand befindet, und hält die Coroutine an, wenn sie sich nicht im Fortsetzen-Zustand befindet.Ähnlich sind launchWhenCreated und launchWhenStarted.

Das Prinzip der Coroutinen mit zugehörigen Lebenszyklen

Das Prinzip der Lösung von Speicherlecks

Wenn Sie wissen, wie man es verwendet, ist es an der Zeit, das Prinzip erneut zu untersuchen und sich auf den Umfang von Koroutinen zu konzentrieren.

#LifecycleOwner.kt
//扩展属性
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

#Lifecycle.kt
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
    
    
        while (true) {
    
    
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
    
    
                return existing
            }
            //构造新的协程作用域,默认在主线程执行协程
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
    
    
                //协程作用域关联生命周期
                newScope.register()
                return newScope
            }
        }
    }

fun register() {
    
    
    launch(Dispatchers.Main.immediate) {
    
    
        if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
    
    
            //监听生命周期变化
            lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
        } else {
    
    
            //如果已经处在destroy状态,直接取消协程
            coroutineContext.cancel()
        }
    }
}

Oben ist es zu sehen:

  1. LifecycleOwner hat ein erweitertes Attribut lifecycleScope, und LifecycleOwner enthält Lifecycle, also stammt lifecycleScope von LifecycleOwner aus dem erweiterten Attribut coroutineScope von Lifecycle
  2. Da es sich um ein erweitertes Attribut von Lifecycle handelt, ist es natürlich möglich, die Statusänderungen von Lifecycle zu überwachen

lifecycleScope überwacht die Statusänderungen von Lifecycle, schauen Sie sich nur die Verarbeitung seines Callbacks an:

#Lifecycle.kt
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
    
    
    if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
    
    
        //如果处于Destroy状态,也就是Activity被销毁了,那么移除监听者
        lifecycle.removeObserver(this)
        //取消协程
        coroutineContext.cancel()
    }
}

Bisher ist relativ klar:

Jede Activity-Instanz ist ein LifecycleOwner, und jede Activity ist einem lifecycleScope-Objekt zugeordnet, das den Lebenszyklus der Activity überwachen und die Coroutine abbrechen kann, wenn die Activity zerstört wird.

Grundsatz der Vermeidung von Ressourcenverschwendung

Verglichen mit dem Prinzip der Lösung von Speicherlecks ist das Prinzip der Vermeidung von Ressourcenverschwendung komplizierter, also werfen wir einen kurzen Blick darauf.
Nehmen Sie als Beispiel die Funktion launchWhenResumed, die eine Funktion in LifecycleCoroutineScope ist:

#Lifecycle.kt
public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
    
    
    //启动了协程
    lifecycle.whenResumed(block)
}

#PausingDispatcher.kt
public suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
    
    
    return whenStateAtLeast(Lifecycle.State.RESUMED, block)
}

public suspend fun <T> Lifecycle.whenStateAtLeast(
    minState: Lifecycle.State,
    block: suspend CoroutineScope.() -> T
): T = withContext(Dispatchers.Main.immediate) {
    
    
    //切换协程,在主线程执行
    val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
    //协程分发器
    val dispatcher = PausingDispatcher()
    //关联了生命周期
    val controller =
        LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
    try {
    
    
        //在新的协程里执行block
        withContext(dispatcher, block)
    } finally {
    
    
        controller.finish()
    }
}

Das Obige enthüllte drei Informationen:

  1. launchWhenResumed ist keine Suspend-Funktion, sondern startet intern eine neue Coroutine
  2. Die Schließung von launchWhenResumed wird von PausingDispatcher gesendet
  3. LifecycleController ist einem Lebenszyklus zugeordnet

Konzentrieren Sie sich auf Punkt 3:

#LifecycleController.kt
private val observer = LifecycleEventObserver {
    
     source, _ ->
    if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
    
    
        //取消协程
        handleDestroy(parentJob)
    } else if (source.lifecycle.currentState < minState) {
    
    
        //小于目标状态,比如非Resume,则挂起协程
        dispatchQueue.pause()
    } else {
    
    
        //继续分发协程
        dispatchQueue.resume()
    }
}

init {
    
    
    if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
    
    
        handleDestroy(parentJob)
    } else {
    
    
        //LifecycleController 初始化时监听生命周期
        lifecycle.addObserver(observer)
    }
}

Der Lebenszyklus ist immer noch mit dem Lebenszyklus verbunden.

Die Kombination der obigen Codes wird als etwas verwirrend und etwas verworren eingeschätzt.Die alten Regeln spielen keine Rolle.Sie können es mit dem Bild sehen: Der
Bild.png
springende Punkt ist die Beurteilung, ob es verteilt werden kann, was ist basierend auf dem Status in der DispatchQueue:

    fun canRun() = finished || !paused

Wenn es sich nicht im Fortsetzen-Zustand befindet, paused=true, kann es nicht verteilt werden;
wenn es sich im Fortsetzen-Zustand befindet, paused=false, kann es verteilt werden.
Wenn die Aktivität beendet wird, ist finished=true.

3. Die Zusammenarbeit zwischen ViewModel und Coroutine

Verwendung von Coroutinen ohne zugeordneten Lebenszyklus

In der MVVM-Architektur besteht der empfohlene Ansatz darin, Daten im ViewModel anzufordern, z. B.:

    val liveData = MutableLiveData<String>()
    fun getStuInfo() {
    
    
        thread {
    
    
            //模拟网络请求
            Thread.sleep(2000)
            liveData.postValue("hello world")
        }
    }

Hören Sie dann auf Datenänderungen in der Aktivität:

        //监听数据变化
        val vm  by viewModels<MyVM>()
        vm.liveData.observe(this) {
    
    
            Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
        }
        vm.getStuInfo()

Natürlich ist die Methode, einen Thread direkt zu öffnen, um Daten anzufordern, nicht elegant.Nun, da es eine Coroutine gibt, verwenden Sie einfach die Coroutine, um zur Sub-Thread-Anfrage zu wechseln.

    val scope = CoroutineScope(Job())
    fun getStuInfoV2() {
    
    
        scope.launch {
    
    
            //模拟网络请求
            delay(4000)
            liveData.postValue("hello world")
            println("hello world")
        }
    }

Dieselben Testschritte wie oben:
Nach dem Beenden der Aktivität wird das Coroutine-Drucken im ViewModel fortgesetzt.Obwohl die Aktivität zu diesem Zeitpunkt nicht leckt, wissen wir auch, dass das ViewModel der Aktivität dient, die Aktivität zerstört wird und das ViewModel dies nicht tut Es ist notwendig, daher sollten die zugehörigen Coroutinen ebenfalls abgebrochen werden, um Ressourcen zu sparen.

Die Verwendung von Coroutinen mit zugehörigen Lebenszyklen

    fun getInfo() {
    
    
        viewModelScope.launch {
    
    
            //模拟网络请求
            delay(4000)
            liveData.postValue("hello world")
            println("hello world")
        }
    }

Diese Schreibweise ist prägnanter als die obige.
Nach dem Verlassen der Aktivität wird die Coroutine abgebrochen, und natürlich wird der Druck nicht angezeigt.

Das Prinzip der Coroutinen mit zugehörigen Lebenszyklen

Der Fokus liegt auf dem viewModelScope-Objekt, das eine Erweiterungseigenschaft von ViewModel ist:

#ViewModel.kt
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        //查缓存
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        //加入到缓存里
        return setTagIfAbsent(
            JOB_KEY,
            //构造协程作用域
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

ViewModel erstellt ein erweitertes Attribut: viewModelScope, das verwendet wird, um den Bereich der Koroutine des aktuellen ViewModel darzustellen und das Bereichsobjekt in der Map zu speichern.
Sie können viewModelScope an der Stelle aufrufen, an der Sie die Coroutine im ViewModel verwenden möchten, was den Komfort erheblich verbessert.
Sehen wir uns als Nächstes an, wie die Coroutine abgebrochen wird, nachdem die Aktivität zerstört wurde.

    final void clear() {
    
    
        mCleared = true;
        if (mBagOfTags != null) {
    
    
            synchronized (mBagOfTags) {
    
    
                //从缓存取出协程作用域
                for (Object value : mBagOfTags.values()) {
    
    
                //取消协程
                closeWithRuntimeException(value);
            }
            }
        }
    }

Der gesamte Prozess wird durch ein Diagramm dargestellt:
Bild.png

Der obige Prozess beinhaltet das Prinzip von ViewModel, wenn Sie interessiert sind, können Sie zu Jetpack ViewModel wechseln

4. Die Anwendung erstellt einen globalen Coroutinenbereich

Unabhängig davon, ob es sich um den lifecycleScope in der Aktivität oder den viewModelScope im ViewModel handelt, beziehen sie sich alle auf die Seite, und es besteht keine Notwendigkeit, dass sie vorhanden sind, nachdem die Seite zerstört wurde. Und manchmal müssen wir Coroutinen an anderen Stellen außerhalb der Seite verwenden, sie sind nicht von der Seitenerstellung und -löschung betroffen, normalerweise denken wir an die Verwendung globaler Coroutinen.
Bild.png

Eigenschaften der benutzerdefinierten Anwendungserweiterung

val Application.scope: CoroutineScope
get() {
    
    
    return CoroutineScope(SupervisorJob() + Dispatchers.IO)
}
//使用
application.scope.launch {
    
    
    delay(5000)
    println("协程在全局状态运行1")
}

Ein globaler Coroutine-Bereich wird erstellt, und auf dieses erweiterte Attribut kann zugegriffen werden, wenn andere Module die Anwendungsinstanz erhalten.
Der Vorteil dieses Ansatzes: Sie können den Coroutine-Kontext einfach anpassen.

GlobalScope

Es wird im Allgemeinen während des Testens verwendet und wird nicht für formelle Projekte empfohlen.

GlobalScope.launch {
    
    
    delay(5000)
    println("协程在全局状态运行2")
}

ProcessLifecycleOwner

Offiziell produziert, wird es häufiger verwendet, um den Status der App vorne und hinten zu überwachen. Das Prinzip ist die Überwachung des Lebenszyklus. Da es den Lebenszyklus gibt, gibt es natürlich eine Reihe von Coroutinen:

ProcessLifecycleOwner.get().lifecycleScope.launch {
    
    
    delay(5000)
    println("协程在全局状态运行3")
}

5. Dreiecksbeziehung zwischen Flow, Koroutine und Lebenszyklus

klares Konzept

Aus Sicht der Android-Entwicklung haben die drei folgende Unterschiede:

  1. Der Lebenszyklus bezieht sich hauptsächlich auf den Lebenszyklus der Benutzeroberfläche
  2. Flow und Coroutinen gehören zur Sprachkategorie Kotlin, und Kotlin ist plattformübergreifend
  3. Flow muss in Coroutinen verwendet werden
  4. Als wir die beiden Punkte in 1.2 kombinierten, stellten wir fest, dass der mit dem Lebenszyklus verbundene Geltungsbereich der Coroutine in Form von erweiterten Attributen existiert, schließlich müssen andere Plattformen den Lebenszyklus möglicherweise nicht zuordnen

Fluss und Lebenszyklus

Lebenszyklus der LiveData-Zuordnung

Flow behauptet, eine erweiterte Implementierung von LiveData zu sein. Wir wissen, dass LiveData den Lebenszyklus erkennen kann, wie zum Beispiel:

        binding.btnStartLifecycleLivedata.setOnClickListener {
    
     
            vm.liveData.observe(this) {
    
    
                //接收数据
                println("hello world")
            }
            vm.getInfo()
        }

Wenn die App zum Desktop zurückkehrt, wird der LiveData-Callback nicht ausgelöst, selbst wenn das ViewModel den LiveData weiterhin Werte zuweist. Wenn die App in den Vordergrund zurückkehrt, wird der LiveData-Callback ausgelöst.
Dieses Design soll unnötige Verschwendung von Ressourcen vermeiden.

Flow kombiniert mit launchWhenXX

An dieser Stelle könnten Sie denken: Anstatt LiveData zu übergeben, um Daten zu übergeben, verwenden Sie Flow, um sie zu ersetzen.
Nach den bisherigen Erfahrungen lässt sich leicht wie folgt schreiben:

        binding.btnStartLifecycleFlowWhen.setOnClickListener {
    
    
            lifecycleScope.launchWhenResumed {
    
    
                MyFlow().flow.collect {
    
    
                    println("collect when $it")
                }
            }
        }

    val flow = flow {
    
    
        var count = 0
        while (true) {
    
    
            kotlinx.coroutines.delay(1000)
            println("emit hello world $count")
            emit(count++)
        }
    }

Erstellen Sie einen Cold-Flow, starten Sie die Coroutine über launchWhenResumed in der Activity und rufen Sie den Collect-Terminal-Operator in der Coroutine auf. collect löst die Ausführung des Codes im Flow Closure aus, gibt kontinuierlich Daten aus und der Druck im Collect Closure wird ebenfalls fortgesetzt.
Bringen Sie zu diesem Zeitpunkt die App auf den Desktop zurück und stellen Sie fest, dass das Drucken nicht angezeigt wird, bringen Sie die App dann wieder in den Vordergrund und das Drucken wird fortgesetzt. Auf diese Weise kann der gleiche Effekt wie bei LiveData erzielt werden.
Aus den gedruckten Ergebnissen haben wir auch interessante Phänomene gefunden:

Wenn die Zahl 5 gedruckt wird, kehren wir zum Desktop zurück und warten einige Sekunden, bevor wir in den Vordergrund zurückkehren. Zu diesem Zeitpunkt beginnt der Druck bei 6, was darauf hinweist, dass die launchWhenXX-Funktion die
Upstream-Arbeit des Flows nicht beendet, wenn die Die Aktivität ist nicht aktiv, sondern hält nur die Coroutine an

Flow kombiniert mit repeatOnLifecycle

Und häufiger, wenn die Aktivität nicht aktiv ist, möchten wir nicht, dass der Flow weiter funktioniert.Zu diesem Zeitpunkt wird eine andere API eingeführt: repeatOnLifecycle

        binding.btnStartLifecycleFlowRepeat.setOnClickListener {
    
    
            lifecycleScope.launch {
    
    
                repeatOnLifecycle(Lifecycle.State.RESUMED) {
    
    
                    MyFlow().flow.collect {
    
    
                        println("collect repeat $it")
                    }
                }
                println("repeatOnLifecycle over")
            }
        }

Gefunden durch Drucken:

Wenn die Zahl 5 gedruckt wird, kehren wir zum Desktop zurück, warten einige Sekunden und kehren dann zum Vordergrund zurück.Zu diesem Zeitpunkt beginnt das Drucken bei 0, was darauf hinweist, dass die Funktion repeatOnLifecycle die
Upstream-Arbeit des Flows beendet, wenn die Activity ist nicht aktiv, da die Coroutine abgebrochen wird. Wenn die Aktivität aktiv ist, wird die Coroutine neu gestartet und die Flow-Arbeit wird neu gestartet

Sie haben vielleicht noch Zweifel: Die obige Demo beweist den Unterschied zwischen den beiden nicht direkt, da der Ausdruck im Flussabschluss nicht erscheint, nachdem sich die Aktivität auf den Desktop zurückgezogen hat.
Mit einer leichten Modifikation der Demo wird das Ergebnis offensichtlich sein:

    val flow = flow {
    
    
        var count = 0
        while (true) {
    
    
            kotlinx.coroutines.delay(1000)
            println("emit hello world $count")
            emit(count++)
        }
    }.flowOn(Dispatchers.IO)

Bei Verwendung von „repeatOnLifecycle“ verschwindet der Druck, nachdem die Aktivität zum Desktop zurückgekehrt ist, was darauf hinweist,
dass der Flow nicht mehr funktioniert

Das Prinzip von repeatOnLifecycle

repeatOnLifecycle ist eine Erweiterungsfunktion von LifecycleOwner und dann eine Erweiterungsfunktion von lifecycle, hat also einen Lebenszyklus.
Die Funktion repeatOnLifecycle öffnet eine neue Coroutine und überwacht Änderungen im Lebenszyklus:

//监听生命周期
observer = LifecycleEventObserver {
    
     _, event ->
    if (event == startWorkEvent) {
    
    
        //大于目标生命状态,则启动协程
        launchedJob = this@coroutineScope.launch {
    
    
            // Mutex makes invocations run serially,
            // coroutineScope ensures all child coroutines finish
            mutex.withLock {
    
    
                coroutineScope {
    
    
                    block()
                }
            }
        }
        return@LifecycleEventObserver
    }
    if (event == cancelWorkEvent) {
    
    
        //小于目标生命状态,则取消协程
        launchedJob?.cancel()
        launchedJob = null
    }
    if (event == Lifecycle.Event.ON_DESTROY) {
    
    
        //Activity退出,则唤醒挂起的协程
        cont.resume(Unit)
    }
}
this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver)

Es gibt eine andere Möglichkeit, repeatOnLifecycle zu verwenden:

                MyFlow().flow.flowWithLifecycle(this@ThirdActivity.lifecycle, Lifecycle.State.RESUMED)
                    .collectLatest {
    
    
                        println("collect repeat $it")
                    }

Es hat die gleiche Wirkung wie repeatOnLifecycle, außer dass der auf diese Weise generierte Flow Thread-sicher ist.

Unterschiede und Anwendungsszenarien zwischen launchWhenXX und repeatOnLifecycle

Bild.png

Fassen Sie abschließend die Beziehung zwischen den dreien zusammen.
Bild.png

Flow ist sehr leistungsfähig und einfach zu verwenden. Der Schlüssel ist, wie man es verwendet, wie man aus vielen Flow-Operatoren für die Geschäftsentwicklung auswählt und wie man ihre Funktionen auf einen Blick unterscheidet. Der nächste Teil wird das Geheimnis der gemeinsamen Operatoren von Flow enthüllen, also bleib dran.
Dieser Artikel basiert auf Kotlin 1.5.3, bitte klicken Sie hier für die vollständige experimentelle Demo

Wenn es Ihnen gefällt, bitte liken, folgen und bookmarken, Ihre Ermutigung ist meine Motivation

Kontinuierliche Aktualisierung, Schritt für Schritt mit meinem System, eingehendes Studium von Android/Kotlin

1. Die Vergangenheit und Gegenwart der verschiedenen Kontexte von Android
2. Android DecorView muss wissen und wissen
3. Window/WindowManager muss Dinge wissen
4. View Measure/Layout/Draw wirklich verstehen
5. Android Event Distribution Full Set of Services
6. Android ungültig machen /postInvalidate /requestLayout gründlich geklärt
7. So ermitteln Sie die Größe des Android-Fensters/der Grund für die mehrfache Ausführung von onMeasure()
8. Android-Event-gesteuerte Handler-Message-Looper-Analyse
9. Android-Tastatur kann mit einem Trick erledigt werden
10. Die verschiedenen Koordinaten von Android werden gründlich verstanden
11. Hintergrund der Android-Aktivität / des Fensters / der Ansicht
12. Zur angezeigten Ansicht erstellte Android-Aktivität
13. Android IPC-Serie
14. Android Storage-Serie
15. Java Concurrency-Serie Keine Zweifel mehr
16. Java Thread Pool-Serie
17. Android Jetpack Pre-Basic-Serie
18. Android Jetpack leicht zu erlernende und zu verstehende Serie
19. Kotlin Easy Entry-Serie
20. Umfassende Interpretation der Kotlin-Coroutine-Serie

おすすめ

転載: blog.csdn.net/wekajava/article/details/129173528