Was ist ein Event Bus und warum ist er so praktisch?
Eine typische Android-App besteht in der Regel aus einer nicht kleinen Menge an Activities, Fragments und Services. Diese Komponenten der App müssen in vielen Fällen miteinander kommunizieren, um sich zum Beispiel gegenseitig mit Events und Updates zu versorgen.
Ein einfaches Beispiel dafür sind Push-Nachrichten. Angenommen die App empfängt über einen BroadcastReceiver einen Push und liefert diesen in einen Hintergrundprozess der App weiter (in der Regel ein IntentService). Jetzt steht man vor der Aufgabe, wie man von dieser Stelle aus genau die Teile der App informiert, für die der Empfang des Pushes von Bedeutung ist. Zum Beispiel weil man im User Interface durch ein grafisches Element andeuten will, dass eine neue Nachricht angekommen ist oder man den Datenbank-Layer auffordern möchste, die Daten des Pushes zu speichern.
Eine Möglichkeit dieses Problem zu lösen sind normale Referenzen auf die jeweiligen Fragments oder Activities. Aber will man das? Eher nicht. Man würde damit feste Beziehungen zwischen diesen Komponenten eingehen und ein späteter Austausch von Teilen der App wäre mehr als umständlich. Von der Null-Pointer-Gefahr mal ganz zu schweigen.
Android selbst bringt von Haus aus ein Event-System mit, das als solches vielleicht zunächst gar nicht zu erkennen ist: Android bietet die Möglichkeit Activities über Intent-Extras und Fragments über Bundles mit den entsprechenden Daten zu starten. Aber auch hier ist man nicht wirklich flexibel: Man muss auch diese Activities & Fragments wieder fest verdrahten und ist auch etwas eingeschränkt bei der Wahl der Datentypen.
Und genau an dieser Stelle kommt der Event Bus ins Spiel. Ein Event Bus ist quasi nichts anderes als ein Kommunikationskanal, in den man zum einen reinhören und zum anderen reinrufen kann. Am Beispiel von oben erklärt: Eine Komponente der App, die wartet bis ein Push ankommt, horcht in den Bus hinein. Der Receiver, der den Push empfängt, ruft bei Empfang die jeweiligen Infos in den Bus. Sprich wir haben eine klassische Publish/Subscriber-Architektur, ähnlich den Listenern, aber generischer zu nutzen.
GreenRobot, Otto und Guava
Für Android gibt es drei Frameworks um mit einem Event Bus zu arbeiten. Guava von Google ist ein EventBus für Java, der auch, aber nicht nur, für Android genutzt werden kann. Der GreenRobot EventBus und Square’s Otto sind speziell für Android angepasste Variante von EventBus Systemen.
Die Frage, ob man Otto oder den GreenRobot einsetzt, ist teilweise mehr ein Glaubenskrieg als ein faktisches Abwegen. GreenRobot wirbt zwar mit vielen Features, die Otto nicht unterstützt, teilweise sind diese aber auch aus Gründen der Einfachheit nicht in Otto enthalten (z.B. lässt sich die Delivery der Events von Background Threads in den Main Thread mit recht wenig Code auch hier verwirklichen).
Was man nun verwendet, ist unserer Meinung nach Geschmackssache. Wir arbeiten sehr gerne mit Otto, da die einfache Verwendung in Kombination mit dem guten Ruf, den die Android Bibliotheken von Square haben, uns immer wieder überzeugt haben. Ich bin mir aber relativ sicher, dass wir genauso mit Greenrobot glücklich geworden wären.
Beispiel: Ein EventBus mit Otto
Greifen wir das Beispiel von oben auf und implementieren beispielhaft einen EventBus, der in einem Fragment generierte Events an die darunter liegende Activity schickt.
Es bietet sich an, den EventBus in einem Singleton bereit zu stellen, um nicht bei jeder Verwendung das Objekt neu zu initialisieren.
public class CortexBus { private static Bus instance; private CortexBus() {} public synchronized static Bus getInstance() { if (instance == null) { instance = new Bus(); } return instance; } }
Im Fragment wollen wir die Events verschicken. Als Beispiel nehmen wir eine Kurznachricht, die dieses Fragment z.B. über eine HTTP Schnittstelle empfangen hat. Hier sind beliebig komplexe Events möglich, da jeder Event über eine eigenen Klasse (ein einfaches POJO) repräsentiert wird. Diese sieht in unserem Beispiel wie folgt aus:
public class MessageReceivedEvent { public long messageId; public String message; public MessageReceivedEvent(long messageId, String message) { this.messageId = messageId; this.message = message; } }
Diesen Event können wir nun über den Bus an alle registierten (siehe unten) Subscriber schicken, ohne diese direkt kennen zu müssen:
public class CardFragment extends Fragment { private CortexBus bus; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); bus = CortexBus.getInstance(); // code, um die nachricht zu empfangen, usw. // long messageId = ... // String message = ... bus.post(new MessageReceivedEvent(messageId, message)); // ... weiterer Code ... } }
In der Activity wollen wir auf eingehende Events hören. Wir initialisieren den Bus über den Singleton und erstellen eine Methode mit der @Subscribe Annotation, die die jeweiligen Events empfängt. Welchen Event diese Methode empfängt, wird über die Methodensignatur und die Annotation entschieden. Der Name der Methode ist hierbei nicht wichtig und kann frei gewählt werden. Die Activity muss sich dazu im onResume() und onPause() jeweils an- bzw. abmelden, damit die Events überhaupt empfangen werden.
public class MainActivity extends FragmentActivity { private Bus bus; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); bus = CortexBus.getInstance(); } @Override protected void onResume() { super.onResume(); bus.register(this); } @Override protected void onPause() { super.onPause(); bus.unregister(this); } @Subscribe public void onPushReceived(PushReceivedEvent event) { // dein event handling passiert an dieser Stelle // .... } }
Das ist alles, was für ein einfaches Event Bus System nötig ist. Man erreicht damit ein sehr angenehmes und stabiles Loose Coupling der Komponenten seiner App. Vor allem wenn die eigene Anwendung komplexer wird, kann man sich damit sehr viel Ärger und NPE-Schmerz ersparen.