Speichern von Status in Android-Anwendungen

Heute wollte ich Ihnen einen anderen Ansatz zur Aufrechterhaltung des Zustands bei der Entwicklung von Android-Anwendungen vorstellen. Es ist kein Geheimnis, dass unsere Anwendung im Hintergrund jederzeit beendet werden kann, und dieses Problem wird mit der EinfĂŒhrung aggressiver Energieeinsparungen immer dringlicher - hallo Oreo . Außerdem hat niemand die KonfigurationsĂ€nderung am Telefon abgebrochen: Ausrichtung, SprachĂ€nderung usw. Und um die Anwendung im Hintergrund zu öffnen und die BenutzeroberflĂ€che im letzten Zustand anzuzeigen, mĂŒssen wir uns darum kĂŒmmern, sie zu speichern. Oh, das onSaveInstanceState .

onSaveInstanceState

Wie viel Schmerz hat er uns gebracht.

Ich werde spÀter Beispiele mit Clean Achitecture und Dagger2 geben , seien Sie also darauf vorbereitet :)

Das Problem der Aufrechterhaltung des Zustands in AbhÀngigkeit von Aufgaben kann auf verschiedene Arten gelöst werden:

  1. Speichern Sie PrimĂ€rdaten in onSaveInstanceState des Hosts (AktivitĂ€t, Fragment) - wie z. B. die Seitenkennung , den Benutzer und was auch immer. Was wir fĂŒr die anfĂ€ngliche Datenerfassung und Seitenanzeige benötigen.
  2. Speichern Sie die empfangenen Daten im interaktiven Programm im Repository (SharedPreference, Database).
  3. Verwenden Sie Rethein-Fragmente, um Daten bei der Neuerstellung von AktivitÀten zu speichern und wiederherzustellen.

Aber was ist, wenn wir den Status der BenutzeroberflĂ€che sowie die aktuelle Reaktion der BenutzeroberflĂ€che auf Benutzeraktionen wiederherstellen mĂŒssen? Betrachten wir der Einfachheit halber die Lösung dieses Problems anhand eines realen Beispiels. Wir haben eine Anmeldeseite - der Benutzer gibt seine Daten ein, klickt auf die SchaltflĂ€che und erhĂ€lt dann einen eingehenden Anruf. Unsere Bewerbung tritt in den Hintergrund. Das System tötet ihn. Klingt beĂ€ngstigend, nicht wahr?)

Der Benutzer kehrt zur Anwendung zurĂŒck und was sollte er sehen? Setzen Sie mindestens den Anmeldevorgang fort und zeigen Sie den Fortschritt an. Wenn sich die Anwendung vor dem Aufrufen der onDestroy-Methode des Hosts anmelden konnte, wird dem Benutzer die Navigation zum Startbildschirm der Anwendung angezeigt. Dieses Verhalten kann mit der Zustandsmaschine leicht gelöst werden. Sehr guter Bericht von Yandex . Im selben Artikel werde ich versuchen, meine gekauten Gedanken zu diesem Bericht zu teilen.

Nun ein kleiner Code:

Basisstaat

public interface BaseState<VIEW extends BaseView, OWNER extends BaseOwner> extends Parcelable{ /** * Get name * * @return name */ @NonNull String getName(); /** * Enter to state * * @param aView view */ void onEnter(@NonNull VIEW aView); /** * Exit from state */ void onExit(); /** * Return to next state */ void forward(); /** * Return to previous state */ void back(); /** * Invalidate view * * @param aView view */ void invalidateView(@NonNull VIEW aView); /** * Get owner * * @return owner */ @NonNull OWNER getOwner(); /** * Set owner * * @param aOwner owner */ void setOwner(@NonNull OWNER aOwner); } 

Baseoverner

 public interface BaseOwner<VIEW extends BaseView, STATE extends BaseState> extends BasePresenter<VIEW>{ /** * Set state * * @param aState state */ void setState(@NonNull STATE aState); } 

BasestateImpl

 public abstract class BaseStateImpl<VIEW extends BaseView, OWNER extends BaseOwner> implements BaseState<VIEW, OWNER>{ private OWNER mOwner; @NonNull @Override public String getName(){ return getClass().getName(); } @Override public void onEnter(@NonNull final VIEW aView){ Timber.d( getName()+" onEnter"); //depend from realization } @Override public void onExit(){ Timber.d(getName()+" onExit"); //depend from realization } @Override public void forward(){ Timber.d(getName()+" forward"); onExit(); //depend from realization } @Override public void back(){ Timber.d(getName()+" back"); onExit(); //depend from realization } @Override public void invalidateView(@NonNull final VIEW aView){ Timber.d(getName()+" invalidateView"); //depend from realization } @NonNull @Override public OWNER getOwner(){ return mOwner; } @Override public void setOwner(@NonNull final OWNER aOwner){ mOwner = aOwner; } 

In unserem Fall ist der StaatseigentĂŒmer ein Moderator.

Auf der Anmeldeseite können drei eindeutige ZustÀnde unterschieden werden:

LoginInitState , LoginProgressingState , LoginCompleteState .

Überlegen Sie nun, was in diesen Staaten passiert.

LoginInitState Wir haben die Validierung von Feldern und im Falle einer erfolgreichen Validierung wird die Login-SchaltflÀche aktiv.

In LoginProgressingState wird eine Anmeldeanforderung gestellt , ein Token wird gespeichert, zusÀtzliche Anforderungen werden gestellt, um die HauptaktivitÀt der Anwendung zu starten.

LoginCompleteState navigiert zum Hauptbildschirm der Anwendung.

Bedingt kann der Übergang zwischen ZustĂ€nden im folgenden Diagramm dargestellt werden:

Anmeldestatusdiagramm

Der Status LoginProgressingState wird beendet, wenn der Anmeldevorgang im Status LoginCompleteState erfolgreich ist und LoginInitState fehlschlĂ€gt . Wenn sich unsere Sichtweise löst, haben wir also einen völlig deterministischen Zustand des PrĂ€sentators. Wir mĂŒssen diesen Status mit dem Standard-Android-Mechanismus onSaveInstanceState speichern . Dazu mĂŒssen alle Anmeldestatus die Parcelable- Schnittstelle implementieren. Daher erweitern wir unsere Basisschnittstelle BaseState .

Als nĂ€chstes haben wir eine Frage, wie dieser Status vom Moderator an unseren Gastgeber weitergeleitet werden kann. Am einfachsten ist es, den PrĂ€sentator vom Host nach Daten zu fragen. Aus architektonischer Sicht sieht dies jedoch nicht sehr gut aus. Und so kommen uns Fragmente zu Hilfe. Wir können eine Schnittstelle fĂŒr den Cache erstellen und in diesem Fragment implementieren:

 public interface Cache{ /** * Save cache data * * @param aData data */ void saveCacheData(@Nullable Parcelable aData); @Nullable Parcelable getCacheData(); /** * Check that cache exist * * @return true if cache exist */ boolean isCacheExist(); } 

Als nĂ€chstes injizieren wir das Cache-Fragment wie Cache in den Konstruktor des Interaktors. Wir fĂŒgen dem Integrator Methoden hinzu, um den Status im Cache abzurufen und zu speichern. Jetzt können wir bei jeder Änderung des Status des PrĂ€sentators den Status im Interaktor speichern, und der Interaktor speichert seinerseits im Cache. Alles wird sehr logisch. Beim erstmaligen Laden des Hosts empfĂ€ngt der PrĂ€sentator den Status vom Interaktor, der wiederum Daten aus dem Cache empfĂ€ngt. So sieht die StatusĂ€nderungsmethode im PrĂ€sentator aus:

 @Override public void setState(@NonNull final LoginBaseState aState){ mState.onExit(); mState = aState; clearDisposables(); mState.setOwner(this); mState.onEnter(getView()); mInteractor.setState(mState); } 

Ich möchte auf diesen Punkt hinweisen: Das Speichern von Daten ĂŒber den Cache kann fĂŒr alle Daten erfolgen, nicht nur fĂŒr den Status. Möglicherweise mĂŒssen Sie Ihren eigenen eindeutigen Snippet-Cache erstellen, um aktuelle Daten zu speichern. Dieser Artikel beschreibt einen allgemeinen Ansatz. Ich möchte auch darauf hinweisen, dass die fragliche Situation sehr ĂŒbertrieben ist. Im Leben muss man Probleme viel komplizierter lösen. In unserer Anwendung wurden beispielsweise drei Seiten kombiniert: Anmeldung, Registrierung, Kennwortwiederherstellung. In diesem Fall war das Zustandsdiagramm wie folgt:

Zustandsdiagramm in einem realen Projekt

Mithilfe des im Artikel beschriebenen Statusmusters und Ansatzes konnten wir den Code lesbarer und wartbarer machen. Wichtig ist, den aktuellen Status der Anwendung wiederherzustellen.

Der vollstÀndige Code kann im Repository angezeigt werden.

Source: https://habr.com/ru/post/de420023/


All Articles