Flutter: Instanzstatus wird nicht gespeichert, wenn die App vom Betriebssystem beendet wird

Erstellt am 12. Nov. 2016  ·  139Kommentare  ·  Quelle: flutter/flutter

Was ist ein Instanzzustand und warum existiert er?

Auf Android kann eine Aktivität jederzeit vom System beendet werden. Dies geschieht normalerweise, wenn Android Speicher benötigt, wenn Ihre Aktivität nicht im Vordergrund ist, oder aufgrund einer nicht behandelten Konfigurationsänderung, z. B. einer Gebietsschemaänderung.
Um zu vermeiden, dass der Benutzer neu starten muss, was er getan hat, als Android die Aktivität beendet hat, ruft das System onSaveInstanceState(…) wenn die Aktivität pausiert wird, wo die App ihre Daten in einem Bundle speichern soll , und übergibt das gespeicherte Paket sowohl in onCreate(…) als auch in onRestoreInstanceState(…) wenn die Aufgabe wieder aufgenommen wird, wenn die Aktivität vom System beendet wurde.

Das Problem damit im Flattern

In den Flatter-Apps, die ich ausprobiert habe (Flutter Gallery und das Basisprojekt mit dem FAB-Tap-Zähler), wenn ich genügend Apps öffne, damit Android die Aktivität der Flatter-App beendet, geht der gesamte Status verloren, wenn ich zur Flatter-Aktivität zurückkomme (während die Aufgabe nicht aus den letzten entfernen).

Schritte zum Reproduzieren

  1. Flutter-Galerie installieren
  2. Öffnen Sie die Einstellungen des Geräts und schalten Sie in den Entwickleroptionen "Aktivitäten nicht beibehalten" ein. (siehe Screenshot)
    dev_option
    Auf diese Weise können Sie simulieren, wann Android Aktivitäten beendet, da es an Speicher fehlt und Sie nicht im Vordergrund sind.
  3. Öffnen Sie die Flutter Gallery-App und gehen Sie zu einem anderen Ort als dem Hauptbildschirm.
  4. Gehen Sie zum Launcher, indem Sie die Home-Taste des Geräts drücken.
  5. Drücken Sie die Übersichtstaste und kehren Sie zur Flutter-Galerie zurück. Hier ist der Fehler.

Was wird erwartet: Die App befindet sich im selben Zustand, in dem wir aufgehört haben, mit unberührter Benutzeroberfläche.
Was passiert: Die Aktivität wird von Grund auf neu gestartet und verliert den gesamten UI-Zustand, sogar wirklich sehr lange Formulare.

P2 annoyance crowd routes framework new feature

Hilfreichster Kommentar

Kurzes Update: Ich arbeite derzeit aktiv daran, Lösungen für dieses Problem zu finden.

Alle 139 Kommentare

https://github.com/flutter/flutter/issues/3427 ist wahrscheinlich auch verwandt.

@eseidelGoogle Das stimmt. In welchem ​​Format könnte der Status der Flatteraktivität gespeichert werden, wenn dies ab der aktuellen Version überhaupt möglich ist?

Im Moment tun wir nichts, um etwas zu retten.

Das Framework selbst hat sehr wenig Zustand, der es wert ist, gerettet zu werden – es sind alles Animationen und ähnliches – also kann es sein, dass wir dies immer der App überlassen. Wir sollten es jedoch wahrscheinlich auf Dart-Ebene enthüllen. (Im Moment ist es nur auf Java-Ebene verfügbar.)

@Hixie Auf Android wird der sodass der Entwickler nur den Nicht-UI-Teil des

In Flutter gibt es nur sehr wenig Rahmenstatus zu speichern. Der aktivierte Status einer Schaltfläche ist beispielsweise kein Status, sondern eine von der Anwendung bereitgestellte Eingabe. Der aktuelle Routenverlauf wird im Framework gespeichert, jedoch nicht in einem Zustand, den das Framework neu erstellen kann (da es sich um alle Instanzen von Objekten handelt, die vom Anwendungscode bereitgestellt werden). Die Scroll-Position ist so ziemlich das Einzige, was wir tatsächlich speichern können (und das speichern wir derzeit in einem Store, nur keinen, der die App überlebt).

Aber auf jeden Fall sollten wir es besser machen als heute.

Als erfahrener Android-Entwickler würde ich mich auch gerne an den Gesprächen mit iOS-Entwicklern beteiligen, wenn es diskutiert wird, damit flattern das perfekte Framework zum Erstellen von iOS- und Android-Apps sein kann.
Ich denke, die Persistenzfähigkeit des Frameworks kann wichtig sein, um Daten, Einstellungen, Cache-Daten und Zustände so entwicklerfreundlich wie möglich zu speichern.

Whoa, ich wusste nicht, dass dies der Fall ist? Dies ist tatsächlich kritisch, und es ist erwähnenswert, dass der Prozess auf Geräten mit weniger Speicher ziemlich leicht beendet werden kann!

Wie wäre es mit dem Speichern von PageStore (oder verwandten) als Bytestream durch serialization.dart in onSaveInstanceState ?

@Takhion Könnten Sie den von Ihnen erwähnten "PageStore" verlinken? Ich bin daran interessiert, dieses Problem zu umgehen, da es anscheinend nicht bald behoben wird (der 31. Dezember 2029 ist IMHO ein bisschen weit)

@LouisCAD sicher ist es hier: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/page_storage.dart

Ich denke, Sie müssen jedoch noch mehr speichern: zumindest die aktuelle(n) Route(n) und vielleicht etwas Bundesland?
Was denkst du @Hixie?

Wir tun derzeit nichts, um dies zu vereinfachen. Wir haben dieses Problem noch nicht im Detail untersucht. Vorerst empfehle ich, die Informationen, die Sie beibehalten möchten, manuell zu speichern und bei der Wiederherstellung der App erneut anzuwenden.

Stellen wir die notwendigen Lebenszyklusereignisse ("Sie werden gleich getötet werden!", "Herzlichen Glückwunsch, Sie werden jetzt wiederhergestellt") in den Dart-Code ein, damit Entwickler mit anhaltenden Zuständen umgehen können? Das scheint die ausreichenden Fähigkeiten zu sein, um Entwickler freizuschalten, um Lösungen zu erkunden. Die Gedanken?

@sethladd Es reicht IMHO nicht aus, es Entwicklern

@LouisCAD danke für das Feedback. Ich denke in Schritten... was ist Schritt eins? Haben wir die Lebenszyklusereignisse schon offengelegt?

@sethladd alles, was ich finden konnte, ist dies , das keinen Rückruf zum Speichern / Wiederherstellen des Instanzstatus anzeigt

@Hixie Sie haben in #3427 erwähnt, dass wir Hooks für den Lebenszyklus haben. Meinten Sie https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html ?

Verwendet Flutter standardmäßig mehrere Aktivitäten? (zB wenn Sie zu einem neuen Bildschirm wechseln).

Der Prozesstod ist ziemlich häufig, der Benutzer drückt die Home-Taste und startet eine andere speicher-/CPU-intensive Anwendung und Ihre Anwendung wird im Hintergrund beendet (oder Ihre Anwendung war zu lange im Hintergrund). Es ist ein integraler Bestandteil des Android-Betriebssystems - jeder Bildschirm (Aktivität) sollte in der Lage sein, seinen Zustand zu erhalten und wiederherzustellen, und der Prozess kann jederzeit nach onStop() beendet werden.

Android erstellt den Backstack der Aktivitäten neu, wenn der Benutzer zu einer beendeten App zurückkehrt, die oberste Aktivität wird zuerst erstellt und dann werden Aktivitäten im Backstack bei Bedarf neu erstellt, wenn Sie im Backstack-Verlauf zurückgehen. Dies kann ein größeres Problem sein, wenn Flutter mehrere Aktivitäten verwendet (nicht sicher, ob dies der Fall ist).

Dies bedeutet, dass das System die Aktivitäten neu erstellt, wenn Sie keine Statusspeicherung implementiert haben, aber alles andere geht verloren (Prozess wurde beendet), was zu Inkonsistenzen und Abstürzen führt.

Ich habe einen Artikel über den Prozesstod auf Android geschrieben https://medium.com/inloop/android-process-kill-and-the-big-implications-for-your-app-1ecbed4921cb

Es gibt auch keine (saubere) Möglichkeit, Android daran zu hindern, Ihre Anwendung zu beenden, sobald sie den überschritten hat (nachdem Sie auf Home geklickt haben oder wenn die App einfach nicht die aktuelle Vordergrund-App ist). Also muss man irgendwie damit umgehen. Standard-Android-Widgets speichern ihren Status (zB eingegebenen Text in EditText) automatisch in der Bundle-Instanz. Ihre Aktivität benachrichtigt Sie, dass Sie Ihren Status speichern müssen, indem Sie onSaveInstanceState(Bundle) aufrufen. Vielleicht sollte Flutter also in der Lage sein, diesen onSaveInstanceState- Rückruf von der Aktivität an Ihre "Bildschirme" onCreate(Bundle savedInstanceState) oder onRestoreInstanceState(Bundle savedInstanceState) in Ihrer Aktivität zurück.

Um es noch einmal zusammenzufassen : Flutter könnte vielleicht die onRestoreInstanceState()- Callbacks an den Entwickler
Das Android-Betriebssystem nimmt dann das Bundle und behält es tatsächlich auf der Festplatte, damit es nicht verloren geht, falls der Prozess beendet wird und erneut deserialisiert werden kann.

Viel Glück damit :-). Bitte passen Sie auf und bringen Sie Flutter nicht zu viel von dieser Android-Status- / Lebenszyklus-Hölle ein.

Ich bin mir nicht sicher, wie das unter iOS funktioniert - aber ich denke, es gibt etwas Ähnliches, aber es ist nicht "notwendig", es zu verwenden (?).

Ein Lifecycle-Callback wäre für mich in Ordnung. Ich würde nur den serialisierten Redux-Zustand speichern/laden.

Danke für alle Rückmeldungen! @Takhion hat auch angeboten, hier zu helfen. Vielleicht ist ein API-Design und eine Bibliothek ein guter Anfang? Wenn diese Bibliothek funktioniert, können wir versuchen, formaler zu integrieren. Außerdem hilft die Bibliothek dabei, herauszufinden, was wir in der Low-Level-Engine tun müssen (wenn überhaupt). Grundsätzlich: Was ist der konkrete API-Vorschlag?

Verwendet Flutter standardmäßig mehrere Aktivitäten? (zB wenn Sie zu einem neuen Bildschirm wechseln)

@DanielNovak Flutter verwendet ein eigenes "Routing" -System und integriert sich standardmäßig in Android über eine einzige Ansicht in einer einzigen Aktivität. Sie können eine hybride Android/Flutter-App und als solche möglicherweise mehrere Aktivitäten und Flutter-Ansichten haben, aber in diesem Fall können Sie den Instanzstatus problemlos direkt über Android speichern/wiederherstellen.

Ich würde einfach den serialisierten Redux-Status speichern/laden

@zoechi Sie Instanzstatus- Bundle den IPC mit einem harten Limit von 1 MB für Ihren gesamten Android-App-Status durchlaufen muss. Der Instanzstatus sollte per Definition nur die Datenelemente sein, die es Ihnen ermöglichen, die gleichen Bedingungen für alle laufenden Aktivitäten des Benutzers wiederherzustellen, also: Texteingabe, Bildlaufposition, aktuelle Seite usw. Alles andere sollte auch auf der Festplatte beibehalten oder von einem anderen Status abgeleitet werden.

Flutter macht das Android onPause-Lebenszyklusereignis verfügbar . Das Android-Lebenszyklusereignis onDestroy() wird nicht
Um mit Androids onSaveInstanceState() und onRestoreInstanceState() kompatibel zu sein, ist es vielleicht sinnvoll, Flutter-Widgets ähnliche Methoden hinzuzufügen.
@sethladd @LouisCAD Hat jemand einen Designvorschlag erstellt?

@raju-bitter onPause() ist nicht optimal, es ist besser, sich in onSaveInstanceState() einzuklinken. Hier ist das Javadoc von onSaveInstanceState, das erwähnt, dass onPause() regelmäßiger aufgerufen wird als onSaveInstanceState (Sie würden das Speichern des Zustands häufiger als nötig auslösen oder wenn es überhaupt nicht notwendig ist):

Verwechseln Sie diese Methode nicht mit Callbacks für den Aktivitätslebenszyklus wie onPause(), das immer aufgerufen wird, wenn eine Aktivität im Hintergrund platziert wird oder auf dem Weg zur Zerstörung ist, oder onStop(), das vor der Zerstörung aufgerufen wird. Ein Beispiel dafür, wann onPause() und onStop() aufgerufen wird und nicht diese Methode ist, wenn ein Benutzer von Aktivität B zu Aktivität A zurück navigiert: Es besteht keine Notwendigkeit, onSaveInstanceState(Bundle) auf B aufzurufen, da diese bestimmte Instanz nie wiederhergestellt wird , sodass das System den Aufruf vermeidet. Ein Beispiel für den Aufruf von onPause() und nicht von onSaveInstanceState(Bundle) ist, wenn Aktivität B vor Aktivität A gestartet wird: Das System kann den Aufruf von onSaveInstanceState(Bundle) für Aktivität A vermeiden, wenn es während der Lebensdauer von B nicht beendet wird, da der Zustand der Benutzeroberfläche von A bleibt intakt.

@Takhion

Instanzstatus Bundle muss IPC mit einem harten Limit von 1 MB für Ihren gesamten Android-App-Status durchlaufen

Das ist nicht mein Thema.
Ich muss nur wissen, wann ich fortbestehen/wiederherstellen muss, mich selbst auf der Festplatte zu belassen ist für mich in Ordnung.

Das Propagieren von onSaveInstanceState / onRestoreInstanceState ist der einfache Teil: Flutter ist in einer Ansicht enthalten und hat richtigen Zugriff auf diese Rückrufe. Was ich versuche, ist, einen Designvorschlag und einen Prototyp zu erstellen, um Objekte, die in einem bestimmten "Repository" enthalten sind, automatisch zu serialisieren, sodass die Verwendung schmerzlos wird (im Gegensatz dazu, dass alles manuell erledigt werden muss).

@Takhion du bist der Wind unter meinen Flügeln. Ich kann es kaum erwarten, den Vorschlag zu sehen!

OK, also baue ich eine PR dafür und bin auf eine Straßensperre gestoßen: [ View.onSaveInstanceState ] (oder [ Activity.onSaveInstanceState ], für die Angelegenheit) erfordern, dass der gespeicherte Instanzstatus synchron bereitgestellt wird , und von meiner Erfahrung nach (und dieser [Kommentar]) sieht es so aus, als ob Host-/Plattformnachrichten nur asynchron sein können . Ich vermute, dass dies daran liegt, dass sie die Nachrichtenwarteschlange von Android verwenden.

@sethladd @Hixie @eseidelGoogle @jason-simmons Bitte sagen Sie mir, dass es eine einfache Möglichkeit gibt, einen einzigen synchronen/blockierenden Anruf von Android an Flutter zu tätigen, der keinen Ad-hoc-C/C++-Code erfordert, da [ dart:jni wurde im Ruhestand]?

Cc @mravn-google

Die asynchrone Natur von Plattformnachrichten ergibt sich aus der Tatsache, dass die Dart-Benutzeroberfläche in einem anderen Thread ausgeführt wird als der, der für die Rückrufe der Android-Plattform verwendet wird. Sie können jedoch jederzeit einen asynchronen API-Aufruf in einen synchronen umwandeln, indem Sie einen Latch verwenden:

final MethodChannel channel = new MethodChannel(messenger, someName);
final CountDownLatch latch = new CountDownLatch(1);
channel.invokeMethod("someMethod", someArguments, new MethodChannel.Result() {
  <strong i="6">@Override</strong>
  public void success(Object result) {
    // handle successful invocation
    latch.countDown();
  }
  <strong i="7">@Override</strong>
  public void error(String errorCode, String errorMessage, Object errorDetails) {
    // handle failed invocation
    latch.countDown();
  }
  <strong i="8">@Override</strong>
  public void notImplemented() {
    // handle invocation of unimplemented method
    latch.countDown();
  }
});
try {
  latch.await();
} catch (InterruptedException e) {
  // handle interruption
}

Sie können eine final AtomicReference<SomeType> Variable (oder ähnliches) neben dem Latch deklarieren, wenn Sie Werte von den // handle xxx Speicherorten an den Code übermitteln müssen, der nach der Rückgabe von latch.await() .

danke @mravn-google!

@mravn-google das ist genau der erste Ansatz, den ich versucht habe, aber der Hauptthread auf Android hängt einfach für immer am await() Aufruf :(

@Takhion Richtig, natürlich.

Sie können einen asynchronen API-Aufruf jederzeit in einen synchronen umwandeln, indem Sie einen Latch verwenden

... es sei denn, die asynchrone Antwort soll an den Thread geliefert werden, in dem Sie sich bereits befinden :-/

Wir brauchen also anscheinend eine Art Synchronisierungs-Handshake für onSaveInstanceState . Kennen wir andere, ähnliche Rückrufe, die eine Synchronisierungsantwort mit Dart-seitigen Berechnungen erfordern? Das würde einen universellen, synchronen Plattform-Nachrichtenaustausch erfordern. Wenn nicht, könnten wir etwas speziell für onSaveInstanceState tun.

@mravn-google leider ist Android _voll_ von diesen fiesen "synchronen Gelegenheitsfenstern", in denen alles passieren muss, bevor es vom Methodenaufruf zurückkehrt! Auf meinem Kopf: viele Activity Lifecycle-Ereignisse, Methoden in Broadcast Receiver , SyncAdapter usw. Ich bin ehrlich überrascht, dass es vorher kein größeres Problem war!

Meiner Meinung nach ist eine synchrone Nachrichtenkommunikation ein Muss, wenn wir möchten, dass Flutter wirklich eine Funktionsparität mit dem erreicht, was durch "native" APIs möglich ist.

@mit-mit Ich meinte SystemChannels.lifecycle und alles, was das verwendet, ja.

@sethladd Ich denke schon.

@mravn-google danke! Ich habe #7560 zugunsten dieser geschlossen, nur um die Konversation zu deduplizieren und zu zentralisieren.

Ich tendiere dazu, MethodChannel ein synchrones Gegenstück für diese Art von Plattform-zu-Dart-Kommunikation hinzuzufügen.

@mravn-google wie geht es dir? Kann ich helfen?

@Takhion Danke! Sollte es bald geben. Ich arbeite an einem synchronen Methodenkanal und hoffe, dass ich in ein oder zwei Tagen eine PR habe. Sobald es da ist, bitte ich um Ihre Bewertung. Ich möchte den neuen Kanal so schnell wie möglich an onSaveInstanceState , um zu testen, ob in diesem Szenario alles wie beabsichtigt funktioniert. Wenn Sie bereits eine Beispiel-App haben, die Sie beitragen könnten, wäre das großartig.

@mravn-google super! Ich habe keine App, weil ich das Framework direkt ändere, aber ich kann sicherlich ein Beispiel zur Verfügung stellen. Übrigens verwende ich BinaryMessages direkt, da auf der Android-Seite kein Codec erforderlich ist (es werden nur Bytes weitergegeben), ist das in Ordnung?

ihr rockt.

@Takhion Die direkte Verwendung von BinaryMessages ist in Ordnung, wenn Sie keinen Codec benötigen. Ich werde a hinzufügen

void setSynchronousMessageHandler(String channel, ByteData handler(ByteData message))

zu dieser Klasse.

@Takhion Schlag das zu. Wir können nicht einfach synchrone Aufrufe in die Dart-VM ausführen, um handler aufzurufen. BinaryMessages bleibt also unverändert. Stattdessen wird aus Sicht des Dart-Codes wie gewohnt alles asynchron sein. Die Änderung wird an BinaryMessenger und seinen Implementierungen auf der Plattformseite vorgenommen. Ich werde hinzufügen

ByteBuffer sendSynchronous(String channel, ByteBuffer message);

Der Plattformansichtscode, der FlutterView , implementiert dies, indem er eine asynchrone Plattformnachricht sendet und dann auf einen Latch wartet. Das in dieser Plattformnachricht enthaltene Antwortobjekt wird keine Antwortverarbeitungsschließung an den Plattformthread (der jetzt blockiert ist) senden, sondern stattdessen die Antwort speichern und den Latch freigeben.

@mravn-google funktioniert für mich, da es uns nicht daran hindert, Daten von der Dart-Seite zurückzusenden, auch wenn es sich um einen asynchronen Anruf handelt :+1:
Ich denke, es wird keine Optimierungen geben, wenn man Future.value(...) in der asynchronen Dart-Antwort verwendet?

@Takhion Bitte schau mal auf https://github.com/flatter/engine/pull/4358

Ich sehe, Sie sprechen hier hauptsächlich über die Android-Plattform. Aber tatsächlich hat iOS auch einen ähnlichen Mechanismus zum Speichern / Wiederherstellen des UI-Zustands, wenn die App aus irgendeinem Grund beendet wurde. Wenn der Benutzer beispielsweise zu einer anderen App wechselt, wird die aktuelle App ausgesetzt und kann aufgrund von Speicherbeschränkungen sogar beendet werden. Weitere Informationen zu diesem Vorgang finden Sie in den offiziellen Dokumenten .

iOS-Entwickler erwarten, dass dieses Konservierungs-/Wiederherstellungsverfahren fast automatisch abläuft. Ich denke, wenn Ihr Ziel darin besteht, ein wirklich plattformübergreifendes Framework zu erstellen, sollten Sie dies auf jeden Fall berücksichtigen.

Man könnte meinen, dass etwas, das Android als Plattform anvisieren soll, zumindest versucht, die Grundlagen des Aktivitätsvertrags zu befolgen....

@Zhuinden lass uns nicht über Dinge beschweren, die sich noch in der Beta befinden? :)

Andere synchrone APIs sind onLowMemory und onTrimMemory und onConfigurationChanged weiß nicht, ob IOS auch ähnliche APIs oder Mechanismen hat

Möglicherweise möchten wir den fehlerhaften Android-Speicher- und Wiederherstellungsmechanismus auf Flutter nicht reproduzieren: Sie können aufgrund der Transaktionsgrößenbeschränkung nicht den gesamten Zustand im Bundle speichern.

Auf Android haben Sie am Ende mehrere Logiken zum Speichern / Wiederherstellen des Zustands:

  • savedInstanceState des Entwicklers, aber Sie können darin keine großen Objekte speichern, wie z. B. eine Liste von Elementen
  • Datenbank, in der Sie große Objekte speichern können
  • die meisten Widgets verwalten auch ihren eigenen Status
  • Aktivitäts-/Fragmentstapel

Für Entwickler ist es wirklich einfach, unordentliche und fehlerhafte Logik zu implementieren. Nach meiner eigenen Erfahrung ist es wirklich selten, dass die Zustandssicherung und -wiederherstellung richtig durchgeführt wird.

Beim Flattern ist der Zustand des Entwicklers die einzige Quelle der Wahrheit.
Entwickler sollten diesen Zustand auf der Festplatte beibehalten, wenn er sich ändert, ohne die eingeschränkte systemeigene Plattformmechanik zu durchlaufen.

Sicher, Sie werden die Scroll-Position nicht einfach speichern können, aber wen interessiert das schon?
Meistens bedeutet eine gute UX für die Wiederherstellung, dass der Benutzer den gleichen Bildschirm sehen sollte, auf dem er zuvor war. Mit Flutter können Sie dies bereits heute tun.

Unterer Punkt: Auf der Flutter-Seite gibt es Raum für Verbesserungen, um das Speichern / Wiederherstellen des Zustands weniger Boilerplate (für Dinge wie Navigator) zu machen. Aber ich denke nicht, dass es einen Haken zum Speichern/Wiederherstellen der Plattform bieten sollte.

Sicher, Sie werden die Scroll-Position nicht einfach speichern können, aber wen interessiert das schon?

Jedermann? Ich wäre wirklich frustriert, wenn die App ihre Liste jedes Mal zurücksetzt, wenn ich versehentlich eine Orientierungsänderung auslöse. :P

@Saketme FWIW, Flutter setzt die Scroll-Positionen bei einer Ausrichtungsänderung nicht zurück.

Die Verwendung einer Flutter-App mit ein bisschen Multitasking sollte auf einem Android-Go-Gerät Spaß machen! Der Fortschritt geht jedes Mal verloren, wenn 1 GB RAM oder weniger unter Druck stehen

@Saketme : Wie @mravn-google sagte, ist Flutter nicht an Konfigurationsänderungen wie Android-Aktivitäten gebunden.
Die einzige erforderliche Zustandsverwaltung ist, wenn die App im Hintergrund vom Betriebssystem beendet wird.

@LouisCAD Sicheres Speichern und Wiederherstellen des Zustands ist wichtig.
Flutter-Entwickler können den größten Teil dieser Logik bereits selbst implementieren, und Flutter sollte wahrscheinlich Wege bieten, um sie weniger Boilerplate zu machen. Der von Android angebotene Mechanismus ist jedoch fehlerhaft und wir könnten die Gelegenheit nutzen, mit Flutter etwas Besseres zu entwickeln, anstatt sich in Activity onSaveInstanceState / onRestoreInstanceState hacken.

Wie ist der aktuelle Status dieses Problems?

Betrachten wir den Fall einer Chat-App, bei der ich eine lange Nachricht in das TextField schreibe.
Plötzlich erhalte ich einen Anruf und nach einiger Zeit beendet das Betriebssystem die Chat-App wegen zu wenig Speicher.
Nach dem Telefonat komme ich zurück zur Chat-App. Wird Flutter automatisch bestehen bleiben und die Nachricht, die ich eingegeben habe, neu erstellen? oder Die App muss bestehen bleiben und manuell neu erstellen?

Was sind außer der Scrollposition und der Route noch "Zustände", die normalerweise nicht von Entwicklern verwaltet werden?

@nsreenath Jede Chat-App sollte alle paar Zeichen und beim Wechsel in den Hintergrund die Nachrichtenentwürfe für sich behalten. Sie können eine lange Nachricht eingeben, und wenn das Gerät aus irgendeinem Grund heruntergefahren wird, möchten Sie Ihren Fortschritt nicht verlieren. Der Android-Instanzstatus ist für leichte Benutzereingaben und den Fortschritt in den Bildschirmen vorgesehen

Ich habe tatsächlich darüber geschlafen und bin zu der Erkenntnis gekommen, dass es für Flutter-Entwickler eigentlich ganz einfach ist zu erkennen, ob die Anwendung vom Prozesstod zurückgekommen ist oder eine völlig neue Instanz ist.

Sehr einfach, solange sie nach savedInstanceState == null suchen (vorausgesetzt, sie legen mindestens 1 Element in die onSaveInstanceState(bundle) ). Wenn ein statisches boolesches Flag isFirstInit == false und savedInstanceState != null , dann ist es nach dem Prozesstod.

Dann können sie jede Art von Serialisierungs-/Deserialisierungs-Callback für komplexe Zustände erstellen, die automatisch gelöscht werden könnten, wenn sie savedInstanceState == null . Auf diese Weise müssen sie nur einen zufälligen Booleschen Wert beibehalten, können aber ihr eigenes Persistenzsystem verwalten.

Sehr einfach, solange sie nach savedInstanceState == null suchen (vorausgesetzt, sie legen mindestens 1 Element in das onSaveInstanceState(bundle)).

Unter iOS gibt es kein savedInstanceState und onSaveInstanceState . Aber die App kann immer noch vom Betriebssystem beendet werden.

Betrachten Sie einen typischen Arbeitsablauf:

  • Ein Benutzer geht zu einem bestimmten Bildschirm in der App
  • Dann wechselt der Benutzer zu einer anderen App
  • Plötzlich beschließt das weise Betriebssystem, die vorherige App zu beenden, um den Speicher zu erhalten
  • Der Benutzer kehrt zur vorherigen App zurück, sieht jedoch den Startbildschirm. Nicht der Bildschirm, der geöffnet war, als sie sich entschied, die App zu wechseln

Was ist also derzeit die empfohlene Methode, um den Bildschirmverlauf in Flutter beizubehalten?

PS Das ist natürlich nicht wichtig, wenn deine App nur einen Bildschirm hat :smirk:

@Zhuinden Ich sehe nicht viele Szenarien, in denen dies sowieso erforderlich ist.

a) Chat-Beispiel:
Speichern und wiederherstellen Sie immer den vorherigen Inhalt, der in das Textfeld geschrieben wurde und nicht gesendet wurde.

b) Langform-Beispiel:

  1. Inhalt immer speichern
  2. Wenn der Benutzer zum Formularbildschirm zurückkehrt, benachrichtigen Sie den Benutzer, dass eine Entwurfsversion gespeichert, aber nicht gesendet wurde, und fragen Sie, ob er sie wiederherstellen möchte. Es macht keinen Unterschied, ob der Benutzer das Formular manuell verlassen hat (evtl. fragen, ob der Benutzer den Entwurf speichern möchte) oder ob die App im Hintergrund beendet wurde.

Es gibt wahrscheinlich einige Fälle, in denen spezifisches Wissen über "frischer neuer Bildschirm" im Vergleich zu "neuer Bildschirm nach Prozesstod" nützlich ist, aber das ist möglicherweise nicht so häufig, wenn Sie den Inhalt Ihrer App so gestalten, dass er immer von einer Datenbank unterstützt wird.

@storix Sie müssen die aktuelle Route manuell
Außerdem: Viele Android-Entwickler wären überrascht, wie wenige iOS-Apps/-Entwickler sich mit der Zustandswiederherstellung beschäftigen. Die Mehrheit der Entwickler, die ich kenne, ist sich nicht einmal bewusst, dass das ein Problem ist. Schätze, es ist der Luxus, nur auf High-End-Geräten zu arbeiten.

@Zhuinden die Lifecycle-Callbacks von https://github.com/flutter/flutter/issues/6827#issuecomment -335160555 funktionierten für meine Chat-Anwendung. Ich speichere einfach jedes Mal einen Status, wenn die App in den Hintergrund geschaltet wird. Siehe auch https://docs.flutter.io/flutter/dart-ui/AppLifecycleState-class.html

Ich möchte das Flutter-Team dringend auffordern, Android/iOS-Lebenszyklusmethoden nicht als kanonische Methode zum Beibehalten/Wiederherstellen des UI-Zustands bereitzustellen. Selbst in nativen Apps war es schwierig, sie richtig zu erkennen und zu codieren, und die Grenze zwischen UI- und Anwendungsstatus war oft verschwommen, wobei für jede unterschiedliche Mechanismen vorgesehen waren. Es wäre schade, all diese unterschiedlichen APIs in Flutter zu veröffentlichen (insbesondere, wenn in Zukunft zusätzliche Plattformen angestrebt werden).

Die zustandslosen Widgets von Flutter haben _bereits_ die Verantwortung für den UI-Status aus den Widgets in die App migriert, sodass das Problem des Beibehaltens/Wiederherstellens des UI-Zustands zunehmend durch das Beibehalten/Wiederherstellen des _app_-Zustands abgedeckt wird. Ich sehe dies als eine gute Sache an, im Gegensatz zu einer unterschiedlichen Behandlung des UI- und App-Status, wie bei iOS und Android.

Mir ist aufgefallen, dass einige Flutter-Widgets wie ScrollView und TextField "Controller" -Klassen bereitstellen, die den dynamischen Zustand des Widgets kapseln. In gewisser Weise fungieren sie als "Memento"-Objekte – eine Momentaufnahme des Zustands des Widgets – die zu einem späteren Zeitpunkt an das Widget zurückgegeben werden kann, um ihren Zustand in einer früheren Konfiguration wiederherzustellen. Klingt vertraut. Wenn diese "Erinnerungen" leicht zu speichern/wiederhergestellt werden könnten, könnte eine Anwendung im Rahmen der Verwaltung ihres eigenen Zustands die Verantwortung dafür übernehmen. Dies würde die Dichotomie des App/UI-Zustands beenden, wäre ein Opt-in und würde das Flutter-Framework überflüssig machen, um Hooks zur Erhaltung des UI-Zustands bereitzustellen (weniger ist mehr!).

Mit anderen Worten, machen Sie es in die Verantwortung des Entwicklers, aber versuchen Sie es so einfach wie möglich zu machen. Wenn ein Entwickler der Meinung ist, dass ein halb ausgefülltes Formular wichtig genug ist, damit Flutter über alle Aufrufe hinweg bestehen bleibt, dann könnte man argumentieren, dass es wahrscheinlich wichtig genug ist, um als Anwendungsstatus angesehen zu werden. Mir ist klar, dass das Beibehalten des Zustands von Objekten zu Versionsproblemen führt, aber ich denke, dies ist ein leichter zu handhabendes Problem.

Gab es diesbezüglich Fortschritte? Ich denke, es besteht eine echte Notwendigkeit, zwischen dem "ersten Durchlauf" und dem "wiederhergestellten" Prozesszustand zu unterscheiden. Wie sonst können Sie programmieren, um den richtigen Bildschirm anzuzeigen, wenn der Benutzer zu einem beendeten Prozess zurück navigiert?

@lukaspili Sie haben geschrieben: "Die einzige erforderliche Zustandsverwaltung ist, wenn die App im Hintergrund vom Betriebssystem beendet wird."

Okey, was ist dann mit Router-Stack, auch auf Disc speichern? Außerdem nutze ich t find proper solution to determine that application was restored. Every time when app goes to background I need to save router stack, app data, state to disc? What if I don Redux nicht?

Ich denke, als ersten Schritt sollten wir Plugins die Möglichkeit geben, den Zustand zu speichern und wiederherzustellen. Hier ist mein Vorschlag: #22328

Gab es zu diesem Thema ein Update?

Ich stehe vor dem gleichen Problem. Ich habe einen Workaround zum Speichern von Texteingaben hier über die Firebase-Echtzeitdatenbank. Aber die Methode ist ziemlich grob und es fehlt oft die Skalierbarkeitsoption, da jeder einzelne Benutzer auf dieselbe Datenbank umgeleitet wird. Somit stehen meine Daten allen anderen mit Zugang zu einem Gmail-Konto zur Verfügung. Ich habe versucht, Redis zu verwenden, aber obwohl ich für andere Benutzer separate Listen in derselben Datenbank erstellen kann, geschieht dies immer über eine POST-Methode. Das bedeutet, dass die Ansicht jedes Mal aktualisiert werden muss, wenn neue Daten gespeichert werden. Das bedeutet keine Echtzeit-Visuals. In der Tat ein großer Fehler.

Hier gibt es ein größeres Problem. Ich hatte vor zwei Monaten einiges hin und her mit @matthew-carroll und ich bin ziemlich zuversichtlich, dass dies im Gange ist.

Ich habe damit begonnen, eine Bibliothek zu erstellen, die sich damit befasst . Ich würde nicht empfehlen, es zu verwenden, aber es ist eine Idee einer Nur-Dart-Implementierung. #6225 machte die Dinge ein bisschen knifflig.

Mir ist aufgefallen, dass die Google Ads-App, die angeblich mit Flutter erstellt wurde, den Navigationsstatus nach einem Neustart der Aktivität beibehält. Es wäre eine nette Referenz zu wissen, wie es implementiert wird, auch wenn es ein Workaround ist.

@aletorrado Behält die Google Ads App den Navigationszustand bei, falls der Nutzer die App aus dem Android/IOS „Task-Manager“ verworfen und später wieder gestartet hat?

@gitspeak Nein, es

@aletorrado Also ja, das ist wahrscheinlich der einzige Weg...

Ich habe tatsächlich versucht, in die Google Ads-App zu gelangen, aber Sie benötigen ein aktives Konto, um zu jedem Bildschirm über den allerersten hinaus zu gelangen: Ich kann nicht testen, ob es wirklich funktioniert oder nicht

Es tut. Garantiert. Es behält auch andere Zustände wie die Scroll-Position bei.

Um einige Verwirrung zu verdeutlichen:

  • Ja, die Google Ads-App wurde mit Flutter geschrieben.
  • Als einer der Early Adopters mussten sie für die Hintergrundausführung etwas Ungewöhnliches tun. Sie erstellen und pflegen ihr Hauptisolat beim Start der Anwendung (nicht der Aktivität). Dann übergeben sie dieses Isolat an die Flutter-Ansicht, wenn sie erstellt wird.
  • Was Sie als "state Keeping" betrachten, ist ein Nebeneffekt der oben beschriebenen Methodik. Wenn Sie die Aktivität in Google Ads beenden, stirbt die "UI" von Flutter nicht. Es wird im Speicher gehalten, der an den Anwendungskontext gebunden ist. Wenn die Aktivität neu gestartet wird, lädt sie dasselbe Isolat und dieselbe Bratsche, Sie erhalten Ihre Benutzeroberfläche zurück.

Wir empfehlen diesen Ansatz zur Lösung dieses Problems nicht, da er das Problem überhaupt nicht löst. Stirbt der Bewerbungsprozess, geht immer noch der gesamte Zustand verloren. Sie können dies mit Google Ads versuchen, indem Sie den Vorgang über Einstellungen > Apps beenden und stoppen. Die Benutzeroberfläche geht verloren.

Möglicherweise gibt es eine Möglichkeit, den Isolationszustand lokal zu speichern, aber es bedarf weiterer Untersuchungen, da wir nicht sicher sind, wie machbar/sicher dies ist.

Im Moment sind die oben beschriebenen Lösungen die besten: Hängen Sie sich an native Methoden und speichern Sie selektiv den Zustand Ihrer App, damit Sie sie später wiederherstellen können. Eine Framework-unterstützte automatische Lösung sowohl auf iOS als auch auf Android ist noch weit entfernt.

Sie können dies mit Google Ads versuchen, indem Sie den Vorgang über Einstellungen > Apps beenden und stoppen. Die Benutzeroberfläche geht verloren.

nein das ist völlig ok. Das Beenden einer App erzwingen sollte den Status verlieren.

Beendet durch einen niedrigen Speicherzustand sollte nicht.

So oder so, wenn sie den Status in der Anwendung als Singleton-In-Memory behalten, ohne etwas auf der Festplatte/dem Bundle zu speichern, dann wird es verloren gehen.

Es ist schade, dass ich die App nicht ohne Konto testen kann, da ich kein Google Ads-Konto erstellen werde, nur um an der App selbst zu basteln. :denkendes_gesicht:

Verbinden Sie sich mit nativen Methoden und speichern Sie den Status Ihrer App selektiv, damit Sie sie später wiederherstellen können.

Tatsächlich wäre die beste Lösung, den aktiven Zustand auf der Festplatte beizubehalten und den Prozesstod auf der nativen Android-Seite zu erkennen. Wenn Sie den Prozesstod feststellen, laden Sie ihn neu von der Festplatte, andernfalls löschen Sie ihn und starten Sie von vorne.

Ich weiß nicht genug über Flutters Navigatoren, um Ihnen zu sagen, ob Sie den Navigationsverlauf mit einer Art setHistory oder setBackstack Methode rekonstruieren können, wie Sie es mit einem Backstack oder Conductor oder so ähnlich tun würden.

Aktivitäten/Fragmente behandeln dieses Zeug intern mit dem ActivityRecord/FragmentRecord, so dass sie darüber hinaus nicht erwähnt werden.

Danke für all diese Einblicke @mehmetf. Es ist traurig zu hören, dass die Unterstützung für das Speichern/Wiederherstellen des Zustands weit davon entfernt ist, umgesetzt zu werden.

Vielleicht könnte flatter_redux helfen, einen Teil des Anwendungsstatus zu serialisieren.

Nun, wenn Sie den Blob des Redux-Speichers auf der Festplatte beibehalten. Achten Sie nur auf das Problem des "unendlichen Ladedialogs nach Prozesstod". :denkendes_gesicht:

Um einige Verwirrung zu verdeutlichen:

Eigentlich bin ich etwas verwirrter..

Als einer der Early Adopters mussten sie für die Hintergrundausführung etwas Ungewöhnliches tun. Sie erstellen und pflegen ihr Hauptisolat beim Start der Anwendung (nicht der Aktivität). Dann übergeben sie dieses Isolat an die Flutter-Ansicht, wenn sie erstellt wird.

Was hat das mit dem vorliegenden Thema zu tun? Laufen Dart-Isolate in einem separaten Prozess?

Was Sie als "state Keeping" betrachten, ist ein Nebeneffekt der oben beschriebenen Methodik. Wenn Sie die Aktivität in Google Ads beenden, stirbt die "UI" von Flutter nicht. Es wird im Speicher gehalten, der an den Anwendungskontext gebunden ist. Wenn die Aktivität neu gestartet wird, lädt sie dasselbe Isolat und dieselbe Bratsche, Sie erhalten Ihre Benutzeroberfläche zurück.

Meines Wissens nach beobachtete

Zu Ihrer Information: Wenn eine "Aktivität" zur Laufzeit stirbt, folgt IMMER das Beenden des Prozesses.

Wir empfehlen diesen Ansatz zur Lösung dieses Problems nicht, da er das Problem überhaupt nicht löst. Stirbt der Bewerbungsprozess, geht immer noch der gesamte Zustand verloren. Sie können dies mit Google Ads versuchen, indem Sie den Vorgang über Einstellungen > Apps beenden und stoppen. Die Benutzeroberfläche geht verloren.

Wie @Zhuinden erwähnte, ist dies kein gültiges Szenario für die "

Möglicherweise gibt es eine Möglichkeit, den Isolationszustand lokal zu speichern, aber es bedarf weiterer Untersuchungen, da wir nicht sicher sind, wie machbar/sicher dies ist.

Das ist nicht der richtige Weg. Für Android sollten Sie sich in den onSaveInstanceState-Mechanismus einklinken, da er explizit dazu dient, den Aktivitätsstatus für Szenarien zu speichern, in denen "State Keeping" nach Beendigung des Prozesses erforderlich ist. Ich kenne den Lebenszyklus von iOS-Apps nicht.

Im Moment sind die oben beschriebenen Lösungen die besten: Hängen Sie sich an native Methoden und speichern Sie selektiv den Zustand Ihrer App, damit Sie sie später wiederherstellen können. Eine Framework-unterstützte automatische Lösung sowohl auf iOS als auch auf Android ist noch weit entfernt.

Es wurde keine Lösung beschrieben, anstatt die entsprechende Android-Einrichtung zum Beibehalten des Zustands für die Fälle zu bestätigen, in denen die Laufzeit beschließt, den Prozess zu beenden.

Es wurde keine Lösung beschrieben, anstatt die entsprechende Android-Einrichtung zum Beibehalten des Zustands für die Fälle zu bestätigen, in denen die Laufzeit beschließt, den Prozess zu beenden.

Ich habe irgendwie beschrieben, was wir tun können, aber ich weiß nicht genug über Flutter, um Ihnen zu sagen, ob es das unterstützt, was dafür notwendig ist (die Möglichkeit, jederzeit einen beliebigen Navigationsverlauf einzurichten 😄 )

@Zhuinden Es gibt eine "Zillion" Randfälle, die von onSaveInstanceState abgedeckt werden, wenn es um Aktivitäten / Kontrollen geht ... vertrauen Sie mir ... aber Sie sind herzlich eingeladen, dies als Schulungsübung zu untersuchen.

... und um meine Position zu wiederholen: Android-Aufgaben/Aktivitäten, Dienste, Anwendungslebenszyklus (einschließlich onSaveInstanceState) sind alle Teil des Android-Programmiervertrags "übrigens" sie werden zuerst in Java realisiert, aber das Wichtigste ist, dass sie es sind Mittel zum Auferlegen von Einschränkungen auf Systemebene für den App-Prozess und da Flutter-Code innerhalb eines Android-App-Prozesses lebt, wäre es unklug von Flutter, diese Einschränkungen zu umgehen.

Liebe Android-Entwickler,

vielen Dank für Ihre Leidenschaft, Ihren Benutzern die beste Benutzererfahrung zu bieten, indem Sie den Status nicht nur bei Konfigurationsänderungen, sondern auch während des Prozessabbruchs speichern. Sie haben vielleicht in den 200 Kommentaren oben gelesen, dass Flutter Schwierigkeiten hat, den Anwendungsstatus zu speichern. Ich möchte Ihnen sagen, warum und wie Sie es lösen können .

Warum kann ich onSavedInstanceState ?

onSavedInstanceState erfordert, dass Entwickler den App-Status synchron speichern. Sie müssen sofort zurückkehren. Aber die Kommunikation mit Flutter ist standardmäßig asynchron ( Future ). Daher können Sie den gespeicherten Status nicht von Ihrer Flutter-App anfordern, wenn Android danach fragt.
_Aber Sie könnten den gespeicherten Zustand zurückgeben, wenn Sie ihn vorher erstellt hätten, nicht wahr?_

Das Problem ist nicht, dass auf onSavedInstanceState zugegriffen werden kann, das Problem ist das Timing. Wenn Android zum Speichern des Status auffordert, müssen Sie keine Daten eingeben.

Wiederherstellungslösung A: Ihre eigene gespeicherte Instanzstatusdatei

Du könntest onSavedInstanceState anhören und deine Flatter-App über dieses Ereignis informieren. Speichern Sie dann Ihren App-Status und schreiben Sie ihn in ein file . Wenn Ihre App nach dem Prozesstod wiederhergestellt wird (Sie können dies über savedInstanceState auf der Android-Seite erkennen), laden Sie die Datei aus dem Dateisystem, analysieren Sie sie und stellen Sie Ihren Zustand wieder her. (Tipp: Verwenden Sie Plattformkanäle, sie sind einfacher als Sie vielleicht denken).

Sie können sogar den Status wiederherstellen, wenn Benutzer die App beendet haben, oder den gespeicherten Status verwerfen, wenn er viel zu alt ist!

Wiederherstellungslösung B: Status kontinuierlich speichern

Wir könnten einen Timer ausführen und den App-Status auf der Plattformseite kontinuierlich im Speicher speichern. Sagen wir alle 3 Sekunden. Wenn Android dann onSavedInstanceState aufruft, können wir den zuvor gespeicherten Zustand zurückgeben. (Ja, wir können Änderungen verlieren, die in den letzten 3 Sekunden stattgefunden haben.)

Wiederherstellungslösung C: Zustand an wichtigen Punkten speichern

Wie Lösung B wird der gespeicherte Zustand auf der Plattformseite im Speicher gehalten. Aber anstatt den Status alle paar Sekunden zu speichern, fordern Sie manuell an, den Status zu speichern, nachdem Benutzer einige Aktionen ausgeführt haben. (öffnete eine Schublade, gab Text in TextField , wie Sie es nennen).


Alle 3 Lösungen sind kombinierbar, verwenden Sie das, was am besten zu Ihrer App passt.

Aber wie kann ich meinen App-Status speichern?

Im Gegensatz zu Android View s haben Widget s keinen onSaveInstanceState Rückruf. Der Widget-Baum wird nicht automatisch gespeichert. Und das ist gut so! Benutzeroberfläche und Status werden endlich getrennt und es gibt keine Reflexionsmagie beim Wiederherstellen des Ansichtsstatus oder beim Instanziieren von Aktivitäten und Fragmenten.

Um den Status zu speichern, müssen Sie jeden Bildschirm in Ihrer App durchlaufen und alle Informationen speichern, die zum Wiederherstellen erforderlich sind. Ich mache dies gerne in json, aber Sie können protobuf für eine bessere Leistung wählen (Messen Sie vor der Optimierung!).

Wenn Sie sich unsere beliebte Probenzähler-App ansehen, kann dies ganz einfach sein:

// complete app state of sample application
{
  "counter": 24,
}

Wenn Sie mehrere Bildschirme oder Dialogfelder haben, erhalten Sie möglicherweise den folgenden App-Status json:

{
    "currentRoute": "/todo-detail/241",
    "routes": {
        "todo-detail/241": {
            "edited": "true",
            "title": "Buy bananas",
            "picture": "http://....",
            "show_confirm_delete_dialog": "false"
        },
        "todo-list": {
            "current_scroll_position": "365",
            "filter": "Filter.TODAY"
        }
    }
}

Die tatsächliche Implementierung hängt stark von Ihrer App-Architektur ab.

  • Wenn Sie Redux verwenden, können Sie eine Middleware verwenden, um den globalen App-Status im laufenden Betrieb beizubehalten.
  • Wenn Sie scoped_model es möglicherweise aus, die Modelle zu speichern. Sie neu zu erstellen unterscheidet sich grundlegend von der Wiederherstellung eines Redux-Zustands.

Die besten Tipps, die ich dir geben kann:

  • Halten Sie eine strikte Trennung zwischen Ihrer Benutzeroberfläche und Ihren Modellen.
  • Schauen Sie sich jeden Bildschirm an und fragen Sie sich, was Sie wirklich brauchen, um den Zustand wiederherzustellen. Meist ist es wirklich nicht viel.
  • Sprechen Sie mit Ihrem iOS colleagues und es wird Ihnen sagen, dass es seit Jahren keine Zustandswiederherstellung über encodeRestorableState implementiert hat. Android 1M+ vs. iOS 974

Pascal

Aktivieren Sie die Einstellung "Aktivitäten nicht behalten".

Nein, dies testet nicht richtig gegen den Prozesstod. Dies geschieht NIE, es sei denn, Sie aktivieren diese Einstellung explizit. 😞

Wenn Sie mehrere Bildschirme oder Dialogfelder haben, erhalten Sie möglicherweise den folgenden App-Status json:

{
  "currentRoute": "/todo-detail/241",
  "routes": {
      "todo-detail/241": {
          "edited": "true",
          "title": "Buy bananas",
          "picture": "http://....",
          "show_confirm_delete_dialog": "false"
      },
      "todo-list": {
          "current_scroll_position": "365",
          "filter": "Filter.TODAY"
      }
  }
 }

Oh Junge, wir erreichen den "Navigationszustand ist Teil des Anwendungszustands als Zustand einer endlichen Zustandsmaschine", damit dies funktioniert, aber ich mag das Aussehen

Oh Junge, wir erreichen den "Navigationszustand ist Teil des Anwendungszustands als Zustand einer endlichen Zustandsmaschine", damit dies funktioniert

lass mich nicht anfangen.

Der Rest des App-Zustands sollte in einer Datenbank/auf dem Server gespeichert werden. Der einzige vorübergehende Zustand, der es wert ist, in onSaveInstanceState gespeichert zu werden, ist der Navigationsstapel und einige Benutzereingaben. Das fordern auch die meisten Leute in diesem Thread.

Der Rest des App-Zustands sollte in einer Datenbank/auf dem Server gespeichert werden. Der einzige vorübergehende Zustand, der es wert ist, in onSaveInstanceState gespeichert zu werden, ist der Navigationsstapel und einige Benutzereingaben. Das fordern auch die meisten Leute in diesem Thread.

und das ist SCHLÜSSEL! (sowie „Android Service“-Unterstützung)

@gitspeak , der letzte Austausch, den wir hatten, hat nichts mit diesem Problem zu tun. Ich werde beides verstecken. Wenn Sie die Diskussion fortsetzen möchten, senden Sie mir bitte eine PN mit meinem Github-Handle @ gmail.com. Hier liegt eindeutig ein Missverständnis vor, das den Rahmen dieser Ausgabe sprengt.

Als einer der Entwickler, die an der Schnittstelle zwischen Flutter und Android arbeiten, möchte ich @passsy für die Bereitstellung eines großartigen Überblicks über die Probleme beim Speichern des Status danken.

Ich möchte auch hinzufügen, dass ich daran interessiert bin, spezifische Anwendungsfälle zu verstehen, mit denen sich Entwickler in diesem Thread befassen. Zustand speichern ist ein Konzept, das wahrscheinlich viele verschiedene Ziele umfasst. Was eine App als Zustand bezeichnet, tut die andere nicht. Eine App muss unbedingt einen garantierten gespeicherten Zustand haben, während eine andere einfach einen bestimmten Zustand speichern würde, aber ohne ihn nicht kaputt geht. Einige Apps werden vollständig mit Flutter erstellt, andere Apps müssen Informationen zwischen der traditionellen Android-/iOS-Benutzeroberfläche und Flutter synchronisieren.

Wenn einer von Ihnen den speziellen Anwendungsfall beschreiben möchte, den Ihre App in Bezug auf den Speicherstatus unterstützen muss, senden Sie mir bitte eine E-Mail an stelle aktiv Anwendungsfälle zusammen.

@passsy Bitte betrachten Sie meine Kommentare unten als aufrichtigen Versuch, konstruktives Feedback zu geben

Wiederherstellungslösung A: Ihre eigene gespeicherte Instanzstatusdatei
Sie können onSavedInstanceState anhören und Ihre Flatter-App über dieses Ereignis informieren. Speichern Sie dann Ihren App-Status und schreiben Sie ihn in eine Datei. Wenn Ihre App nach dem Prozesstod wiederhergestellt wird (Sie können dies über savedInstanceState auf der Android-Seite erkennen), laden Sie die Datei aus dem Dateisystem, analysieren Sie sie und stellen Sie Ihren Status wieder her. (Tipp: Verwenden Sie Plattformkanäle, sie sind einfacher als Sie vielleicht denken).
Sie können sogar den Status wiederherstellen, wenn Benutzer die App beendet haben, oder den gespeicherten Status verwerfen, wenn er viel zu alt ist!

Mehrere Probleme:

1) Bei diesem Ansatz gibt es eine inhärente Race-Condition. Der Thread "state persisting" kann den Main-Thread der Android-App sehr gut überdauern, der die Lebenszyklusereignisse aufruft. In diesem Fall kann der Prozess beendet werden, während die Datei geschrieben wird. Sie können versuchen, die beiden Threads zu koordinieren, indem Sie in einer Lebenszyklusmethode ein Abschlusssignal vom "Zustandsspeicher"-Thread blockieren, aber dies kann nicht nur eine zusätzliche Komplexität der Synchronisation gewährleisten, da eine ausreichend lange Verzögerung zu ANR führen kann. (https://developer.android.com/topic/performance/vitals/anr)
Sie könnten argumentieren, dass das "Speichern des Zustands" in einer Datei so schnell ist, dass dies unwahrscheinlich ist, aber bedenken Sie tatsächliche Verzögerungen bei der Thread-Planung und der Speicherlatenz, während andere Prozesse ausgeführt werden, z. B. Hintergrunddownloads, Play Store-Updates, Media Player usw Ich möchte nicht darüber diskutieren, aber meine persönliche Erfahrung (die von anderen geteilt wird) ist, dass Android-Geräte anscheinend nicht immun gegen dieselbe "Grippe" sind, die Windows-PCs infiziert - sie werden auch mit der Zeit langsamer. Ich habe gerade mein Nexus 7 sauber gemacht und es dauerte nicht nur STUNDEN, um alle Apps über Wifi herunterzuladen und zu installieren, sondern die Reaktionsfähigkeit des Geräts ist jetzt ein kompletter Witz, der darauf hindeutet, dass ich bezweifle, dass ich Geduld habe, es sogar zum Nachschlagen von Rezepten zu verwenden.

2) Sie können keine zuverlässige Definition von "viel zu alt" haben. Wenn ich die App aus dem Task-Manager verwerfe, erwarte ich, dass der gesamte Status sofort verschwindet, während ich beim Wechsel zu einer anderen App erwarte, dass der Status auf unbestimmte Zeit erhalten bleibt, solange das Telefon eingeschaltet ist. Der Punkt ist, Android bietet Ihnen keine Möglichkeit, zwischen diesen beiden Zuständen zu unterscheiden.

3) Nicht alle UI-Steuerelemente legen ihren Zustand über eine API offen, die es dem Benutzer ermöglicht, das Steuerelement für einen bestimmten Zustand zu konfigurieren. Eine solche API würde zweifellos zusätzlichen Entwicklungsaufwand seitens des Steuerungsentwicklers verursachen. Auf Android wird erwartet, dass gut verhaltene UI-Steuerelemente "zustandserhaltende" Probleme vom Entwickler abstrahieren, indem sie onSave/Restore InstanceState-Benachrichtigungen intern verarbeiten und so steuern, welcher Zustand gegebenenfalls erhalten bleibt. Wenn ich mich richtig erinnere, behält das AutoVervollständigen-Steuerelement den Abfragebegriff, aber nicht die Ergebnisliste bei.

Wiederherstellungslösung B: Status kontinuierlich speichern
Wir könnten einen Timer ausführen und den App-Status auf der Plattformseite kontinuierlich im Speicher speichern. Sagen wir alle 3 Sekunden. Wenn Android dann onSavedInstanceState aufruft, können wir den zuvor gespeicherten Zustand zurückgeben. (Ja, wir können Änderungen verlieren, die in den letzten 3 Sekunden stattgefunden haben.)

Sehr anderer Meinung, abgesehen davon, dass die gleichen Probleme mit "Lösung A" auftreten, besteht ein neues Risiko, eine Dose mit Datenduplizierungswürmern zu öffnen.

Wiederherstellungslösung C: Zustand an wichtigen Punkten speichern
Wie Lösung B wird der gespeicherte Zustand auf der Plattformseite im Speicher gehalten. Aber anstatt den Status alle paar Sekunden zu speichern, fordern Sie manuell an, den Status zu speichern, nachdem Benutzer einige Aktionen ausgeführt haben. (eine Schublade geöffnet, Text in ein TextField eingegeben, was auch immer).

Habe die gleichen Probleme rund um "Lösung A".

Der Widget-Baum wird nicht automatisch gespeichert. Und das ist gut so! Benutzeroberfläche und Status werden endlich getrennt und es gibt keine Reflexionsmagie beim Wiederherstellen des Ansichtsstatus oder beim Instanziieren von Aktivitäten und Fragmenten.

Verschiedener Meinung sein. Androids automatische Beibehaltung des Widget-Baums in den Fällen, in denen sein Zustand geworfen werden kann, ist sehr hilfreich! Warum um alles in der Welt möchten Sie diese Verantwortung vollständig auf den App-Entwickler übertragen? Warum steht dies im Gegensatz zur UI-Zustandstrennung?? Tatsächlich scheint diese automatische Methode zum Speichern / Wiederherstellen des Widget-Baums ein perfektes Beispiel für die Trennung von Benutzeroberfläche und Status zu sein.

Die "Lösung A" erscheint mir bei Verwendung entsprechender Sperren durchaus plausibel, und bei vertretbarem Aufwand sollte kein ANR ausgelöst werden. Außerdem wird durch den Benachrichtigungsmechanismus der Plattform vermieden, dass ständig unnötige Serialisierungsarbeiten durchgeführt werden, die für jeden einzelnen Tastendruck teuer sein können.

Über die Möglichkeit, dieses Verhalten zu automatisieren, scheint mir klar, dass dies ohne Reflexionsmechanismus in der Dart-Laufzeit und ohne strukturierte Möglichkeit, den Zustand in Flutter im Speicher zu speichern, auf keinen Fall möglich ist. Im Moment ist der einfachste Ansatz, den ich mir vorstellen kann, dies zu tun, um den Status in einem einzelnen dynamischen Objekt zu behalten (genau wie this.props und this.state bei React).

@aletorrado

Die "Lösung A" erscheint mir bei Verwendung entsprechender Sperren durchaus plausibel, und bei vertretbarem Aufwand sollte kein ANR ausgelöst werden.

Ich stimme zu. wenn ... Wo das Android-Modell viel einfacher und robuster ist, da es nicht erfordert, dass der Entwickler mit Sperren umgeht, behält es den vorübergehenden Zustand auf der Festplatte nicht bei und behält den Zustand nur in den Fällen bei, in denen es für Sie relevant ist (flattern) kann nicht auf die Einbindung in die Android-Lebenszyklusereignisse und die onSaveInstance-Funktion verzichten - zunächst einmal, wozu ist es gut?

Außerdem wird durch den Benachrichtigungsmechanismus der Plattform vermieden, dass ständig unnötige Serialisierungsarbeiten durchgeführt werden, die für jeden einzelnen Tastendruck teuer sein können.

Es ist nicht erforderlich, jeden Tastendruck beizubehalten, sondern eine Momentaufnahme bestehend aus ausgewählten Zuständen

Die Daten sollten nur im Bundle als ByteArray gespeichert werden, um dies zu vermeiden
Auslösen von ANR oder Verzögerung, die durch E/A im Hauptthread verursacht werden.

Am Mi., 19. Dez. 2018, 18:08 Alejandro Torrado [email protected]
schrieb:

Die "Lösung A" erscheint mir bei entsprechender Verwendung durchaus plausibel
Sperren, und es sollte kein ANR ausgelöst werden, wenn der Arbeitsaufwand vertretbar ist.
Durch die Nutzung des Benachrichtigungsmechanismus der Plattform wird dies auch vermieden
unnötige Serialisierungsarbeit die ganze Zeit, die teuer sein kann
für jeden einzelnen Tastendruck.

Über die Möglichkeit, dieses Verhalten zu automatisieren, scheint mir klar, dass
ohne Reflexionsmechanismus in der Dart-Laufzeit und ohne
strukturierte Möglichkeit, den Zustand in Flutter im Speicher zu speichern, das ist nicht möglich
auf jeden Fall. Im Moment der einfachste Ansatz, den ich mir vorstellen kann
Dies wäre die Option, den Status in einem einzelnen dynamischen Objekt beizubehalten (genau wie
this.props und this.state auf React).


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/flutter/flutter/issues/6827#issuecomment-448671264 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AGpvBUQk17YOsjLMHTWcdJHL9rR71u6lks5u6nKGgaJpZM4KwSuX
.

Der wahre Trick besteht darin, dieses ByteArray aufzubauen, von dem Sie sprechen :slightly_smiling_face:

Ich würde eher sagen, dass der wahre Trick darin besteht, dies wiederherstellen zu können
ByteArray richtig.

Am Mittwoch, den 19. Dezember 2018, 18:41 Uhr schrieb Gabor Varadi [email protected] :

Der wahre Trick besteht darin, dieses ByteArray aufzubauen, von dem Sie sprechen 🙂


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/flutter/flutter/issues/6827#issuecomment-448682229 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AGpvBep8AW2xkKECt6uyTjqRGwri9Pmoks5u6npKgaJpZM4KwSuX
.

Nein. Sobald Sie das Byte-Array haben, ist es sehr einfach. Zeigen Sie während des Ladens einfach eine Ladeanzeige an (vorausgesetzt, Sie haben sich bei den Kanälen gesagt, dass Sie sie laden müssen).

Das eigentliche Problem ist die Flutter-Integration, was ich den Wiederherstellungsteil nenne.
Die Navigation sollte automatisch gespeichert werden und auch Benutzereingaben, genau wie auf
native Android-Apps.

Aufgrund dieses Problems können Flutter-Apps nicht korrekt in Android integriert werden, da
sie verhalten sich nicht richtig, wenn RAM-Druck herrscht und der Benutzer wechselt
Apps unter diesen Bedingungen, bevor Sie zurückkommen.

Am Do, 20.12.2018, 02:42 AM schrieb Gabor Varadi [email protected] :

Nein. Sobald Sie das Byte-Array haben, ist es sehr einfach. Zeigen Sie einfach eine Ladung an
Indikator, während Sie es laden (vorausgesetzt, Sie haben es sich selbst gesagt mit
Kanäle, die Sie zum Laden benötigen).


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/flutter/flutter/issues/6827#issuecomment-448827537 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AGpvBR4rmSxelodAjZvcQ6kOwPd7yxKPks5u6ushgaJpZM4KwSuX
.

Ein paar von uns haben hier ein bisschen mehr darüber diskutiert: https://github.com/mehmetf/flutter/issues/1. Relevante Punkte zu diesem Thema:

  • Zur Korrektur (https://github.com/flutter/flutter/issues/6827#issuecomment-447488177): Google Ads wurde mit aktivierter Option "Aktivitäten nicht beibehalten" getestet. Der App-Prozess stirbt in diesem Fall nicht ab, daher steht meine Erklärung unter (https://github.com/flutter/flutter/issues/6827#issuecomment-447955562). Wie @gitspeak und @Zhuinden darauf hingewiesen haben, ist dies keine gute Möglichkeit, die Szenarien zu testen, vor denen wir uns schützen, da dies in der Praxis nicht funktioniert.

  • Immer wenn die Anwendung im Hintergrund läuft, wird View.onSaveInstanceState im UI-Thread für jede Ansicht in der Hierarchie aufgerufen, für die das Speichern aktiviert ist. Eine gute Lösung aus Entwicklersicht wäre also, dass FlutterView implementiert. Um Race-Bedingungen mit dem UI-Thread zu verhindern (die Sie nicht blockieren können/sollten), sollte diese Methode nicht in die Flutter-Anwendung eindringen, da es keine synchrone Möglichkeit gibt, dies zu tun.

  • Was auch immer onSaveInstanceState speichert, darf nicht größer als 1 MB sein, sodass das Speichern des gesamten Isolate-Status nicht in Frage kommt. Stattdessen können wir möglicherweise einen Weg finden, den Code, die Assets und die Daten, die im Isolatorspeicher enthalten sind, aufzuteilen und nur den serialisierten Datenteil in eine parzellierbare Datei zu kopieren.

  • Wenn die Ansicht später wiederhergestellt wird, erstellt FlutterView ein Isolat und entpackt den zuvor gespeicherten Datenteil, falls vorhanden.

  • Wenn die App im Hintergrund ist, sollte sich der Zustand auf der Flutter-Seite vermutlich nicht mehr ändern. Es wird immer Eckfälle geben, daher wäre es schön, diese Lösung mit einer Liste von Best Practices für Flutter-Apps zu begleiten (in Anbetracht des Lebenszyklus und des Speicherdrucks ).

  • Ich könnte mir vorstellen, dass dies in Schwellenländern wie Indien, in denen Flutter voraussichtlich auf Low-End-Telefonen läuft, ein weitaus dringenderes Problem darstellt. Es wird auch gemunkelt, dass

  • Sie erstellen und pflegen ihr Haupt-Isolat, wenn die _Anwendung_ startet (nicht die Aktivität). Dann übergeben sie dieses Isolat an die Flutter-Ansicht, wenn sie erstellt wird.

können Sie ein Beispiel für diesen Code geben?

Sie erstellen und pflegen ihr Hauptisolat beim Start der Anwendung (nicht der Aktivität). Dann übergeben sie dieses Isolat an die Flutter-Ansicht, wenn sie erstellt wird.

können Sie ein Beispiel für diesen Code geben?

Ich kann kein Beispiel zum Kopieren/Einfügen bereitstellen, aber Folgendes passiert:

Anwendung

  • Rufen Sie in Ihrem Application.onCreate Initialisierungsfunktionen für FlutterMain . ( startInitialization und ensureInitializationComplete ). Hinweise dazu finden Sie im Flatter/Engine-Repo.
  • Erstellen Sie ein neues FlutterNativeView-Objekt, übergeben Sie die Application-Instanz als Kontext.
  • Führen Sie Ihr Bundle in dieser nativen Ansicht aus (Suchen Sie nach Beispielen für runFromBundle ).
  • Implementieren Sie eine Schnittstelle in dieser Anwendung, mit der Sie diese native Ansicht zurückgeben können (etwa FlutterNativeViewProvider ).

Aktivität

  • Erweitern Sie FlutterFragmentActivity und überschreiben Sie diese beiden Methoden:
  <strong i="26">@Override</strong>
  public FlutterNativeView createFlutterNativeView() {
    FlutterNativeViewProvider provider = (FlutterNativeViewProvider) getApplication();
    return provider.getFlutterNativeView();
  }

  <strong i="27">@Override</strong>
  public boolean retainFlutterNativeView() {
    return true;
  }

Etwas in dieser Richtung sollte Sie in Schwung bringen. Beachten Sie, dass Ihre Flutter-Anwendung ausgeführt wird, bevor eine Aktivität (und damit ein Fenster) verfügbar ist. Daher sollten Sie runApp() in Ihrer Hauptfunktion erst aufrufen, wenn Sie ein Signal von Activity erhalten, dass sie bereit ist. Sie können dies über PlatformChannels und einen Ort tun, um dies in createFlutterNativeView() zu signalisieren.

Haftungsausschluss(e)

  1. Wie ich oben erwähnt habe, dulden wir dieses Muster nicht ganz, da es dieses spezielle Problem nicht wirklich löst und ziemlich chaotisch ist.

  2. Beachten Sie, dass viele dieser APIs geändert werden sollen. Da sich add2app-Anwendungsfälle noch weiterentwickeln, sind die Android- und iOS-Einbettungs-APIs noch nicht ganz stabil. Wir werden wie gewohnt Breaking Changes auf flatter-dev@ bekannt geben.

@passsy , @gitspeak

Wiederherstellungslösung A: Ihre eigene gespeicherte Instanzstatusdatei

Um sicherzustellen, dass Sie eine gültige Statusdatei haben, können Sie in eine .tmp-Datei schreiben und diese dann umbenennen, wenn das Schreiben abgeschlossen ist. Wenn Sie auch die vorherige Statusdatei behalten, erhalten Sie immer einen gültigen Status. Auch wenn der Prozess beim Speichern des Zustands beendet wird.

@jsroest

Sie werden immer mit einem gültigen Zustand enden.

Aber nicht unbedingt der richtige Zustand, was der springende Punkt ist..

Es ist auch nicht erforderlich, dass der Zustand des nichtflüchtigen Speichers mit ..

@gitspeak

Sie werden immer mit einem gültigen Zustand enden.

Aber nicht unbedingt der richtige Zustand, was der springende Punkt ist..

Es ist auch nicht erforderlich, dass der Zustand des nichtflüchtigen Speichers mit ..

In vielen Situationen ist es wichtiger, einen konsistenten Zustand zu haben, als „die meisten der neuesten Werte“ zu haben. Ein Beispiel dafür sind relationale Datenbanken, die bei Transaktionen verwendet werden. Was meiner Meinung nach auch ein großartiger Ort ist, um den Zustand zu speichern.

Das Beharren auf nichtflüchtigem Speicher hat einige Vorteile; Der Prozess wird auch bei „Telefonabbrüchen“, Batteriewechseln, Hardwarefehlern, Betriebssystemaktualisierungen, Neustarts im Allgemeinen usw. beendet, sodass der Zustand beim Speichern im nichtflüchtigen Speicher bestehen bleibt.

@jsroest Ihre Argumente sind gültig, aber IMHO nicht auf dieses spezielle Problem anwendbar, da der "Zustand", auf den hier vorübergehender UI-Zustand ist (z. B. Scroll-Position, aktive Auswahl(en), temporäre Texteingabe usw.). Beachten Sie, dass die Wiederherstellung dieser Art von Daten nur in Situationen erforderlich ist, in denen der Benutzer den "Bildschirm" nicht explizit verworfen hat, aber Benutzereingaben können dennoch verloren gehen, z. B. wenn ein Benutzer zu einer anderen App wechselt. Es gibt keine Benutzererwartung, den Übergangszustand bei einem Stromausfall wiederherzustellen, so wie es auch keine Benutzererwartung gibt, dass der Browser die zuletzt angezeigte Webseite bis zum genauen Offset vor einem Systemstromausfall scrollt. Dabei ist die Art der Daten, die als "transient state" angesehen werden, von Bedeutung, und die Diskussion hier (insbesondere Android onSaveInsatnceState) dreht sich um den Umgang mit dieser Art von Daten.

@gitspeak
Ich schreibe Programme für das Unternehmen. Zum Beispiel Programme, die in Lagerhallen von Kommissionierern verwendet werden, oder andere Beispiele im Einzelhandel, wo der Ladenbesitzer Artikel scannt, um sie bei seinem Lieferanten zu bestellen. Meine Kunden erwarten, dass die Anwendung dort beginnt, wo sie sie verlassen hat, egal was passiert ist. Das beinhaltet den Transienten-Zustand, aber auch den Navigations-Stack. Könnten Sie erklären, warum dies nicht perfekt für die hier besprochene Funktion OnSaveInstanceState geeignet ist? Vielleicht ist die Benutzererwartung auf dem Verbrauchermarkt anders, aber die gleiche Logik zum Speichern und Wiederherstellen des Zustands kann IMHO verwendet werden.

@jsroest

Meine Kunden erwarten, dass die Anwendung dort beginnt, wo sie sie verlassen hat, egal was passiert ist.

Tut mir leid, ich weiß nicht, wie man Anwendungen für solche Kunden erstellt, vielleicht kann jemand anderes helfen ...

@jsroest Das ist eine großartige Frage, liegt aber nicht im Rahmen dieses Problems. Flutter wird durch die Möglichkeiten der Plattform selbst begrenzt, und wie onSaveInstanceState um das Speichern eines vorübergehenden UI-Zustands, bei dem der Datenlebenszyklus anders ist als gewünscht. Lokaler Speicher oder Remote-Speicher (wenn Sie möchten, dass der Zustand über Deinstallationen und Geräte hinweg überlebt) würde Ihnen das geben, was Sie wollen, aber es gibt Vorbehalte, die wiederum außerhalb des Umfangs dieses Problems liegen.

Weitere empfehlenswerte Lektüre:

https://developer.android.com/topic/libraries/architecture/saving-states [Beachten Sie zum Beispiel, wie dieser Artikel den lokalen Speicher von onSaveInstanceState trennt]
https://developer.android.com/guide/topics/data/data-storage
https://firebase.google.com/docs/firestore/

@jsroest Wenn Ihre Kunden erwarten, dass die App auch wenn sie zwangsweise gestoppt, Aufgaben gelöscht oder das Betriebssystem neu gestartet wurde , dann ist das etwas völlig Eigenes und nicht das, worüber die Leute hier sprechen.

onSaveInstanceState hat den Aufgabenstatus für eine bestimmte Aufgabe gespeichert, aber er wurde beim Löschen der Aufgabe oder beim Erzwingen des Stoppens oder Neustarts des Betriebssystems gelöscht.

@mehmetf Ich denke, dieses Thema hat in der Tat einen Grad an Ausführlichkeit erreicht, bei dem es ziemlich schwierig sein kann, zu groken. Mit dem Segen von

Ich stimme zu, aber ich überlasse das @matthew-carroll; er wird wahrscheinlich dieses Problem besitzen.

Danke für die Rückmeldung.

Ich möchte die Dinge nicht vorantreiben, aber ich denke, ich habe nicht klar genug beschrieben, wonach ich suche. Ich kenne nicht alle Begriffe, die hier in diesem Thread verwendet werden. Ich sehe eine große Ähnlichkeit zwischen dem, was ich in Flutter sehen möchte, und dem, was @LouisCAD und @passsy beschreiben.

Meine Programme bestehen in der Regel aus folgenden Teilen:

  • Backend zum Pushen von Daten aus dem stationären Zustand, wenn der Benutzer mit einem Job fertig ist.
  • Lokale SQLite-Datenbank für den stationären Zustand
  • Im Speicher als „Variablen“ bezeichnete Datenstruktur für den Übergangszustand, die in den nichtflüchtigen Speicher serialisiert werden kann, damit sie den Prozess überdauern kann.

Die Datenstruktur 'Variablen' hat nur einfache Klassen mit Eigenschaften, die serialisierbar sind. Ein wichtiger Teil dieser 'Variablen'-Datenstruktur ist der Screenstack. (Einfach auflisten). Der obere Bildschirm ist der letzte Bildschirm, mit dem der Benutzer interagiert hat. Die folgende ist diejenige, zu der der Benutzer navigiert, wenn er auf "Zurück" drückt.

Ich kann mir vorstellen, dass der OnSaveInstanceState auf der Android-Plattform verwendet werden kann, um eine Serialisierung dieser Datenstruktur auszulösen. Ähnliches muss auf den anderen Plattformen (iOS, zukünftig: Win, Mac, Web) gefunden werden, aber wie @passy vorschlägt, können auch andere Schlüsselpunkte dies auslösen und das kann gut genug sein.

Wenn das Programm startet, prüft es, ob eine serialisierte Datenstruktur verfügbar ist und ob die aktuelle Version des Programms mit der Version übereinstimmt, die es serialisiert hat. Dies kann nur durch Vergleichen der Versionsnummern erfolgen. Wir möchten keine Datenstruktur laden, die nicht kompatibel ist.

Wenn sich dies alles summiert, ist die Datenstruktur deserialisiert und im Speicher verfügbar. Das Programm lädt den Bildschirm, der sich oben auf dem Stapel befindet. Der Bildschirm selbst lädt seinen Übergangszustand aus der Datenstruktur. Jetzt haben wir ein Programm, das Prozesstote überlebt. (Oder übersehe ich etwas? Dies funktioniert definitiv bei meinen früheren Projekten auf Windows Mobile, Xamarin Forms und asp.net. Ich denke, es sollte auch in Flutter funktionieren).

Anwendungsbeispiel, wobei SXXX für einen Bildschirm mit einer Zahl steht. Die Nummer wird nur verwendet, um den Programmierer daran zu erinnern, welche Bildschirme mehr oder weniger zusammengehören.

S100 Main menu
  S200 Order pick
    S210 Select order
      S220 Confirm pick location
        S230 Pick nr of pieces
          S240 Report shortage
        S250 Scan dropoff location
    S300 Cycle count
      S210 XXXXXX
        S220 XXXXXX
      S230 XXXXXX
    S300 Reprint labels
      S310 XXXXXX

Beispielvariablen Datenstruktur

Class variables {
    Class AmbientVariables {
        ….
    }

    Class ScreenStack{
        List<string> ScreenStack;
    }

    Class Orderpick {
        int selected_order;
        string comfirmed_location;
        string nr_picked;
        ….
    }

    Class CycleCount {
        ….
    }

    Class ReprintLabels {
        ….
    }
}

Alles, was ich gerne in Flattern sehen würde, ist wahrscheinlich schon da, außer der Nachbildung des Screenstack aka Navigationstack. Ich habe keine Ahnung, wie man diesen Objektbaum im Speicher neu erstellt. Ich bin mir auch nicht sicher, ob ich es nachbauen soll. Ich kann auch einen eigenen Navigationsstack erstellen und diesen in flattern als Single-Page-Anwendung implementieren. Genauso wie ich es in früheren Projekten gemacht habe. Aber dann würde ich viele eingebaute Goodies aus den MaterialApp- und Scaffold-Klassen verlieren, wo der Zurück-Pfeil / die Schaltfläche am offensichtlichsten ist.

Mir ist klar, dass dies nicht automatisch alle Übergangszustände speichert. Zum Beispiel ausgewählte Texte, Position von Listen, Position einer bestimmten Animation. Aber als Programmierer können Sie pro Bildschirm entscheiden, was gespeichert werden soll. Obwohl @LouisCAD genau das zu verhindern versucht, weil der gesamte Boilerplate-Code benötigt wird.

Soll ich ein neues Thema machen oder passt das in diesen Thread?

Nochmals vielen Dank für das Feedback, das freut mich sehr.

@jsroest Danke für die ausführliche Erklärung. Bitte poste dies auf StackOverflow mit dem Flatter-Tag. Sie fragen im Wesentlichen nach einer Empfehlung, wie Sie Ihre Routen strukturieren und aufbauen und wie Sie die zuletzt besuchte Route im persistenten Speicher speichern.

Sie könnten denken, dass die Lösung dieses speziellen Problems auch Ihr Problem löst, aber das ist nicht wahr. Sie können mir eine PN an meinem Github-Handle @ gmail.com senden, wenn Sie nicht verstehen, warum.

Gibt es Fortschritte zu diesem Thema?

@vanlooverenkoen theoretisch, wenn Sie die beibehalten und löschen, wenn Sie auf der nativen Android-Seite savedInstanceState == null , dann könnten Sie es möglicherweise schaffen dauerhaft und wiederherstellbar, es ist einfach nicht so einfach.

@Zhuinden Es ist eigentlich viel komplexer, als nur die Routen und Schlüssel zu speichern. Die Kommunikation zwischen den Bildschirmen erfolgt entweder über Rückrufe oder durch Warten auf ein Ergebnis. Sie müssten diese Zustände auch irgendwie wiederherstellen. Andernfalls haben Sie am Ende etwas, das richtig aussieht, aber nicht so wirkt. Sie könnten die Wiederherstellung von Rückrufen und dergleichen umgehen, indem Sie eine Form der Kommunikation verwenden, die Datenbündel verwendet, genau wie Sie es mit nativem Android (mit Absichten) tun, aber das würde Flutter viel Benutzerfreundlichkeit nehmen. Solange es keine wirkliche Lösung für dieses Problem gibt, ist Flutter für viele Apps nicht verwendbar, hauptsächlich für solche, die zum Sammeln großer und komplexer formularbasierter Daten verwendet werden. Ich frage mich, warum dieses Problem vom Flutter-Team nicht offen angesprochen wird, da es sich dessen sicherlich bewusst sein muss. Vielleicht würde es einen gravierenden Fehler in der Flutter-Architektur aufdecken und zeigen, dass das System jenseits von auffälligen Präsentationen bis hin zum Eindrucksmanagement keine gute Option für reale Anwendungen ist.

Ich habe dies von Anfang an verfolgt, musste aber eine Reihe von Nachrichten erneut lesen, da ich den Kontext verloren habe.

Wenn das Flutter-Team der Meinung ist, dass es eine neue, frische Ausgabe geben sollte, mit weniger Flut und klareren Plänen, habe ich kein Problem damit und diese wird daher geschlossen.

Ich habe das native_state-Plugin erstellt , um die Android- und iOS-Mechanismen zum Speichern des App-Status zu nutzen, wenn der Prozess beendet wird.

Die Navigator können die App-Routen bereits durch die initialRoute Eigenschaft wiederherstellen, wie sich herausstellt, _wenn_ Sie einige bestimmte Regeln befolgen, für die ich dokumentiert und Beispiele hinzugefügt habe, und die Sie wahrscheinlich immer verwenden möchten das Plugin in Kombination mit der Wiederherstellung der Route (entweder mit dem bereitgestellten SavedStateRouteObserver oder einem anderen Mechanismus).

Tao hat mich gerade darauf hingewiesen, dass dies in unserer Umfrage im letzten Herbst das am vierthäufigsten nachgefragte Thema war. Ich denke, es gibt hier (in den über 100 Kommentaren!)

@eseidelGoogle Ich stimme Ihrer Einschätzung nicht zu. IMHO gibt es keine „Mischung von geäußerten Benutzerwünschen“, sondern einen Mangel an Android-Plattformunterstützung, der verschiedene Programmierherausforderungen aufdeckt, die sich aus demselben Kernproblem ergeben.

Android-Anwendungen haben zwei zentrale Themen:

1) Prozesszustandsmaschine (auch bekannt als Lifecycle-Ereignisse).
2) UI-Toolkit.

Flutter tut (2), aber nicht (1). Es muss beides ansprechen.

3 Jahre...

Kurzes Update: Ich arbeite derzeit aktiv daran, Lösungen für dieses Problem zu finden.

@hvisser wie empfehlen Sie das Speichern des Navigationszustands? Der Navigator von Flutter hat keine getRoutes Methode.

@hvisser Ja, ich schaue mir das für beide Plattformen an (und es sollte flexibel genug sein, um es auch in andere Plattformen

@hvisser wie empfehlen Sie das Speichern des Navigationszustands? Der Navigator von Flutter hat keine getRoutes Methode.

@Zhuinden Der Navigator hat bereits Unterstützung dafür (obwohl ich nicht glaube, dass es bekannt ist). Ich habe ein Beispiel dafür, wie es im nativen State-Projekt eingerichtet wird, aber TL;DR

  • Sie müssen benannte Routen verwenden
  • Die Routen sollten hierarchisch sein, zB /screen1/screen2 folgt auf /screen1 folgt auf /
  • Die Routen werden in den Navigationsstapel verschoben, wenn Sie die Eigenschaft initialRoutes festlegen, und Ihre Routen werden auf diese Weise organisiert.
  • Ich _glaube_, Sie brauchen auch GlobalKey für navigatorKey auf MaterialApp aber das könnte für meinen Fall spezifisch sein.

Wenn Sie also initialRoutes auf /screen1/screen2 und Ihre Routen korrekt eingerichtet haben, funktioniert auch die Wiederherstellung der Navigation.

Ein weiterer Fall auf alten Geräten mit 2 SIM-Karten, wenn Sie den Flugzeugmodus ein- und ausschalten, werden die nativen YouTube- und Flatter-Apps neu gestartet #49057
Video ist in meiner Ausgabe angehängt

Versuchen Sie, den Flatter-Debug-Modus zu deaktivieren. Wenn der Debug-Modus aktiviert ist, muss die App immer neu gestartet werden.

debugShowCheckedModeBanner: false,

Ich arbeite seit 2018 in Flatter, vielleicht Juni/Juli und habe nach 2-3 Monaten festgestellt, dass dies ein Problem ist. Ich wusste nicht, dass es so ein großes Problem wird. Ich machte eine Musik-Player-App und wenn ich die Zurück-Taste drückte, zerstörte Flattern jeden Zustand, während die Musik im Hintergrund weiterspielte. Um dies zu beheben, habe ich damals diese system_shortcuts- Bibliothek erstellt.
Fügen Sie das Plugin Ihrem Projekt in pubspec.yaml hinzu _[ Für Anfänger :) ]_

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  system_shortcuts:

Ich habe immer eine Problemumgehung verwendet, die für mich gut funktioniert, aber es gibt ein paar Probleme, die unten in diesem Kommentar erwähnt werden, aber sie betreffen mich nicht so sehr.

Verwendungszweck -

Unabhängig davon, auf welchem ​​Widget Sie sich befinden und wenn Sie die Zurück-Taste drücken, wissen Sie, dass die App geschlossen wird (dh der Benutzer verlässt die App). Fügen Sie folgt hinzu

class _MusicPlayerState extends State<MusicPlayer>
    with WidgetsBindingObserver{
...
}

Überschreiben Sie danach die didPopRoute-Methode und fügen Sie den folgenden Code in die Methode ein

<strong i="21">@override</strong>
  Future<bool> didPopRoute() async {
    await SystemShortcuts.home();
  return true;
  }

_Lass uns verstehen, wie das funktioniert_:
App läuft >> Benutzer drückt Zurück-Taste >> Home-Taste wird gedrückt und Zurück-Taste nicht >> Die App geht in den Hintergrund und der Zustand wird NICHTS davon zerstört

Arbeitsprobe -

_ BEARBEITEN _
_Diese App wurde mit der Verwaltung des Anbieterstatus geschrieben, aber es wird kein Code verwendet, um den Status nach dem Schließen der App und dem erneuten Öffnen aus der letzten zu verwalten. Es ist nur der Code, den wir hier hinzugefügt haben._

ezgif com-video-to-gif

ezgif com-video-to-gif (1)

Themen -

Wenn Sie von einer anderen App zu Ihrer App gekommen sind, kehren Sie beim Zurückkehren von Ihrem Home-Widget immer zum Startbildschirm zurück und nicht zu der vorherigen App, von der Sie gekommen sind.
Beispiel :-
Der Benutzer ist in WhatsApp (oder einer anderen App) >> Sie erhalten eine Benachrichtigung von Ihrer App >> Sie öffnen Ihre App über die Benachrichtigung >> Jetzt drücken sie die Zurück-Taste >> Sie kehren zum Startbildschirm des Telefons zurück und nicht WhatsApp (was das Standardszenario sein sollte)
_Dieses Problem hat mir bisher keine großen Probleme bereitet und ich denke, alle normalen Benutzer und Entwickler werden damit einverstanden sein, da Sie in seltenen Fällen von einer anderen App zu Ihrer App kommen. Ich vernachlässige nicht, dass es ein paar Mal ruhig passieren kann, aber es funktioniert für mich und es ist nur ein Workaround, bis es wirklich Hilfe vom Flatter-Kernteam bekommt._

Die Tatsache, dass dieses Problem schon fast dreieinhalb Jahre alt ist und immer noch nichts ist, ist so ärgerlich.

Anscheinend soll daran bis Mai 2020 gearbeitet werden, gemessen am aktuell gesetzten Meilenstein… rechtzeitig zur Google I/O?

hvisser oben hat das Problem jedoch gelöst, jetzt müssen die Leute nur noch den Code schreiben, der es funktioniert

Ich mache Fortschritte bei der Bereitstellung einer Lösung dafür. Für eine frühe Vorschau, wie eine App den Instanzstatus wiederherstellen kann, wenn dies abgeschlossen ist, sehen Sie sich diesen PR an: https://github.com/flutter/samples/pull/433.

Bis alles fertig ist, wird es leider noch etwas dauern.

Das Designdokument mit Details zur Zustandswiederherstellung in Flutter ist hier verfügbar: http://flutter.dev/go/state-restoration-design

Die PR mit dem allgemeinen Rahmen für die Wiederherstellung des Zustands, wie im obigen Entwurf skizziert, wurde veröffentlicht: https://github.com/flutter/flutter/pull/60375. Ich arbeite immer noch daran, mehr Testabdeckung hinzuzufügen.

Das allgemeine Rahmenwerk zur Zustandswiederherstellung (https://github.com/flutter/flutter/pull/60375) wurde eingereicht. Ich arbeite jetzt daran, es in unsere Widgets zu integrieren. Beginnen Sie mit Scrollables, Textfeldern und dem Navigator.

Sie können dies selbst implementieren, indem Sie die Daten, die Sie wiederherstellen möchten, in einer SQLite- Datenbank speichern. Das ist eine Technik, die beim Schreiben von Servern verwendet wird.

Das hat den Vorteil, dass eine App auch dann wiederhergestellt wird, wenn der Akku des Telefons leer ist oder eine Kernel-Panik oder was auch immer auftritt. Ich empfehle, einen OO-Wrapper um Ihre SQLite-Aufrufe zu erstellen. Dart hat nicht so etwas wie SQLite.Net, das es leider automatisch für Sie erledigt.

Bearbeiten: Das haben wir auch in den frühen Tagen der mobilen Entwicklung gemacht, weil der Speicher so begrenzt war und sqlite eine großartige Möglichkeit ist, Dinge zwischen Speicher und Festplatte auszutauschen.

Ich schließe dieses Problem, da das allgemeine Framework zur Zustandswiederherstellung (und die Android-Einbettung) gelandet ist. Die verbleibende Arbeit wird in separaten Ausgaben nachverfolgt:

Gut erledigt!!!!! Danke flattern!!!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

Hixie picture Hixie  ·  3Kommentare

shadyaziza picture shadyaziza  ·  3Kommentare

cjztool picture cjztool  ·  3Kommentare

drewwarren picture drewwarren  ·  3Kommentare

xster picture xster  ·  3Kommentare