Knackpunkt "orientation changed"

  • Antworten:14
Michael H
  • Forum-Beiträge: 127

14.08.2012, 09:31:19 via Website

Hallo zusammen

Meine App kommuniziert über eine Socketverbindung mit dem Server. Nun habe ich noch eine Art Zeiterfassung in dieser App implementiert. Da bin ich auf folgendes Problem gestossen:

Die Idee ist simpel, der Benutzer hat die Möglichkeit entweder auf "Kommen" oder "Gehen" zu klicken (je nachdem was zuletzt geklickt wurde). Auch hat er die Möglichkeit eine bestimmte Nummer einzutragen. Anschliessend wird die Zeiterfassung an den Server übermittelt, welcher darauf eine Antwort (Erfolgreich, Nicht erfolgreich) zurückliefert. Im Falle einer negativen Antwort vom Server darf der Status "Kommen"/"Gehen" nicht wechseln.

Alles funktioniert wunderbar, nur wenn der Benutzer das Gerät genau zwischen Anfrage des Clients und Antwort des Servers dreht, baut das Phone die View auf und zeigt die falsche View (die noch alte, d.H. der Button wechselt nicht von "Kommen" auf "Gehen") an. Jetzt ist die Gefahr dass der Benutzer 2 x hintereinander "Kommen" oder "Gehen" erfassen könnte.

Hat jemand eine Idee?

Gruss

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

14.08.2012, 09:35:28 via Website

Da musst du halt den Übergang beim Wechsel sauber implementieren und die Werte retten, oder die Neuerstellung der Activity beim Wechsel der Ausrichtung abstellen.

Hier steht alles dazu: http://developer.android.com/guide/topics/resources/runtime-changes.html

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

14.08.2012, 10:10:30 via Website

Die Stichworte hierzu sind die beiden Methoden-Paare:

* onSaveInstanceState/onRestoreInstanceState (beides sind Callbacks)

* onRetainNonConfigurationInstance/getLastNonConfigurationInstance (ersteres ist ein Callback, letzteres eine Methode die üblicherweise im onCreate abgefragt wird)

Das erste Pärchen eignet sich für simple Daten die in einem Bundle hinterlegt werden. Das zweite Pärchen ist für komplexere Daten. Das kann zum Beispiel eine ganze Klasse mit einem Schwung Daten sein. Diese Klasse muss noch nicht einmal aufwändig serialisiert werden, was bei einem Bundle (s.o.) notwendig wäre.

Antworten
Michael H
  • Forum-Beiträge: 127

14.08.2012, 10:22:32 via Website

Irgendwie stelle ich das in meinem Fall nicht so einfach vor. Wenn eine Erfassung an den Server gesendet wurde und ich die Antwort nicht bekomme, weil sich der View aufbaut und damit das Warten auf die Antwort abbricht. Das was ich zum Zeitpunkt der "orientation change" noch nicht habe, kann ich auch nicht übergeben... Was meint ihr?

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

14.08.2012, 10:25:39 via Website

Die einfachste Lösung, speziell, wenn du keine unterschiedlichen Layouts für Hoch- und Querformat benutzt, ist es das neu Instanzieren der Activity mit dem Attribut configChanges zu deaktivieren.

1<activity android:name=".MyActivity"
2 android:configChanges="orientation|keyboardHidden"
3 android:label="@string/app_name">

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

14.08.2012, 10:34:05 via Website

Michael H
Irgendwie stelle ich das in meinem Fall nicht so einfach vor. Wenn eine Erfassung an den Server gesendet wurde und ich die Antwort nicht bekomme, weil sich der View aufbaut und damit das Warten auf die Antwort abbricht. Das was ich zum Zeitpunkt der "orientation change" noch nicht habe, kann ich auch nicht übergeben... Was meint ihr?

Das Design ist in diesem Fall falsch. Wenn eine Netzwerk-Kommunikation abbricht wenn Du das Handy drehst dann machst Du etwas grundlegend falsch. Deine App wird auf Geräten mit Android >= 3.0 eventuell, auf Geräten mit Android >= 4.0 mit Sicherheit, abstürzen. Der mit diesen Android Versionen eingeführte StrictMode unterbindet Netzwerk-Kommunikation die nicht in einem eigenen Thread läuft.

Die Netzwerk-Kommunikation gehört in einen AsyncTask o.ä.. Schau mal in die Code-Snippets zwei Foren weiter.

Sobald Du den Request abschickst legst Du Dir das in einer Variablen (e.g. boolean requestPending) ab die Du im OrientationChange an die nächste Activity-Instanz weitergibst (onSaveInstanceState/onRestoreInstanceState). Deine View kannst Du dann mit Hilfe dieser Variablen sauber implementieren und in aller Ruhe auf das Ergebnis des AsyncTask warten.

— geändert am 14.08.2012, 10:37:37

Gelöschter Account

Antworten
André
  • Forum-Beiträge: 77

14.08.2012, 10:45:40 via Website

Harald Wilhelm
Die Netzwerk-Kommunikation gehört in einen AsyncTask o.ä..
Das hilft allerdings auch nicht, wenn die Activity beim orientation change beendet und neu gestartet wird. Da müsste man die Kommunikation schon in einen Service auslagern, was etwas übertrieben wäre.

Es gibt zwei einfache Lösungen.
1) Du verzichtest auf einen Neustart der App beim Wechsel und implementierst ihn selbst in onConfigurationChanged(). Dann bleibt die Netwerk-Verbindung bestehen.
2) Du speicherst die noch unbeantworteten Anfragen an den Server in onSaveInstanceState() und sendest sie nach dem Neustart der App einfach nochmal an den Server.

Antworten
Michael H
  • Forum-Beiträge: 127

14.08.2012, 10:50:32 via Website

@ Rafael :
Diese Möglichkeit kannte ich vorher noch nicht. Habe in deinem Verweis sehrwahrscheinlich nicht so weit runtergescrollt! :-) Aber durchaus eine gute Alternative zum erhalten von Viewelementen wie LoadingWheels, ProgressBars etc...

@ Harald :
Die Komplette Netzwerkkommunikation läuft in einem eigenen Thread, welcher in einem eigenen Service implementiert ist. Dieser holt sich die Daten aus einer SQLite-DB. D.H. die Zeiterfassungen welche der Benutzer eingibt werden "kurz" in einer SQLite-DB abgelegt und anschliessend vom Service abgeholt.
Wenn ich nun eine Zeiterfassung mache, wird diese in die DB geschrieben. Anschliessend wird 4 sec gewartet. Sofern in dieser Zeitspanne eine Antwort des Servers zurückkommt, wird der Button von "Kommen" auf "Gehen" geändert oder vice versa... Deshalb das Problem mit dem View...

Ich werde das ganze zunächst mal mit dem android:configChanges="orientation|keyboardHidden" Lösen. Vielleicht nicht die "eleganteste" Lösung, aber ein guter Anfang! :-)

Auf jeden Fall: Vielen Dank Euch beiden...

Gruss

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

14.08.2012, 10:53:27 via Website

André
Das hilft allerdings auch nicht, wenn die Activity beim orientation change beendet und neu gestartet wird. Da müsste man die Kommunikation schon in einen Service auslagern, was etwas übertrieben wäre.

Das ist definitiv falsch.

In meinen Apps gibt es dutzende AsyncTasks die Netzwerk-Kommunikation betreiben (POIs von Google Maps holen im Tankbuch, Kommunikation mit Garmin Connect in GaCoMo, Kommunikation mit dem Fritz!Repeater im Streaming Repeater, diverse Google Drive und Wuala Apps, und, und, und). In keinem dieser Fälle gibt es einen Service (jedenfalls dafür nicht) und Du kannst das Handy drehen bis Dir schwindelig wird - die Netzwerk-Kommunikation läuft problemlos im Hintergrund weiter obwohl die Activity, die den AsyncTask gestartet hat, abgeschossen und wieder neu aufgebaut wird,

Der Manifest Hack ist bequem, ich weiß. Aber mach Dir bitte mal kurz die Mühe und schau in mein Beispiel in den Code-Snippets. Steck in den doInBackground Deine Netzwerk-Kommunikation und das läuft absolut sauber.

— geändert am 14.08.2012, 11:09:28

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

14.08.2012, 11:03:05 via Website

Michael H
Anschliessend wird 4 sec gewartet.

Warum nicht fünf Sekunden?

Mit dieser Frage will ich Dir verdeutlichen das ein solches Design absolut falsch ist. Diese vier oder fünf ist eine willkürliche Annahme. In keine Anwendung gehören solche Zufallswerte.

Zum Service/Thread: Der AsyncTask funktioniert hier nicht und der Thread ist deshalb die richtige Wahl. Aber was hindert Dich daran mit dem Netzwerk-Request gleichzeitig an "interessierte" Activities einen Broadcast zu senden (ich hole gerade Daten). Die legen sich diesen Zustand in einem Boolean ab und Sichern/Restoren diesen Zustand mit den onSaveInstanceState/onRestoreInstanceState Callbacks. Auf diese Weise kannst Du sogar Unmengen an Stati rumschicken. Nimm mal als Beispiel Music Player (ich habe zwei davon im Portfolio). Deren Services melden an die Activities ob sie gerade Puffern, Abspielen, Skippen, oder warten. Im Gegenzug können die Activities dem Service auch Meldungen übermitteln. Wird die Activity nach dem OrientationChange neu aufgebaut so kann die Activity an Hand des Boolean erkennen das ein Request läuft und sperrt einfach die Buttons. Kommt dann später der Broadcast mit der erfolgreichen Meldung vom Request dann setzt Du den Boolean wieder zurück und gibst die Buttons frei. Damit hast Du ein absolut sauberes und event-getriebenes Design.

Ein Beispiel hierzu (BroadcastReceiver) gibts in den Code Snippets zwei Foren weiter.

— geändert am 14.08.2012, 11:11:44

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

14.08.2012, 11:20:43 via Website

Michael H
Ich werde das ganze zunächst mal mit dem android:configChanges="orientation|keyboardHidden" Lösen. Vielleicht nicht die "eleganteste" Lösung, aber ein guter Anfang! :-)

IMHO ist es in den meisten Fällen sehr wohl die beste Lösung und gleichzeitig die performanteste.
"Hack" ist auch übertrieben. Es ist ja auch kein Hack das Standard-Theme im Manifest zu ändern, oder die TitleBar zu deaktivieren. Es ist ein konfigurativer Eingriff :)

Der einzige echte Vorteil des Standardverhaltens ist IMHO tatsächlich, dass dann automatisch die zur Ausrichtung passenden resourcen gezogen werden.
Sonst bringt das in meinen Augen nur Nachteile, sowohl für den Entwickler (Aufwand) als auch für den Anwender (Performance).

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

14.08.2012, 11:56:20 via Website

Aber an der eigentlichen Situation ändert sich nichts. In einer Kombination aus Activity und Service kann auch aus anderen Gründen die Activity abgeschossen werden (Ressourcen-Knappheit, langer Druck auf den Back-Button in diversen Custom-ROMs, etc.) - nicht nur durch OrientationChange.

Preisfrage: Wenn nach einem Abschuss die Activity durch den Benutzer neu gestartet wird und der Service war nach wie vor aktiv - welcher Status ist dann gültig und welcher wird angezeigt?

Ich versuche aufzuzeigen das in dem Konzept ein Problem schlummert.


Zum Manifest "Hack". Die Google Android Entwicklerin des UI-Frameworks Dianne Hackborn hat an vielen Stellen von diesem "Hack" abgeraten. Nur einer von vielen Beiträgen:

onCreate and orientation change

Was die meisten Entwickler vergessen: "orientation|keyboardHidden" ist die Liste der Events die aktuell einen OrientationChange auslösen. Morgen kommt vielleicht eine Fernbedienung im Android-TV dazu ;-). Wenn dieser bisher unbekannte Event in Deiner Manifest-Liste fehlt dann wird Deine Anwendung beim Druck auf die Fernbedienung still sitzen und nichts machen und Deine Benutzer hauen auf die Fernbedienung und verstehen nicht warum der Tastendruck nicht ankommt der bei den anderen Anwendungen in einem neuen Fenster eingeblendet wird. Deine Anwendung zeigt noch nicht mal das Fenster. Der "richtige" Weg wird dann aber weiterhin funktionieren.

Der "Hack" funktioniert aktuell - ist aber nicht zukunftssicher.

— geändert am 14.08.2012, 12:06:54

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

14.08.2012, 12:10:54 via Website

Naja die Frau heißt ja auch Hackborn :D *SCNR*

Ansonsten:
Zustimmung! Den instance-state zu speichern ist in jedem Fall sinnvoll!
Teilweise wird die eigene Activity ja schon gekillt, wenn man nur eine Galerie als Image-Picker aufruft, oder sie nur minimiert.
Ich finde es auch perfekt den orientation-change zu "mißbrauchen" um diese Speicherlogik zu testen.
Für den produktiven Betrieb sehe ich aber in den meisten Fällen dann keinen Sinn mehr darin unnötig Activities zu verschrotten und neu zu erstellen.

EDIT:
Zum Thema "zukunftssicher": In dem Link, den ich oben gepostet habe ist von einer Änderung in API Level 13 die Rede betreffend den Event screenSize.
Was hierbei wichtig ist, ist die Tatsache, dass dabei Apps, die als Target-API <=12 haben von dieser Änderung nicht betroffen sind. Für die gilt das alte Verhalten.
Google ist scheinbar auch nicht so leichtfertig damit Änderungen zu machen, die bisherige Apps fahrlässig vor die Wand laufen lassen :)

Das Wort "richtig" finde ich in dem Zusammenhang auch nicht "richtig". Ob etwas "richtig" ist, entscheidet IMHO der Kontext und nicht eine dogmatische Regel ;-)

— geändert am 14.08.2012, 12:38:08

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

14.08.2012, 12:38:34 via Website

Rafael K.
Für den produktiven Betrieb sehe ich aber in den meisten Fällen dann keinen Sinn mehr darin unnötig Activities zu verschrotten und neu zu erstellen.

Mach mal folgenden Test - einfach für Dich, nur so: Erstelle mal spaßeshalber ein horizontales Layout (kopier einfach das vertikale und setz in das horizontale einen zusätzlichen Dummy View). Nun dreh das Handy. Na, was ist passiert? Kratz am Kinn, wieso ist das Layout jetzt anders - trotz des Manifest "Hacks"? Verdammt, wer war das? Irgendwann im Laufe der Android Evolution hat sich da etwas eingeschlichen. Android nimmt Dir immer mehr Arbeit ab - aber eben auch nicht alles.

Warum ich den "Hack" nicht mehr verwende? In meinen Apps wird fast alles in Threads ausgelagert. Selbst jede ListActivity holt sich die Daten aus der Datenbank in einem AsyncTask. Der Task wird aber nicht von Android gesichert. Wenn man es also in über 50% der Activities "richtig" machen muss dann kann man das auch in den anderen machen. Ich möchte bei meinen Kunden nicht die ANR Bombe hinterlassen ab welcher Größenordnung die App bei ihnen crasht. Ich habe schon GaCoMo Kunden mit tausenden Sportaktivitäten (mit Millionen Koordinaten). Die größte mir bekannte Datenbank meiner Apps da draußen ist über 80MB. Die laufen dank der vielen Threads nach wie vor system-konform.

EDIT wegen Deines EDITs: Bzgl. richtig oder nicht: Der Entwickler einer Methode kann vorschlagen was richtig oder falsch ist. Dianne Hackborn sagt "Hack" ist falsch. Romain Guy sagt "Hack" ist falsch (seine Methode finde ich jetzt aber auch nicht besser - er killt die Threads wenn die Activities abgeschossen werden). Die beiden wissen wovon sie reden - die beiden entwickeln das System. Ich bin geneigt diesen beiden eher zu glauben.

— geändert am 14.08.2012, 12:49:19

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

14.08.2012, 12:56:36 via Website

Harald Wilhelm
Ich habe schon GaCoMo Kunden mit tausenden Sportaktivitäten (mit Millionen Koordinaten). Die größte mir bekannte Datenbank meiner Apps da draußen ist über 80MB. Die laufen dank der vielen Threads nach wie vor system-konform.
In deinem Kontext mag das zweifellos die "richtige" Lösung sein :)
In einem anderen Kontext ist es evtl. die sprichwörtliche Kanone für den Spatz.

Harald Wilhelm
EDIT wegen Deines EDITs: Bzgl. richtig oder nicht: Der Entwickler einer Methode kann vorschlagen was richtig oder falsch ist. Dianne Hackborn sagt "Hack" ist falsch. Romain Guy sagt "Hack" ist falsch (seine Methode finde ich jetzt aber auch nicht besser - er killt die Threads wenn die Activities abgeschossen werden). Die beiden wissen wovon sie reden - die beiden entwickeln das System. Ich bin geneigt diesen beiden eher zu glauben.
In deren Position können die auch nur Ratschläge geben, die DAU-safe sind, sonst würden die in Rückfragen und Kritik versinken.
Genauso wie IT-ler immer wieder "Formatieren, neu installieren" als Universallösung propagieren, obwohl es auch einfacher gehen würde. Dann müssten sie aber viel mehr erklären.
Das ist halt der kleinste gemeinsame Nenner, der immer funktioniert.
Wenn man sich ein bisschen mit der Materie beschäftigt hat, kann man dann aber auch gerne mal seine eigenen Schlüsse ziehen.
Dafür gibt es diese Optionen schließlich :)

Jetzt kann sich der Thread-Ersteller ja zwischen Tor1 oder Tor2 entscheiden :D

— geändert am 14.08.2012, 13:11:23

Antworten