MainActivity wird unerwartet neu aufgebaut und läuft parallel zur alten !

  • Antworten:7
  • OffenNicht stickyNicht beantwortet
  • Forum-Beiträge: 41

24.07.2017, 16:13:38 via Website

Hallo,

Ich habe eine App geschrieben, die Daten aus der Location-Api periodisch in eine Textdatei schreibt. Sie registriert sich im System beim Listener (Location Manager), welcher eine Callback-Methode aufruft und die Daten übergibt. Diese werden dann in eine Textdatei geschrieben.

Die App läuft soweit perfekt und zu 95% stabil. Es kommt jedoch manchmal vor, dass sich scheinbar die MainActivity neu erstellt (was ich an der Oberfläche sehen kann, da sich dann die UI wie nach einem Erststart der App neu aufbaut) allerdings die alte Activity im Hintergrund weiterläuft (was ich wiederum daran erkennen kann, dass die Datei weiter aufgezeichnet wird! D.h. es werden weiterhin neue Daten in die Datei geschrieben.)
Problem 1: Die UI sieht aus, als ob nicht aufgezeichnet werden würde, weil sie gerade erst "neu gestartet" ist. In Wirklichkeit findet aber eine Aufzeichnung statt. Die App befindet sich in einem inkonsistenen Zustand.
Problem 2: Wenn man in der neu aufgebauten UI (bzw. Activity) auf "Start" drückt, dann wird ein neuer Listener registriert und da die alte Activity mit dem alten Listener noch läuft, findet eine doppelte Aufzeichnung statt! D.h., es werden Daten doppelt in die Textdatei geschrieben und das auch noch ziemlich asynchron, da ziemlich oft geflusht wird -> Datei wird völlig unbrauchbar / zerschrieben.

Screen-Rotation ist übrigens aus (immer Portrait).

mir scheint es, als ob dieser Activity-Neuaufbau besonders häufig bei meinem Samsung S8 auftritt. Ich kann allerdings nicht sagen, wie und wann das passiert. Ich kann es leider nicht bewusst erzwingen. Mir scheint es, als ob dies manchmal passiert, wenn die App in den Hintergrund geht und dann längere Zeit dort bleibt bzw. das Gerät in Schlafmodus gegangen ist.

Fragen:

1.) Unter welchen Bedingungen tritt dies auf (also dass die MainAcitivity+UI neu aufgebaut wird und die alte MainActivity weiterläuft bzw. die darin enthaltene Callback-Funktion immer weiter aufgerufen wird).

2.) Wie könnte ich elegant und einfach Problem 1 und 2 lösen? Ich meine Problem 1 evtl. mit static Variablen lösen zu können, sicher bin ich mir aber nicht.

Antworten
  • Forum-Beiträge: 2.907

24.07.2017, 16:30:28 via Website

Hallo Arne,

eines mal vorweg : das gleiche Standard Paket "startet" sich nicht von Alleine zweimal und kann auch nicht
zweimal gestartet werden.

1) Allgemein müsstest du m.E. eher mit Services oder Processes arbeiten . Der Service kann dann entweder
selbst abschreiben oder was an die App-Instanz schicken. - Dann bist du UI / Rotation-frei.
Für dein jetziges Problem denke ich, dass du nicht ordentlich OnPause / OnResume anwendest.

Des Weiteren würde ich empfehlen , mit SingleTon - Classen zu arbeiten und oder den Status in eine
DB wegzuschreiben ( nicht in den Shares)

Anmerkung : Ich glaube , dass du Dich noch ein wenig in die EIgenschaften von Android einlesen
musst - insbesondere LifeCycle und GC.

Services, wie auch Apps können NICHT rund um die Uhr laufen , es sei denn sie sind Foreground.
Das ist vom System so gewollt und schmeisst dir alles raus , was es nicht für wichtig hält.

Ein kleines Workaround wäre ein Service mit ALarmmanger - hat aber zur Folge , dass dir immer
wieder eine Pause in der Aufzeichnung enstehen wird.

Android ist halt nicht für Aufzeichnungen im Dauerbetrieb konzipiert.

— geändert am 24.07.2017, 16:52:48

Liebe Grüße - Stefan
[ App - Entwicklung ]

Arne

Antworten
  • Forum-Beiträge: 41

24.07.2017, 17:42:18 via Website

Hey Stefan,

danke für deine superschnelle Antwort! Ich weiß, dass ich als Anfänger natürlich vieles nicht so gemacht habe wie es Fortgeschrittene machen würden und ich bin natürlich für alle Verbesserungen offen. Ich komme in meinem nächsten Post zurück auf deine Vorschläge, aber kannst du mir zunächst, nur für das Verständnis, erklären was bei meiner App genau vor sich geht? Mir lässt das nämlich keine Ruhe und ich denke, dass ich um die LifeCycle-Sache zu verstehen nicht drum herum kommen werde zu verstehen, was hier genau passiert.

[[cite swa00]]
eines mal vorweg : das gleiche Standard Paket "startet" sich nicht von Alleine zweimal und kann auch nicht
zweimal gestartet werden.

Ich habe eine boolean Variable logging_flag. Diese wird bei Initialisierung (global) auf false gesetzt.

Start-Button macht logging_flag = true und Stop-Button logging_flag = false. Außerdem ändern sie ihren Enabled/Disabled-Status und des jeweiligen anderen.
onCreate() prüft ob logging_flag true oder false ist und setzt die Buttons entsprechend.

In der Callbackfunktion habe ich stehen:

if(logging_flag == true){writeDataToFile(...);}

Wenn nun dieser mysteriöse Wiederaufbau der UI passiert, ist der Start-Button enabled und der Startbutton disabled. Das heißt also, dass logging_flag false gewesen sein muss. Trotzdem wird aber die Datei noch weitergeschrieben (Sie wird sogar sofort neu erzeugt, wenn ich sie lösche!)

Kannst du mir erklären, was hier vor sich geht?
Für mich ergibt das nur Sinn, wenn die logging_flag nun zwei mal existiert (die alte ist true, die neue ist false).

Antworten
  • Forum-Beiträge: 2.907

24.07.2017, 18:30:19 via Website

Hallo Arne,

wenn ich jetzt auf Alles detailliert antworten würde, dann hätte ich leider viel zu tun .
Ich versuche mich kurz und knackig zu äussern.

Im englischen Forum habe ich gelesen , dass du aus der C / C++ Ecke kommst.
Das mache ich auch hauptberuflich und kann Dir nur sagen : UMDENKEN :-)

In Android gibt es keine globalen Variablen, die du einfach auf einen Wert setzen kannst und gut ist .

Es kann durchaus sein , dass dir der Wert flöten geht, je nachdem was passiert .
Das kann bei einen onResume passieren ,wenn die App in den Hintergrund geschickt wird, wenn sich
die Orientation ändert - oder das System schlichtweg Resourcen braucht etc etc.
Einzige Ausnahme : Die entsprechende Activity (nicht die App !) ist im Vordergrund, das Display an und
der OrientationFlag in der Manifest ist disabled. (android:configChanges="orientation")

Beispiel : Wenn die App im Taskmanager im Hntergrund ist , heisst das nicht,
dass sie noch läuft . Das ist lediglich ein Screenshot . Irgendwann mal kann die App auch durch das
System angehalten worden sein und ist trotzdem noch darin zu sehen .
Tippst du sie wieder an und kommt in den Vordergrund gilt das quasi als ein Neustart. (OnResume)

Hier mal etwas zum Lesen für die essentiellen Grundlagen :
https://developer.android.com/guide/components/activities/activity-lifecycle.html
https://de.wikipedia.org/wiki/Garbage_Collection
http://www.anandtech.com/show/8231/a-closer-look-at-android-runtime-art-in-android-l/2

Du musst Dich also selbst als Entwickler darum kümmern, dass alles noch im rechten Lot ist.

Dein logging_flag kann also durchaus seinen Status ändern und schon ist die Misere perfekt.
Einen Hinweis , dass der Status geändert wurde, gibt es nicht ( Listener / Event)

Android ist auf low-power konzipiert, dass heisst , alles was du Entwickler willst kann jederzeit vom
System zunichte gemacht werden .
Alleine schon der Umstand , dass hier auf einen Linux-Kernel ein virtuelles Java aufgesetzt wird (Zygote)
macht das Leben als C / C++ Guru schwer - da passieren Sachen , die du einfach nicht willst.
Android ist ein Klicki klicki Bunti hight Level paket , nix Halbes und nix Ganzes.

Wenn du also eine systemnahe 24/7 App haben magst , ist Android der falsche Weg .

Du kannst allerdings deine Variable in eine SingleTon Klasse definieren , das gibt dir zumindest die Sicherheit ,
während die App läuft , dass dieser Wert konstant ist.
https://android.jlelse.eu/how-to-make-the-perfect-singleton-de6b951dfdb0
.

Eine ganz andere Sache ist der Service/Process - im Prinzip ein background thread
( Linux/C = pthread, Windows/C = CreateThread) - Vereinfacht : Eine App ohne UI

Den startest du von deiner App aus ( oder beim booten) und der wackelt alleine vor sich hin.
Der kann auch deine Daten in Ruhe abschreiben, oder seine Daten an deine App schicken , wenn sie denn mal auf
ist .Wenn nicht, dann wackelt der Service trotzdem.
http://www.vogella.com/tutorials/AndroidServices/article.html
.

Aber auch dieser Service kann vom System bei Faulheit beendet werden,

Da kann man wieder hingehen und den Service regelmäßig selbst beenden lassen und durch den ALarmmanager
wieder zum leben erwecken ( die oben erwähnte Pause)
Mit dieser Technik ( ich nenne sie liebevoll JoJo ) verhinderst du , dass das System dir den Stecker
aus Langeweile zieht - denn der Service wird ja durch dich immer wieder neu gestartet .

Hier mal ein recht nettes Beispiel :
https://github.com/gipi/Android-keep-alive/blob/master/src/org/devtcg/demo/keepalive/KeepAliveService.java

Ich hoffe das war jetzt ein wenig "einfach" erklärt , was denn so bei uns im "Grünen Männchen Land" los ist :-)

— geändert am 25.07.2017, 13:42:18

Liebe Grüße - Stefan
[ App - Entwicklung ]

ArnePascal P.

Antworten
  • Forum-Beiträge: 2.907

25.07.2017, 11:15:05 via Website

EDIT : gelöscht , war sinnfrei :-)

— geändert am 25.07.2017, 11:15:59

Liebe Grüße - Stefan
[ App - Entwicklung ]

Arne

Antworten
  • Forum-Beiträge: 41

25.07.2017, 11:56:28 via Website

Hey Stefan,

ich werde deine Ratschläge und Infos die ich alle für wahr halte verinnerlichen und umsetzen.

Nur möchte ich an dieser Stelle ungern die Angelegenheit abhaken mit dem Gedanken "Tja, ich habs halt nicht ganz richtig programmiert und das System hat halt meine Variablen (ab und an) einfach so aus Laune zunichte gemacht".

Falls also noch jemand eine Idee haben sollte, wieso und warum sich das System so verhält und was genau hier vor sich geht, würde ich mich über einen Tip sehr freuen.

noch ein paar Backgrounds:


Ich benutze die Android Location Services, etwa so wie in diesem Post:

https://stackoverflow.com/questions/42890585/gnssstatus-callback-onsatellitestatuschanged-not-working

Ich registriere beim Listener die Callbackfunktion die vom System regelmäßig aufgerufen wird. Dort drinnen habe ich den FileWriter der die Datei schreibt. Damit läuft das ganze auch in einem eigenen Thread und meiner Meinung nach auch unabhängig von meiner Activity. Deswegen meine ich, dass ich quasi bereits in diesen Service alles ausgelagert habe und nichts eigenenes schreiben soll.
Die Callbackfunktion schreibt nur, wenn logging_flag = true ist. Dieser ist in der MainActivity deklariert und wird dort auch über die UI geändert. Klappt alles wunderbar. Nur wenn dieser mysteriöse Activity-Neustart bzw. Neuaufbau der UI passiert, ist logging_flag false und die App quasi im Neuzustand aber der Service läuft weiter, sprich die Callbackfunktionen werden immer noch aufgerufen und das Logging findet weiterhin statt, was nur sein kann, wenn logging_flag true ist. Deshalb vermute ich, dass die logging_flag nun irgendwie 2 mal existieren muss, weil vielleicht eine neue Instanz von MainActivity erzeugt wurde.

Ich würde sehr gerne verstehen, was hier genau vor sich geht.​

Antworten
  • Forum-Beiträge: 2.907

25.07.2017, 12:02:14 via Website

Hallo Arne,

Nur möchte ich an dieser Stelle ungern die Angelegenheit abhaken mit dem Gedanken "Tja, ich habs halt nicht ganz richtig programmiert und das System hat halt meine Variablen (ab und an) einfach so aus Laune zunichte gemacht".Falls also noch jemand eine Idee haben sollte, wieso und warum sich das System so verhält und was genau hier vor sich geht, würde ich mich über einen Tip sehr freuen.

Der Hintergrund wird Dir in den obigen Links genaustens erklärt und ich habe mir die Mühe gemacht,
dir den "Inhalt" auf ein kurzes Deutsch zusammen zu fassen :-)

Mehr können wir für Dich leider nicht tun

.

Damit läuft das ganze auch in einem eigenen Thread und meiner Meinung nach auch unabhängig von meiner Activity. Deswegen meine ich, dass ich quasi bereits in diesen Service alles ausgelagert habe und nichts eigenenes schreiben soll.

Nein, ein Thread ist KEIN Service ... Du benutzt lediglich in deinem Activity-Thread den Callback eines Google Service.
Schau dir bitte in den obigen Links an , wie ein Service deklariert wird.

Ein Service ist was Anderes, als eine Activity und wird vom System gesondert behandelt
Du musst also für Dein Paket einen eigenen Service erstellen

Auch wenn du es nicht wahrhaben möchtest : Es ist nunmal Fakt :-)

Es gibt auch mehrere Unterschiede bei der Deklaration von Threads. So kann/sollte z.b. der hoch geliebte
AsyncTask auch nicht für längere Operationen benutzt werden . Der wäre also falsch für deinen Service

— geändert am 25.07.2017, 14:00:47

Liebe Grüße - Stefan
[ App - Entwicklung ]

Arne

Antworten
  • Forum-Beiträge: 41

26.07.2017, 14:39:49 via Website

Danke Stefan. Ich habe die Links vorher nicht gesehen, die hast du wahrscheinlich nachträglich reingebracht. Sie sind gut verständlich und gefallen mir.

Ich verstehe den Sinn und Zweck von Services sehr gut. Nun verwende ich hier nur nicht meinen eigenen, sondern den von Google (Location Services). Dieser ruft zwar die Callbackfunktion in meiner Activity auf, aber er lässt sie in seinem Thread laufen und der Prozess wird nach meinen intensiven Beobachtungen auch nie unterbrochen, weder wenn die App in den Hintergrund geht und nicht sichtbar wird noch wenn die Activity destroyed wird mit dem Back-Button. Selbst wenn das Display aus geht, wird die Callbackfunktion regelmäßig aufgerufen und die Aufzeichnung vorgenommen, selbst nach Stunden. Also kann doch dieser Weg auch nicht ganz falsch sein, oder?

Mein Problem war ja auch nicht, dass der Prozess unterbrochen wird, sondern eher das Gegenteil, dass er nochmal gestartet wird wenn die Activity destroyed wird und onCreate nochmal ausgeführt wird und dann eine parallele Aufzeichung stattfindet die alles kaputt macht.

Ich habe jetzt mein Problem provisorisch so gelöst:

Ich habe keine Singletonklassen geschrieben, habe aber die logging_flag -Variable (und viele andere) static gemacht. Damit konnte ich das Problem lösen, dass die Variable nach so einem "Neustart" scheinbar 2 mal existiert hat.

Ich habe ein neues static Flag eingeführt, das gesetzt wird, wenn die erste Listenerregistrierung erfolgreich vorgenommen wurde und der Service gestartet ist. Bei so einem "Neustart" der Activity wird dann kein zweites mal der Service gestartet, weil das vorher abgefragt wird. Damit konnte ich auch das Problem mit der parallen Aufzeichnung lösen.

Problematisch könnte es werden, falls doch irgendwann mal der Service abgeschaltet werden würde. Den Fall hat die App garnicht vorgesehen, weil er bisher noch nie aufgetreten ist. Ich würde eine solche Abfrage schon gerne hier oder da einbauen, habe aber bisher nicht herausgefunden, wie ich das machen kann (ich habe in den Docs keine geeignete Methode finden können).

Link

Als Workaround würde mir nur der umständliche Weg eines Threads einfallen, der eine zeitliche Überwachung vom Aufruf der Callbackfunktion macht. Vielleicht hast du ja noch eine Idee

Antworten

Empfohlene Artikel