Googles Android/ BroadcastReceiver

Zurück zu Googles Android

Broadcasts

Bearbeiten

Auf einem Mobiltelefon passiert ein Menge: SMS-Nachrichten und Telefonanrufe treffen ein, WLAN-Netze tauchen auf und verschwinden, die Uhrzeit ändert sich – selbst wenn nichts dergleichen passiert, geht irgendwann der Akku zur Neige. All dies sind Beispiele für Ereignisse die behandelt werden können. Aus eigener Erfahrung wissen wir, dass Einiges bereits vom Android-System übernommen wird: Wenn etwa ein Anruf eintrifft, wird die Telefon-Activity aufgerufen.

In diesem Kapitel erfahren wir, wie wir solche Ereignisse

  • mit selbstentwickelten Komponenten verarbeiten und
  • selbst auslösen können.

GUIs werden im Allgemeinen entwickelt, um dem Anwender Interaktion zu ermöglichen. Wenn er beispielsweise einen Button drückt, dann arbeitet ein Listener, der zuvor mit dem Button verbunden wurde, diesen Klick ab.

Diese Vorgehensweise hat sich etwa innerhalb homogener Java-Anwendungen bewährt, kann aber für die oben beschriebenen Android-Szenarien nicht verwendet werden: Eine Android-Anwendung ist modular aufgebaut und besteht aus mehreren DVM-Prozessen, die wir nicht mit Java-Bordmitteln auf Ereignisse ihrer Kollegen lauschen lassen können. Eine Android-Komponente kann aber DVM-übergreifend Broadcasts versenden, die andere Komponenten dann verarbeiten können. Beispielsweise versendet die Komponente, die eine SMS empfängt, einen Broadcast aus. Dieser Broadcast wird in einen Intent (siehe das Kapitel „Intents“) eingebettet, dem wir über seine Extras auch Daten übergeben können. Wenn ein geeigneter Intent-Filter vorliegt, kann der Broadcast von einer speziellen Komponente, dem Broadcast-Receiver empfangen werden.

Der Receiver kann die Bundle-Daten – und dazu gehört etwa auch der Inhalt einer SMS – auslesen und verarbeiten. Broadcast-Receiver gehören zusammen mit Activities und Services zu den wichtigsten Komponenten einer Android-Anwendung.

Um Broadcasts zu abonnieren, müssen wir

  • den Namen der zugehörigen Action kennen
  • einen entsprechenden Intent-Filter im Manifest definieren
  • einen Broadcast-Receiver für diese Action entwickeln

BroadcastReceiver – ein einfaches Beispiel

Bearbeiten

Das probieren wir gleich mal aus. Ansprechende Beispiele kann man für den SMS-Empfang entwickeln. Da es hier aber wieder um das grundsätzliche Verständnis des Typs BroadcastReceiver[1] geht, backen wir kleinere Brötchen und beschränken uns auf den Broadcast, der versendet wird, wenn jemand die Uhrzeit am Telefon ändert. Die zugehörige Action heisst Intent.ACTION_TIME_CHANGED[2], der hinterlegte Text ist android.intent.action.TIME_SET.

Wir legen ein neues Android-Projekt an oder nehmen einfach ein Bestehendes und fügen in das Manifest die folgende Receiver-Definition mit einem Intent-Filter ein:

<receiver android:name=".TimeChangedReceiver"
          android:label="@string/app_name">
  <intent-filter>
    <action android:name="android.intent.action.TIME_SET" />
  </intent-filter>
</receiver>

Eine Kategorie ist nicht erforderlich. Die Klasse TimeChangedReceiver aus dem <receiver>-Tag sind wir natürlich noch schuldig. Sie muss eine Unterklasse von BroadcastReceiver sein. Wir zeigen einfach nur einen Toast (siehe das Kapitel „Intents“), wenn ein Broadcast eingetroffen ist:

public class TimeChangedReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    Toast toast = Toast.makeText(context, "Time Changed", Toast.LENGTH_LONG);
    toast.show();
  }
}

Uns fallen zwei Dinge auf:

  • Die Methode onReceive[3] ist für die Entgegennahme und Verarbeitung des Broadcasts verantwortlich.
  • Am ganzen Ablauf ist keine Activity beteiligt. Tatsächlich können wir eine App entwickeln, die nichts anderes macht, als Broadcasts zu verarbeiten und gar nicht mit dem Anwender kommuniziert.

Jeder BroadcastReceiver lebt nur so lange, wie seine onReceive-Methode aktiv ist. Nach dem Abschluss der Methode haben wir auf die zugehörige Instanz keinen Zugriff mehr, insbesondere nimmt er keine weiteren Broadcasts mehr entgegen. Das ändert natürlich nichts an der Zuordnung der Broadcast-Action zu der BroadcastReceiver-Klasse, so wie sie im Intent-Filter definiert wurde. Für jeden Broadcast wird ein neues Objekt erzeugt. Die Zuordnung bleibt bestehen, so lange die Anwendung installiert ist!


Wenn wir nach der Installation der App die Uhrzeit im Emulator ändern, sollte das ungefähr so wie in der folgenden Abbildung aussehen.

Grundsätzlich

  • ist ein Receiver nicht nur auf eine Action festgenagelt, sondern kann auch verschiedene Broadcast-Arten verarbeiten.
  • kann der zum Broadcast gehörende Intent auch Daten enthalten. In dieser Hinsicht hat ACTION_TIME_CHANGED aber wenig zu bieten.

Wir verändern unsere Anwendung jetzt so, dass auch diese beiden Optionen umgesetzt werden: Ähnlich einfach wie ACTION_TIME_CHANGED wird Intent.ACTION_TIMEZONE_CHANGED[4] immer dann ausgelöst, wenn in den Einstellungen unseres Android-Systems die Zeitzone geändert wird.

Anders als im ersten Beispiel, enthalten die Extras des Intents Daten in Form der neuen Zeitzone. Diese holen wir ab und zeigen sie an.

@Override
public void onReceive(Context context, Intent intent) {
  String action=intent.getAction();
  String msg="";
  if (action.equals(Intent.ACTION_TIME_CHANGED))
    msg="Time Changed";
  else if(action.equals(Intent.ACTION_TIMEZONE_CHANGED))
    msg="time-zone";
  else
    msg="Unknown Broadcast";
  Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
}

Unser Receiver kann jetzt zwei verschiedene Arten von Actions verarbeiten. Im Fall einer ACTION_TIMEZONE_CHANGED wird aus den Extras der zum Schlüssel „time-zone“ gehörende Wert ausgelesen und in einer Toast-Nachricht angezeigt. Der Fall der ACTION_TIME_CHANGED wird praktisch genauso wie im ersten Beispiel behandelt. In der Manifestdatei muss der Broadcast für ACTION_TIMEZONE_CHANGED angefordert werden.

Komplexe Broadcast-Verarbeitung

Bearbeiten

Die Grundzüge der Broadcast-Verarbeitung haben wir an zwei einfachen Beispielen kennen gelernt. So ganz lebensnah ist das aber noch nicht:

Nur in den wenigen Fällen reicht die Anzeige eines Toasts. Häufiger findet eine komplexere Verarbeitung des Broadcasts statt. Und genau hier treten zwei Probleme auf:

  • Wenn wir eine Activity in der onReceive-Methode starten wollen, um mit unseren Benutzern zu interagieren, werden wir – wenn wir nicht aufpassen – wenig Freude haben. Der DVM-Prozess zu dem der Receiver und somit auch die Activity gehört, wird nämlich zusammen mit der onReceive-Methode beendet. Das Gleiche passiert, wenn der Broadcast zur Verarbeitung einen Thread startet. Auch seine Lebenszeit ist durch die onReceive-Methode begrenzt.
  • Wenn wir keinen Thread oder keine Activity für die Verarbeitung starten, sondern den Broadcast an Ort und Stelle im gleichen Thread behandeln, lauert Android bei einer Laufzeit von mehr als 10 Sekunden mit Abschüssen in Form von 'Application Not Responding'.

Für beide Probleme gibt es aber Lösungen. Im Kapitel „Activities, Tasks und Launch Modes“ haben wir gelernt, wie wir Activities in einem eigenen DVM-Prozess starten können. Ein Implementierungsmuster sehen wir in der folgenden onReceive-Methode:

@Override
public void onReceive(Context context, Intent intent) {
  String action=intent.getAction();
  if (action.equals(Intent.ACTION_TIME_CHANGED))
    Toast.makeText(context, "Time Changed", Toast.LENGTH_LONG).show();
  else if(action.equals(Intent.ACTION_TIMEZONE_CHANGED))
    showTimezone(context, intent.getStringExtra("time-zone"));

}

private void showTimezone(Context context, String msg) {
  Intent intent=new Intent("de.wikibooks.android.TIMEZONEACTIVITY");
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  intent.putExtra("Message", msg);
  context.startActivity(intent);
}

Wir sehen, dass für ACTION_TIMEZONE_CHANGED-Broadcasts unsere eigene Methode showTimezone aufgerufen wird.

Ihr übergeben wir neben dem Kontext noch den Wert der neuen Zeitzone als Text. Die Intent-Verarbeitung erfolgt wie im Kapitel „Intents“) beschrieben; neben der Activity wurde dem Intent noch das Flag FLAG_ACTIVITY_NEW_TASK[5] mitgegeben, um die Activity in einem separaten DVM-Prozess einzubetten. Selbstverständlich müssen wir die zu unserer selbstdefinierten Action TIMEZONEACTIVITY gehörende Klasse noch definieren und im Manifest erfassen.

AndroidManifest.xml

<activity android:name=".TimezoneActivity"
          android:label="@string/app_name">
  <intent-filter>
    <action android:name="de.wikibooks.android.TIMEZONEACTIVITY" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>        
</activity>

TimezoneActivity.java

public class TimezoneActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.timezone);
    String msg=getIntent().getStringExtra("Message");    
    TextView view = (TextView) findViewById(R.id.zone);
    view.setText("New Timezone: "+msg);
  }
}

Der Inhalt der Layout-Datei timezone.xml sollte klar sein und wird hier nicht weiter diskutiert: Die Datei enthält nur eine einfache TextView[6] mit zone als id. Auch die Entgegennahme der Extras in der onCreate-Methode verläuft geschmeidig.

Im Kapitel über Services (siehe Services) haben wir gesehen, dass wir auch Services in eine separate DVM einbetten können.

Nun ist es aber nicht die feine englische Art den Benutzer mit einer plötzlich erscheinenden Activity bei seiner 'User-Experience' zu stören. Geeigneter sind hier die Notfications, die wir auch noch in einem eigenen Kapitel (siehe Notifications) kennen lernen werden.

Dynamische Receiver

Bearbeiten

Wenn wir einen Receiver über einen Intent-Filter im Manifest mit einer Action verbunden haben, dann gilt dieser Bund bis dass die zum Receiver gehörende Anwendung vom Gerät entfernt wird. Diese Art der Broadcast-Receiver wird daher auch statisch genannt. Statische Receiver sind aber nicht immer das, was wir brauchen. Oft soll die Bindung einer Action an einen Receiver nur so lange, wie eine bestimmte Komponente bestehen. Hier gibt es die Möglichkeit den Intent-Filter 'programmatisch' zu definieren und dann die Zuordnung innerhalb der Komponente und nicht im Manifest durchzuführen.

Im folgenden Beispiel definieren wir einen Receiver als innere Klasse einer Activity. In der onResume-Methode der Activity werden Receiver und Intent-Filter erzeugt und über die registerReceiver-Methode mit der Activity verbunden. In der onPause-Methode werden Activity und Receiver wieder entkoppelt. So wird erreicht, dass der Receiver so lange lebt und Broadcasts entgegennehmen kann, wie die Activity im Vordergrund ist. Erst beim Wechsel in den Hintergrund, wird die Zuordnung aufgehoben und der Receiver der Garbage-Collection übergeben.

private class TimeChangedReceiver extends BroadcastReceiver{
  @Override
  public void onReceive(Context context, Intent intent){
    Toast.makeText(context, "Time  Tick", Toast.LENGTH_LONG).show();
  }
}

private BroadcastReceiver receiver;
@Override
public void onResume() {
  receiver=new TimeChangedReceiver();
  IntentFilter filter =new IntentFilter(Intent.ACTION_TIME_TICK  );
  this.registerReceiver(receiver, filter);
  super.onResume();
}
@Override
public void onPause() {
  this.unregisterReceiver(receiver);
  receiver=null;
  super.onPause();
}

Während ihrer Lebenszeit können dynamische Receiver also, anders als statische Receiver, mehrere Broadcasts entgegenehmen.

Broadcasts versenden

Bearbeiten

Bisher haben wir Broadcasts nur empfangen. Der Versand ist aber für uns kein Problem. Er erfolgt ganz ähnlich wie mit der Methode startActivity zum Start einer Activity im Rahmen der Intent-Verarbeitung mit Hilfe der Methode startBroadcast, die ebenso wie startActivity von der Klasse Context geerbt wird.

Im folgenden Beispiel versenden wir einen ACTION_TIME_CHANGED[7]-Broadcast aus einer beliebigen Activity. Wenn unser kleiner Demo-Receiver TimeChangedReceiver jetzt noch installiert ist, sollte er gleich beim Start dieser Activity anschlagen und einen Toast anzeigen.

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
  sendBroadcast(intent);
}

Wir werden uns noch mit dem Empfang von SMS-Nachrichten beschäftigen und werden dann Gelegenheit haben, unser Wissen über Broadcasts weiter anzuwenden und an praxisnäheren Beispielen zu vertiefen.

Geordnete Broadcasts

Bearbeiten

Zusätzlich zur Methode sendBroadcast gibt es noch sendOrderedBroadcast.[8] Auch hier wird ein Intent als Parameter übergeben, der neben der Beschreibung des Intents auch weitere Daten in Form von Extras enthalten kann.

Ein Broadcast, der mittels sendBroadcast versendet wurde, wird an alle Empfänger (quasi) gleichzeitig übermittelt. Ein Broadcast, der mittels sendOrderedBroadcast verschickt wurde, wird dagegen sequentiell an Receiver verschickt. Begonnen wird bei denen mit der höchsten Priorität. Bei Receivern mit gleicher Priorität ist die Reihenfolge willkürlich.

Die Priorität kann durch ein Tag <android:priority>[9] innerhalb der Definition des Intent-Filters in der Deklaration des <receiver>-Tags[10] definiert werden. All diese Deklarationen sind aber optional.

Broadcasts, die mit sendOrderedBroadcast gesendet wurden, bieten einzelnen Receivern die Möglichkeit, Nachrichten anzuhängen, die der nächste Empfänger lesen kann. Dazu dienen die Methoden setResult[11] (und Varianten davon) und getResult[Code[12]|Data|Extras]. Damit lassen sich Abarbeitungsketten aufbauen und Zwischenergebnisse und Status übermitteln. Das ist ein bekanntes und sinnvolles Entwurfselement für Software (siehe auch das Design Pattern Zuständigkeitskette).

Einzelnachweise

Bearbeiten
  1. http://developer.android.com/reference/android/content/BroadcastReceiver.html
  2. http://developer.android.com/reference/android/content/Intent.html#ACTION_TIME_CHANGED
  3. http://developer.android.com/reference/android/content/BroadcastReceiver.html#onReceive(android.content.Context,%20android.content.Intent)
  4. http://developer.android.com/reference/android/content/Intent.html#ACTION_TIMEZONE_CHANGED
  5. http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK
  6. http://developer.android.com/reference/android/widget/TextView.html
  7. http://developer.android.com/reference/android/content/Intent.html#ACTION_TIMEZONE_CHANGED
  8. http://developer.android.com/reference/android/content/Context.html#sendOrderedBroadcast%28android.content.Intent,%20java.lang.String%29. Stand 14. November 2010
  9. http://developer.android.com/reference/android/R.styleable.html#AndroidManifestIntentFilter_priority. Stand 14. November 2010
  10. http://developer.android.com/reference/android/R.styleable.html#AndroidManifestReceiver. Stand 14. November 2010
  11. http://developer.android.com/reference/android/content/BroadcastReceiver.html#setResult%28int,%20java.lang.String,%20android.os.Bundle%29. Stand 14. November 2010
  12. http://developer.android.com/reference/android/content/BroadcastReceiver.html#getResultCode%28%29. Stand 14. November 2010