Googles Android/ Intents oder "Ich hätte gern den Zucker"

Aus Wikibooks
Zur Navigation springen Zur Suche springen

Zurück zu Googles Android

Allgemein[Bearbeiten]

Mittlerweile kennen wir Activities, ihren Aufbau und ihren Lebenszyklus. Aber mit einer Activity alleine kommt man in den meisten Anwendung eher nicht aus. Also, wie kommen wir nun von einer Activity zur nächsten? Die Antwort ist das Konzept von „Intents“. Kurz gesagt stellen Intents einen indirekten Nachrichtendienst dar, mit dem es möglich ist, Activities und Services aufzurufen und Broadcast-Receiver über Ereignisse zu informieren. Aber warum nun „indirekt“? Stellen Sie sich folgendes Szenario vor:

„Sie haben bei sich zu Hause eine Vorratskammer, in der Sie Gewürze, Mehl, Haushaltswaren usw. lagern. Nun brauchen Sie z. B. ein Paket Zucker. Was machen Sie? Sie gehen einfach in die Kammer und holen sich den Zucker. Genauso funktioniert das Android-Betriebssystem bzw. Intents nicht. Im Falle von Android wartet vor der Vorratskammer ein Butler, dem Sie sagen was Sie möchten. Dieser holt Ihnen den Zucker und übergibt Ihnen diesen.“

Was heißt das für uns? Wenn wir auf Kernelemente von Android (den Zucker) zugreifen wollen, müssen wir ein Intent-Objekt erstellen (den Butler), dieses mit Informationen befüllen (bitten den Zucker zu holen) und dieses an den Kontext weitergeben (in die Vorratskammer schicken).

Die gerade genannten Kernelemente sind

Der Kontext versucht, mit den im Intent enthaltenen Informationen, die gewünschte (explizite) oder eine passende (implizite) Komponente zu finden und dem Absender des Intents zukommen zu lassen. Von den drei möglichen Typen, die an einem Intent teilnehmen können, kennen wir bisher nur Activities. In den beiden folgenden Abschnitten werden wir uns anhand einiger beispielhafter Activities den Unterschied zwischen expliziten und impliziten Intents klarmachen. Damit haben wir Intents grundsätzlich verstanden. Aspekte von Intents, die für Services und Broadcast-Receiver spezifisch sind, besprechen wir in den jeweiligen Spezialkapiteln.

Die Android-Projekte, die wir im Kapitel Activities entwickelt haben, bestehen aus nur einer Activity. So etwas kommt auch in der Praxis vor, doch umfassen viele Apps mehrere Activities. In diesem Kapitel umfasst unser Projekt zwei Activities. Um diese Komponenten miteinander zu verbinden brauchen wir Intents.

Doch zunächst legen wir ein neues Android-Projekt an. Wir nennen es IntentDemo und lassen uns von Eclipse auch gleich eine Activity erzeugen, die wir PrimaryActivity nennen wollen.

Explizite Intents[Bearbeiten]

Wir duplizieren den Java-Code, der zu PrimaryActivity gehört, und nennen die Klasse SecondaryActivity. Die erste Activity verzeichnet Eclipse für uns im Manifest, bei der zweiten müssen wir selbst Hand anlegen. Der application-Teil des Manifestes sollte also etwa so aussehen:

  <application android:icon="@drawable/icon" android:label="@string/app_name">
    <activity android:name=".PrimaryActivity"
              android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity android:name=".SecondaryActivity"
              android:label="@string/app_name">
    </activity>
  </application>

Da es in diesem Kapitel wirklich nur um die Wirkungsweise von Intents geht, brauchen wir kein kunstvolles Layout. Durch umbenennen und kopieren des Standard-Layouts verschaffen wir uns zwei Dateien primary.xml und secondary.xml. Die zweite lassen wir wie sie ist, die erste ergänzen wir noch um einen Button:

  <Button android:id="@+id/next"
          android:layout_height="wrap_content"
          android:layout_width="wrap_content"
          android:text="Next"/>

In die onCreate-Methoden der beiden Activity-Klassen fügen wir noch – so wie wir es gelernt haben – Code ein, um die zugehörige Views zu laden. Den Listener für den Button definieren wir zwar, lassen ihn aber zunächst wirkungslos:

PrimaryActivity.onCreate

  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.primary);
    Button next = (Button) findViewById(R.id.next);
    next.setOnClickListener(new View.OnClickListener() {
      public void onClick(View v) {
      }
    });
  }

SecondaryActivity.onCreate

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.secondary);
  }


Die beiden sehr einfachen Activities sehen wie folgt aus:


Beim Klick auf den mit „Next“ beschrifteten Button soll PrimaryActivity in den Hintergrund und SecondaryActivity in den Vordergrund wechseln. Und genau dafür brauchen wir Intents.

Wir definieren ein Objekt vom Typ Intent[1] und teilen dem Kontext mit, dass SecondaryActivity den Vordergrund übernehmen soll.

Den folgenden Code setzen wir also an das Ende von PrimaryActivity.onCreate:

  final Intent intent=new Intent(this, de.wikibooks.android.SecondaryActivity.class);
    next.setOnClickListener(new View.OnClickListener() {
      public void onClick(View v) {
        startActivity(intent);
 }
  });

Wir beobachten, dass wir beim Erzeugen des Intents den Packagenamen und den kompletten Klassennamen der Ziel-Activity mitgegeben. Die Methode startActivity[2] erbt die Klasse Activity von Context. Sie macht das, was ihr Name verspricht und bringt uns zur SecondaryActivity.

Wenn wir die App starten und „Next“ auslösen, wird anstandslos zur SecondaryActivity gewechselt. Diese Art von Intent nennt sich explizit, weil wir unserem Butler sehr präzise gesagt haben, was wir brauchen. Also nicht nur einfach „Zucker“, sondern „Alnatura Rohrohrzucker“.

Diese Vorgehensweise findet man auch in anderen Java-Frameworks: es gibt Methoden, denen man den Namen einer Klasse voll qualifiziert übergibt und die sich dann um die Objekterzeugung kümmern. Und genau diese enge Koppelung von Klassen hat sich bei Änderungen als unflexibel erwiesen. Nachteile, die auf der Hand liegen, sind:

  • Wenn wir in eine andere als die SecondaryActivity verzweigen wollen, müssen wir den Java-Code wieder anfassen.
  • Wir können nur Activities aus unserer eigenen Anwendung ansteuern.
  • Die Activities (und natürlich die Services und Broadcast-Receiver) unserer Anwendung können nicht von anderen Anwendungen genutzt werden.

Dass es auch anders geht, sehen wir im folgenden Abschnitt.

Implizite Intents nutzen[Bearbeiten]

Bei impliziten Intents werden Ross und Reiter nicht genannt, sondern nur beschrieben. Wenn wir in der Klasse PrimaryActivity die folgende Intent-Definition

  final Intent intent=new Intent(this, de.wikibooks.android.SecondaryActivity.class);

austauschen durch

  final Intent intent=new Intent(Intent.ACTION_DIAL);

dann landen wir, wenn wir den Next-Button betätigen, in der Dial-Activity des Android-Systems.


Wir kennen nicht den Namen der Klasse, der zu der im Intent hinterlegten Activity gehört. Das brauchen wir auch nicht, da hinterlegt ist, dass sie auch über die sogenannte Action abgerufen werden kann.

Der Vorteil sollte klar sein: Wenn die Dialer-Activity durch einen anderen Dialer ausgetauscht werden soll, muss einfach nur die Zuordnung der Aktion zur Activity geändert werden. Wir werden noch sehen, dass dazu einfache Eingriffe in die Manifest Datei reichen. Es ist also nur eine einzige Änderung nötig, um alle ACTION_DIAL-Intents[3] auf den neuen Dialer umzulenken.

Hinsichtlich der Zuordnung gelten die folgenden Regeln:

  • Jede Action kann keiner oder mehreren Activities zugeordnet sein
  • Jede Activity kann mehreren Aktionen zugeordnet sein.

Die Aktionen sind dabei einfache Texte, von denen sehr viele – wie auch ACTION_DIAL – bereits als Konstante im Typ Intent definiert wurden. Wenn es zu einer Aktion keine passende Activity gibt, meldet das System einen Fehler. Das passiert etwa, wenn wir die Intent-Definition

  final Intent intent=new Intent(Intent.ACTION_DIAL);

durch

  final Intent intent=new Intent("ACTION_FEHLER");

ersetzen: Es gibt keine Activities mit der Aktion ACTION_FEHLER. Stehen mehrere Activities zur Auswahl überlässt Android dem Anwender die Auswahl. So gibt es beispielsweise zur Action ACTION_VIEW[4] viele Activities.

Wenn wir unseren Intent wie folgt definieren

  final Intent intent=new Intent(Intent.ACTION_VIEW);

bekommen wir ein Angebot an Activities:


Je nach Anwendung kann es wichtig sein, vor dem Start des Intents zu wissen, ob das Ziel wirklich eindeutig beschrieben ist. Dazu können wir aber den Kontext befragen:

  boolean isUnique(intent){
    return 1==getPackageManager().queryIntentActivities(intent, PackageManager.GET_INTENT_FILTERS);
  }

Wie schon gesagt, kann es zu einer Action mehrere Activities geben. Um hier spezifischer zu werden, können wir bei der Definition der Intents noch eine URI übergeben. Beispiel für URIs sind die bekannten URLs wie

http://de.wikibooks.org/

aber auch weniger gängige Ausprägungen wie

tel:(+49) 7723 9200

  • An dem Teil der URI, der vor dem Doppelpunkt steht, kann der Kontext auch erkennen, welche der ACTION_VIEW-Activities benötigt wird.
  • Der Teil, der dem Doppelpunkt folgt, kann dann als Daten verwendet werden.

Beim Start des Intents

  final Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse("http://de.wikibooks.org/"));

wird also die Web-Browser-Activity angezeigt und die Seite „de.wikibooks.de“ geladen. Dagegen wird mit

  final Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse("tel:(+49) 7723 9200"));

die Dialer-Activity gestartet und gleich die Nummer „(+49) 7723 920 0“ gewählt. Da es sehr viele Aktionen gibt, können sie nicht alle aufgezählt werden. Mehr Informationen und Beispiele gibt es in der API-Dokumentation zum Typ Intent oder auch auf der Seite www.openintents.org.


Neben der Angabe einer Aktion können wir mit Hilfe der Methode addCategory[5] auch eine oder mehrere Kategorien angeben, denen die Activity zugeordnet sind. Auf diese Weise können wir die Anzahl der in Frage kommenden Ziele weiter einschränken und auch an unsere Anforderungen anpassen.

  • So kennzeichnet Intent.CATEGORY_LAUNCHER[6] eine Activity, die auch die Start-Activity einer App ist;
  • Die Kategorie Intent.CATEGORY_TAB[7] identifiziert dagegen solche Activities, die als Teil einer Activity einen eigenen Reiter bekommen kann.

Actions und Intent-Filter[Bearbeiten]

Natürlich wollen wir implizite Intents nicht nur benutzen, sondern auch unsere eigenen Komponenten über implizite Intents nutzbar machen. Um etwa unsere SecondaryActivity auch impliziten Intents zur Verfügung zu stellen, reicht eine einzige Änderung im Manifest:

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

Neu ist hier das Tag <intent-filter>. Hier wird die Beschreibung der Activity durchgeführt. Wir kennzeichnen sie hier mit unserer eigenen Aktion „de.wikibooks.android.SECONDARY“, es wäre aber kein Problem gewesen, dafür eine der Aktionen aus der Klasse Intent zu verwenden.

Zu jedem Intent-Filter muss es mindestens eine Aktion und mindestens eine Kategorie geben. Mehrere sind aber durchaus möglich. Als Kategorie wird in den meisten Fällen bedeutungsfrei Intent.CATEGORY_DEFAULT[8] gewählt. Bereits jetzt können alle Android-Anwendungen (und nicht nur unsere eigene) mit impliziten Intents der Form

  final Intent intent=new Intent("de.wikibooks.android.SECONDARY")

zur SecondaryActivity gelangen.

Wir überzeugen uns jetzt davon, dass unsere Activities auch „von außen“ erreichbar sind. Im Kapitel Activities haben wir uns mit einem Android-Standard-Projekt beschäftigt und der (von Eclipse erzeugten) Activity den Namen StartActivity gegeben. In diesem Projekt gibt es auch ein Manifest. Dort wurde StartActivity bereits automatisch verzeichnet. Erst jetzt verstehen wir den Manifest-Eintrag

  <activity android:name=".StartActivity"
            android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>

vollständig: Die Activity hat die Kategorie CATEGORY_LAUNCHER[9] und ist über die Action ACTION_MAIN[10] implizit erreichbar.

Implizite Intents gehören zu den gewöhnungsbedürftigsten Komponenten in der Android-Programmierung. Wenn man ihre Vorteile aber einmal verstanden hat, mag man sie nicht mehr missen.

Datenübergabe mit Intents[Bearbeiten]

Dass wir jetzt zwischen einzelnen Activities wechseln können, ist schon ein Fortschritt. Noch mehr Möglichkeiten erhalten wir, wenn wir es schaffen, dass der Ziel-Activity Daten aus der Quell-Activity übergeben werden. Wir können uns vorstellen, dass eine Datenübergabe auch für andere Komponenten wie Services oder Broadcast-Receiver interessant sein kann.

Die Datenübergabe erarbeiten wir uns wieder an einer einfachen Anwendung mit zwei Activities, die wir bei Bedarf modifizieren und auch für andere Komponenten ganz ähnlich verwenden können. Bei der App aus dem Activities gibt der Anwender einen Text ein, der dann in Großbuchstaben umgewandelt wird. Diese Anwendung verteilen wir jetzt auf PrimaryActivity und SecondaryActivity. Die erste übernimmt die Eingabe und sendet den Text per Klick auf einen Button an die zweite Activity. In die Layout-Datei primary.xml fügen wir noch ein Eingabefeld ein:

  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:orientation="vertical"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">
    <EditText android:id="@+id/word"
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:inputType="text"/>
    <Button android:id="@+id/next"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="Next"/>
  </LinearLayout>

Die Activities sehen jetzt wie folgt aus:

Wenn der Anwender den Button anklickt, wird der eingegebene Text abgelegt:

public void onClick(View v) {
secondary.putExtra("word", word.getText().toString());
startActivity(secondary);
}

Jeder Intent verfügt über ein Verzeichnis von Key-Value-Paaren, die auch mit an die Zielkomponente versendet werden. Dieses Verzeichnis hat den Typ Bundle[11] und wird auch als Extras bezeichnet.

Im Beispiel fügen wir den Extras mit der Methode putExtra[12] das Paar hinzu, dass aus dem Schlüssel "word", sowie dem Text besteht, den der Anwender eingegeben hat.

Außer diesem einen Paar könnten wir Bedarf noch mehr Informationen mitgeben. Diese werden dann in SecondaryActivity mit Hilfe der Methode getStringExtra[13] aus der Klasse Intent eingesammelt:

  String word=getIntent().getStringExtra("word");
  TextView view = (TextView) findViewById(R.id.tv);
  view.setText(word.toUpperCase());

wobei tv, die id der TextView ist, die wir in secondary.xml vereinbart haben. Den zu Intent, zu der die SecondaryActivity gehört, finden wir mit getIntent. Aus den Extras des Intents können wir dann unter Angabe des Schlüssels "word" auch wieder den zugehörigen Wert auslesen.

Pending-Intent[Bearbeiten]

Ein PendingIntent[14] Objekt ähnelt einem Intent mit einer Ausnahme. Das Objekt, das durch ein Pending Intent aktiviert wird, läuft unter den Rechten des Erzeugers der Pending Intent. Das ist sogar dann noch der Fall, wenn zum Beispiel die erzeugende Activity oder eben der erzeugende BroadcastReceiver nicht mehr existiert. Das ist offenbar ein interessantes Mittel für einen Receiver. Es gibt eine Reihe von statischer Factory-Methoden zur Erzeugung von PendingIntent-Objekten. Bsp.: getActivity(), getBroadcast(), getService(). Dieser Methoden erzeugen ein Pending Intent Objekt das bei dessen Nutzung jeweils eine Activity, einen Broadcast oder einen Service erzeugt – mit den Rechten des Erzeugers des Pending Intents! Man sollte sehr vorsichtig damit umgehen.

Einzelnachweise[Bearbeiten]

  1. http://developer.android.com/reference/android/content/Intent.html
  2. http://developer.android.com/reference/android/content/Context.html#startActivity(android.content.Intent)
  3. http://developer.android.com/reference/android/content/Intent.html#ACTION_DIAL
  4. http://developer.android.com/reference/android/content/Intent.html#ACTION_VIEW
  5. http://developer.android.com/reference/android/content/Intent.html#addCategory(java.lang.String)
  6. http://developer.android.com/reference/android/content/Intent.html#CATEGORY_LAUNCHER
  7. http://developer.android.com/reference/android/content/Intent.html#CATEGORY_TAB
  8. http://developer.android.com/reference/android/content/Intent.html#CATEGORY_DEFAULT
  9. http://developer.android.com/reference/android/content/Intent.html#CATEGORY_LAUNCHER
  10. http://developer.android.com/reference/android/content/Intent.html#ACTION_MAIN
  11. http://developer.android.com/reference/android/os/Bundle.html
  12. http://developer.android.com/reference/android/content/Intent.html#putExtra(java.lang.String,%20android.os.Bundle)
  13. http://developer.android.com/reference/android/content/Intent.html#getStringExtra(java.lang.String)
  14. http://developer.android.com/reference/android/app/PendingIntent. Stand 14. November 2010