Entkopplung mit Events

Ein Ausweg aus der Multithread-Hölle (Sie wissen schon, die mit dem fröhlichen Bad in siedenden Race conditions) ist die Entkopplung mit Events. Statt einen linearen Programmablauf zu denken, der streckenweise aus wichtigen Gründen in verschiedenen Threads abläuft, denken Sie lieber an herumgereichte Events oder, allgemeiner: Nachrichten. Ein Message-Broker läuft dazu im Hintergrund und reicht Nachrichten herum. Das entspricht einem Publish-Subscribe-Entwurfsmuster. Der entscheidende Vorteil: Die Nachricht “gehört” immer nur jenem Programmteil (oder Thread), der gerade aktiv ist. Es gibt keinen gleichzeitigen Zugriff mehrerer Threads auf das gleiche Nachrichtenobjekt. Auch der Message-Broker interessiert sich nicht mehr für eine Nachricht, sobald er sie zugestellt hat. Am Ende der Verarbeitung wird einfach eine neue Nachricht mit dem Ergebnis der Berechnung auf gleiche Weise zurück geschickt.

Publish und Subscribe

Sie wissen sicher: Viele größere Software-Systeme arbeiten längst mit Microservices und Message-Brokern wie Apache Kafka, die Nachrichten herumreichen. Aber das geht auch in Android, und Sie können damit leicht und elegant Arbeit in den Hintergrund verlagern. Statt mit AsyncTask, Thread, Handler und runOnUiThread können Sie einfach EventBus verwenden – tun Sie vielleicht eh, denn die Library hat sich in unzähligen Apps bewährt:

dependencies {
implementation 'org.greenrobot:eventbus:3.2.0'
}

Meist verwenden Sie EventBus, um Nachrichten zwischen UI-Komponenten, Fragmenten und Activities oder Services auszutauschen. Aber da Sie per Annotation festlegen können, ob ein EventHandler im Vorder- oder Hintergrund aufgerufen wird, können Sie auch sehr einfach eine saubere Background-Task-Verarbeitung umsetzen:

EB ist eine Abkürzung für EventBus.getDefault()

Links, im Main Thread, schicken Sie (z.B. nach einem Knopfdruck des Nutzers) eine CalculationStartMsg los, nix weiter. Die Message ist ein POJO, das alle nötigen Daten enthält, um die gewünschte Berechnung zu starten. Diese Nachricht (oberer Kaffeefleck) stellt EventBus im Hintergrund zu (siehe @Subscribe-Annotation). Wohlgemerkt ist der UI-Thread völlig unbeteiligt, er macht nach dem EB.post() gar nichts mehr bzw. wartet auf weitere Eingaben.

Die Berechnung im Hintergrund erzeugt eine neue Nachricht mit dem Ergebnis der Berechnung, ResultMsg (unterer Kaffeefleck), und überstellt es dem EventBus. Dieser stellt es der passenden onMessageEvent-Funktion im Main-Thread zur Verfügung, die wiederum das Ergebnis in der UI darstellt.

Async im Pool

Falls Sie oft längere Berechnungen im Hintergrund durchführen, verwenden Sie statt ThreadMode.BACKGROUND lieber ThreadMode.ASYNC. Denn während erstere Variante nur einen Thread verwendet, und mehrere Operationen daher nacheinander verarbeiten muss, benutzt ASYNC einen ThreadPool und kann daher problemlos mehrfach und für länger dauernde Berechnungen (wie Netzwerkzugriff) eingesetzt werden.

Beachten Sie immer den Android-Lifecycle: Beide Klassen (die blaue und die orange) müssen bereits instanziiert sein, sonst können sie keine Events empfangen (). Entweder die Worker-Klasse wird in onCreate der Activity (blau) erzeugt, oder alle Funktionen liegen sogar in der gleichen Activity-Klasse. EventBus kann im Gegensatz zu (expliziten) Broadcasts keine neuen Objekte erzeugen. Natürlich müssen alle beteiligten Klassen sich bei EventBus registrieren (mit EventBus.getDefault().register(this)).

In den Messages können Sie beliebige serialisierbare Daten übertragen, auch größere Mengen. Die Latenz beträgt wenige Millisekunden.

tl;dr: EventBus-ähnliche Architektur löst auf elegante Weise viele Multithreading-Probleme, da sie auf gleichzeitige Zugriffe auf ein und dieselben Ressourcen prinzipiell verzichtet. Das bedeutet maximale Entkopplung, weniger Abhängigkeiten und weniger Probleme. Mit ganz einfachen Mitteln. Investieren Sie Ihre wertvolle Zeit lieber in wichtigere Dinge, zum Beispiel Vermeiden von Sicherheitslücken…

Prohibition für Saugrobby!

Was muss ich da lesen? Besoffene Saugroboter?

Jetzt mal unabhängig von der Frage, ob es schlimm oder lustig ist, wenn Saugrobby wie ein verwirrter Hamster immer im Kreis fährt oder länger als sonst zum Reinigen der Wohnung braucht: Kann ja mal passieren, dass beim Abschlusstest eines Updates irgendwas übersehen wird, nicht wahr?

Ich will auch gar nicht über schlechte Testbarkeit meckern oder spekulieren, wie hoch die technische Schuld der womöglich nicht tip-top sauberen Software der betroffenen Roombas des Herstellers iRobot ist (dear iRobot, if you need help here, drop me a message!). Aber der Anlass ist willkommen für die regelmäßige Erinnung an die inhärente Fehlerfortpflanzung bei Software:

Menschen machen nunmal Fehler, das ist menschlich. Unterläuft beispielsweise einem Frisör ein Fehler, rennt hinterher ein Kunde mit doofen Haaren herum. Unterläuft einem Programmierer ein Fehler, so sind viel, viel mehr, schlimmstenfalls Millionen Nutzer betroffen, nämlich alle, die diese Software verwenden oder den fraglichen Code bei einer Sitzung auf einer Cloud-Instanz durchlaufen, falls es sich um eine Webanwendung handelt.

Während der Frisör deshalb mit einem minimalen Korrektiv auskommt (z.B. dem Kunden den Spiegel hinter den Kopf halten und fragen, ob’s gefällt), muss die Software deutlich höhere Hürden überwinden, um in die freie Wildbahn entlassen zu werden. Da ist zunächst mal die Suite von Unit Tests (Sie haben doch Unit-Tests, oder?), Integrationstests auf einer Testumgebung und die Abnahme auf einer Staging-Umgebung bzw. weitere Ende-zu-Ende-Tests, sei es automatisiert oder manuell. Im Idealfall jedenfalls. Eine Testabdeckung von 100% aller Fälle ist jedoch utopisch. Das gilt umso mehr, wenn Endgeräte im Spiel sind, die über individuelle Daten verfügen (z.B. Aufzeichnungen über die Geometrie zu saugender Räume). Die kann man nicht alle testen. Geht nicht.

Also sind halt bisweilen ein paar Staubsauger-Bots besoffen.

Software wird von Menschen geschrieben, die nicht perfekt sind. Folglich kann auch das Produkt nicht perfekt sein. Deshalb wird Software immer ein Restrisiko mit sich bringen. Es mag bei guten Programmierern (die mein Buch gelesen haben) klein sein, aber nie Null. Wer von Software Wunder erwartet, übersieht den menschlichen Faktor. Wer den menschlichen Faktor übersieht, kalkuliert Kosten für Fehlerbehebung oder Wartung nicht hinreichend in die Wirtschaftlichkeitsanalyse ein – und gelang möglicherweise zu einem Ergebnis größer als Null und ist später überrascht, wenn er draufzahlt.

Disclaimer: Nein, dies ist keine pauschale Entschuldigung für Bugs. Schon gar nicht für solche, die durch guten Code und sauberes Testen vermeidbar gewesen wären. Es ist der ausdrückliche Wunsch nach realistischen Einschätzungen.

Wer die Anfälligkeit von Software mit einrechnet, kommt nämlich auch nicht auf so drollige Ideen wie z.B. autonome Drohnen mit tödlichen Waffen oder diskriminierende Algorithmen für die Sichtung von Bewerbungsunterlagen, Anwendungen also, die ein bisschen weniger witzig sind als besoffene Roboter.

tl;dr: Vermeiden Sie Fehler – aber tun Sie nicht so, als gäbe es keine.