Zurück zu Googles Android

Der Vielfalt der Programmiersprachen und Frameworks entsprechend gibt es viele Möglichkeiten, um graphische Benutzerschnittstellen (GUIs) zu entwickeln. Wenn wir in Java mit den Swing-Klassen arbeiten, definieren wir beispielsweise Container-Klassen, in die wir dann Steuerelemente (Widgets) wie Buttons oder Eingabefelder einfügen. Die Steuerelemente können dabei selbst Container sein, die andere Steuerelemente enthalten. Auf diese Weise ergibt sich eine hierarchische Struktur. Im .NET-Framework gibt es seit der Version 3.0 die Windows Presentation Foundation (WPF). Hier können die Steuerelemente in XML-Dokumenten hierarchisch strukturiert werden. Die eigentliche Logik, die die Interaktion mit dem Anwender steuert, wird dann in zusätzlichem Programmcode definiert. Auf diese Weise erhält man eine Trennung zwischen Präsentation und Anwendungslogik.

Activities brauchen Views

Bearbeiten

Die Android-API enthält nicht die Swing-Klassen aus dem Java-SDK. In Android-Anwendungen werden GUIs nach einem ähnlichen Konzept wie in der WPF definiert.

Die Darstellung der GUI wird in XML-Dateien durchgeführt; das hierarchische XML-Format trägt dabei der hierarchischen Struktur einer GUI besonders gut Rechnung. In einer Android Anwendung können wir auf diese GUI in Form von Objekten vom Typ View zugreifen. Diese View-Objekte reflektieren die Struktur und die Namensgebung, wie sie in der XML-Datei hinterlegt sind. Ein View haben wir in der Datei main.xml bereits im vorherigen Kapitel definiert.

Die eigentliche Anwendungslogik wird dann in Objekten vom Typ Activity hinterlegt. Eine Activity ist ein Objekt, das direkt an der Benutzerfront arbeitet:

  • Sie ist mit einer View verbunden und kann so die Buttons, Eingabefelder, Checkboxen und was es sonst noch so in der GUI gibt steuern.
  • Wenn ein Anwender sich entschließt mit der GUI zu interagieren, werden Events ausgelöst, auf die die Activity dann geeignet reagieren kann.

Grundsätzlich muss einer Activity nicht unbedingt eine View zugeordnet sein, doch verliert sie ohne View eigentlich ihre Existenzberechtigung. Die Zuordnung geschieht mit der Methode setContentView. Diese Vorgehensweise haben wir auch bereits im vorhergehenden Kapitel gesehen. Die Standardanwendung, die automatisch mit jedem Android-Projekt erzeugt wird, enthält eine Klasse, die von Activity erbt:

public class StartActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
  }
}

Wir werden im Laufe des Kapitels noch die Bedeutung der Methode onCreate kennenlernen. Uns interessiert zunächst nur ihr Inhalt: Im wesentlichen enthält sie den Aufruf von setContentView. Damit wird der Activity die View zugewiesen, die in main.xml definiert wurde. Die View ist dabei eine Ressource ähnlich wie ein Text aus der Datei strings.xml (siehe auch Ein Déjà-vu – Hello World). Wir wollen hier Objekte vom Typ Activity[1] genauer unter die Lupe nehmen, die Beschreibung von Views in XML-Dateien ist Gegenstand eines eigenen Kapitels. Da GUIs so ganz ohne Views ziemlich langweilig sind, werfen wir noch einmal einen Blick auf die Standard-View aus dem vorherigen Kapitel. Dort ist eine TextView enthalten, die für die Darstellung eines einfachen Textes verantwortlich ist:

<TextView  
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/hello"
/>

Zwar gibt es auch eine korrespondierende Java-Klasse namens TextView,[2] doch haben wir keine Möglichkeit an die Objektdarstellung der View zu kommen. Daher können wir den Text der View nicht programmgesteuert in unserer onCreate-Methode[3] anpassen. Um mit dem Objekt zu arbeiten, das zu dieser View gehört, müssen wir der View einen Namen geben:

<TextView  
  android:id="@+id/simpletv"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
/>

Ähnlich wie bei den Texten im letzten Kapitel sorgt das Android-Plugin der Eclipse-IDE dafür, dass wir jetzt in der Klasse R.id ein ganzzahliges Attribut simpletv haben, das unsere TextView eindeutig identifiziert. Zwar ist das Textfeld noch leer, doch können wir die View mit ihrer id und der Methode findViewById[4] im Universum der Objekte unserer Android-Anwendung wiederfinden:

TextView message = (TextView) findViewById(R.id.simpletv);

Die Methoden der Klasse TextView – eine Unterklasse des allgemeinen Typs View[5] – verwenden wir jetzt für die Steuerung. Den Text setzen wir wie folgt:

message.setText("Hello Android");

Die Interaktion mit dem Anwender

Bearbeiten

Zur Übung wollen wir zu unserer View noch ein Eingabefeld (Typ EditText)[6] und einen Button (Typ Button)[7] hinzufügen. Die Steuerelemente bekommen die id word bzw. upper; der Button soll programmgesteuert mit dem Text „To Upper“ beschriftet werden. Wer etwa Geduld hat und experimentierfreudig ist, kann sich etwa mit Hilfe der Dokumentation zur API an dieser kleine Aufgabe versuchen.

Wer ungeduldiger ist, fügt in main.xml vor der Definition von firsttv die folgenden Zeilen ein:

<EditText
  android:id="@+id/word"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:inputType="text"
/>
<Button
  android:id="@+id/upper"
  android:layout_height="wrap_content"
  android:layout_width="wrap_content"
/>

Der zugehörige Java-Code sieht wie folgt aus:

TextView view=(TextView)findViewById(R.id.firsttv);
EditText edit=(EditText)findViewById(R.id.word);
Button  upper = (Button)findViewById(R.id.upper);
upper.setText("To Upper");

Die App sollte im Emulator ungefähr so aussehen:

Wenn wir diese App starten, tut sich natürlich nichts, wenn wir den Button anklicken. Sein Click-Event wird zwar ausgelöst, läuft aber ins Leere. Wie bei der ereignisgesteuerten Programmierung üblich, können wir unserem Button einen Listener zuordnen. Dazu enthält der Typ View ein Interface namens OnClickListener[8] mit einer Methode onClick.[9] Dieses Interface implementieren wir mit einer anonymen inneren Klasse. Anonyme Klassen werden in Android-Anwendungen oft gebraucht und wer sie noch nicht kennt, sollte das schnell nachholen.

private TextView view;
private EditText edit;
private Button upper;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  view = (TextView) findViewById(R.id.simpletv);
  edit = (EditText) findViewById(R.id.word);
  upper = (Button) findViewById(R.id.upper);
  upper.setText("To Upper");
  upper.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
      String result=edit.getText().toString().toUpperCase();
      view.setText(result);
    }
  });
}

Die Organisation des Codes hat sich etwas geändert: Die Views werden nicht mehr in lokalen Variablen, sondern in Attributen gehalten.

So können wir sie in allen Methoden unserer Klasse StartActivity nutzen. Wirklich neu sind nur die letzten Zeilen der Methode. Wir sehen, dass ein Objekt vom Typ OnClickListener anonym erzeugt und dem Button mit Hilfe seiner Methode setOnClickListener[10] zugeordnet wird.

Die Funktionalität ist einfach: Der Inhalt des Eingabefeldes wird ausgelesen und in ein Objekt vom Typ String transformiert. Dieser Text wird dann in Großbuchstaben umgewandelt und der Variablen result zugewiesen. Anschließend wird der Inhalt von result in unserer TextView dargestellt.

Das ist sicher keine spannende Anwendung, doch haben wir so bereits einige wesentliche Aspekte bei der Arbeit mit Activities kennen gelernt.

  • Die Zuordnung einer View.
  • Der Zugriff auf die Objekte, die in der View enthalten sind.
  • Die Arbeit mit Widgets, die alle durch jeweils eine View repräsentiert werden.
  • Die Verarbeitung von Anwendereingaben mit Hilfe von Listenern.

> Activities sind meldepflichtig <

Bearbeiten

Zwar werden wir die Manifest-Datei, die ja zu jeder Android-Anwendung gehört noch in einem eigenen Kapitel kennen lernen, doch sollten wir bereits jetzt wissen, dass dort auch alle Activities unserer Anwendung verzeichnet sein müssen. Wenn wir einen Blick auf die Datei AndroidManifest.xml werfen, sehen wir, dass Eclipse das in unserem Fall bereits erledigt hat:

 <application android:icon="@drawable/icon" android:label="@string/app_name">
   <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>
 </application>

Ein Teil der Angaben ist selbsterklärend, einen anderen Teil wie intent-filter lernen wir noch kennen, wenn es um Manifeste oder Intents geht.

Der meiste Code, der in einer Activitiy anfällt, hat zwar mit Views zu tun, doch gibt es auch Eigenschaften einer Activity, die unabhängig von Views sind. So können wir in einer Activity beispielsweise Code hinterlegen, der ausgeführt wird, wenn die Akkuleistung unseres Gerätes nachlässt. Ebenso können Ereignisse abgefangen werden, die ausgelöst werden, wenn bestimmte Tasten des Gerätes gedrückt werden.

Exemplarisch behandeln wir den Fall, dass ein Anwender die Menü-Taste seines Gerätes drückt. In diesem Fall wird das so genannte Kontext-Menü angezeigt, sofern wir eines definiert haben. Dafür ist die Methode onCreateOptionsMenu[11] zuständig, die wir wie folgt überschreiben wollen:

private static final int BOLD_MENU = Menu.FIRST;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
  menu.add(Menu.NONE, BOLD_MENU, Menu.NONE, "Large Font");
  return super.onCreateOptionsMenu(menu);
}

Der Name der Methode add[12] deutet ja schon sehr darauf hin, dass wir hier einen Menüeintrag hinzufügen. Er wird mit dem Text „Large Font“ beschriftet und soll später die Möglichkeit bieten, den Inhalt der TextView vergrößert darzustellen. Der erste Parameter von add kann genutzt werden, um Einträge zu gruppieren, der dritte um den Eintrag in eine Reihenfolge einzuordnen. Wer sich dafür interessiert, sollte mal einige Einträge hinzufügen und diese Parameter etwa variieren. Wir haben es uns hier leicht gemacht: Gruppierung und Reihenfolge sind uns in diesem Beispiel egal. Die id des ersten (und einzigen) Menüeintrages merken wir uns in der Konstanten BOLD_MENU.

Den zum Menü gehörenden Code definieren wir, indem wir die Methode onOptionsItemSelected[13] überschreiben. Typischerweise werden hier in einer switch/case-Anweisung die einzelnen Menüeinträge abgeklappert:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
  switch (item.getItemId()) {
    case BOLD_MENU:
      view.setTextSize(21);
    default:
  }
  return super.onOptionsItemSelected(item);
}

In unserem Fall ist das natürlich sehr einfach gehalten: Wenn ein Anwender den Menüpunkt „Large Font“ auswählt wird die Größe des Textes einfach auf 21 erhöht. Nachdem wir das einmal ausprobiert haben, schadet es sicher nicht dieses rudimentäre Menü unter Verwendung der API-Dokumentation des Typen Menu selbstständig zu erweitern.

Views, wie wir sie hier kennengelernt haben, sind mit Abstand die gebräuchlichsten Instrumente, um mit Anwendern zu interagieren. Wir lernen jetzt, dass wir unabhängig von XML-Dateien etwa kleine Textfelder anzeigen können.

Tatsächlich bedürfen diese Objekte vom Typ Toast[14] keiner Layoutdateien, sie brauchen noch nicht einmal eine Activitiy. Um ein Ergebnis zu sehen, reicht es, wenn wir den folgenden Code etwa an das Ende unserer onCreate-Methode schreiben.

Toast toast=Toast.makeText(this, "in onCreate", Toast.LENGTH_LONG);
toast.show();

Der erste Parameter, den die Factory-Methode makeText[15] braucht, ist vom Typ Context[16]; einem der Basistypen von Activity. Da aber auch Typen wie Service oder BroadcastReceiver von Context abgeleitet sind, ist der Einsatz einer Activity nicht zwingend erforderlich.

Für den dritten Parameter gibt es die beiden Möglichkeiten

  • Toast.LENGTH_LONG[17] und
  • Toast.LENGTH_SHORT[18]

für die Anzeigedauer der Textnachricht; angezeigt wird der Text aber erst nach Aufruf der Methode show.

Das Leben einer Activity

Bearbeiten

Wir benutzen den Typ Toast jetzt, um einige grundlegende Erfahrungen mit Activities in Android zu sammeln. Jede Activity hat einen der folgenden Zustände:

  • aktiv und im Vordergrund: Dies ist der Zustand, in dem wir unsere Beispiel-Activity immer erlebt haben.
  • passiv: Die Activity ist nicht mehr sichtbar und eine andere Anwendung steht im Vordergrund. Dies passiert etwa, wenn wir in unserer Anwendung mit mehreren Activities arbeiten und zwischen diesen hin und her navigieren. Um mit mehreren Activities zu arbeiten benötigen wir noch mehr Wissen über Intents, das wir aber erst in einem eigenen Kapitel erwerben werden.
  • aktiv, aber teilweise überdeckt: Zu den beiden extremen Zuständen 'aktiv' und 'passiv' gibt es noch ein Zwischenstadium. Die Activity steht nicht im Vordergrund und ist noch sichtbar, sie wird aber teilweise durch eine andere Activity überdeckt.

Activities wechseln in typischen Android-Use-Cases häufig ihren Zustand. Immer, wenn eine Zustandsänderung eintritt, wird mindestens eine der so genannten Lifecycle-Methoden aufgerufen.

Die erste dieser Methoden kennen wir bereits: Sie wird bei der Erzeugung der Activity aufgerufen; die Methoden onStart und onRestart immer dann, wenn eine nicht sichtbare Activity in den Vordergrund wechselt. Wechselt die Activity in den Hintergrund wird ihre onPause-Methode ausgeführt und onStop zusätzlich dann, wenn sie nicht mehr sichtbar ist. Bei jedem Wechsel in den Vordergrund wird onResume aufgerufen. Wenn die Activitiy zerstört wird, wird onDestroy ausgelöst. Es ist sehr interessant diesen Zustandswechsel zu beobachten. Dazu überschreiben wir in unserer Klasse StartActivity jede der sieben Lifecycle-Methoden so, dass sie einen Toast anzeigt.

Für die Methode onCreate haben wir das schon gemacht. Dabei dürfen wir nicht vergessen, dass die überschriebenen Lifecycle-Methoden immer auch ihre Implementierung in der Basisklasse aufrufen muss. Diesem Umstand haben wir beim Überschreiben von onCreate bereits mit der Zeile

super.onCreate(savedInstanceState);

Rechnung getragen.

Wenn wir diese App laufen lassen, sehen wir beispielsweise auch, dass auch nach dem Betätigen der Return-Taste am Gerät, die Activity angehalten wird, und somit onPause und onStop aufgerufen werden. Die Activity wird aber nicht beendet, sondern läuft im Hintergrund weiter. Auf den Aufruf von onDestroy warten wir im Normalfall vergebens. Wenn das System allerdings eine Engpass mit seinem Speicher hat, kann es passieren, dass eine Activity über die Klinge springen muss.

Zunächst werden natürlich passive Activities zerstört und dann erst teilweise sichtbare; in keinem Fall wird aber die Activity im Vordergrund kassiert. Dieses Verfahren hält möglichst viele Activities am Leben und erspart uns so deren häufige Erzeugung und somit einige Laufzeit. Nur Activities, die zerstört werden, müssen neu erzeugt werden und durchlaufen dann wieder onCreate.

Zustand retten

Bearbeiten

Da bei der Zerstörung eines Objektes auch sein ganzer Zustand verloren geht, kann es durchaus sinnvoll sein, diesen Zustand so abzuspeichern, dass er im Fall der Vernichtung beim nächsten Aufruf von onCreate wiederhergestellt werden kann.

Dafür bietet die Android-API den Typ SharedPreferences:[26] In onPause können mit diesem Typ Werte einiger wichtiger Attribute abgelegt und in onCreate wieder ausgelesen werden. Am Namen SharedPreferences erkennt man ja bereits, dass dieser Typ eigentlich zum Speichern von Einstellungen gemacht ist.

Wir wollen uns seine Funktionsweise aber in einem anderen Zusammenhang klarmachen: Wenn ein Anwender in unserer Beispielanwendung einen Text eingibt, soll der Text erhalten bleiben und nach dem Wiederbeleben der Activity wieder angezeigt werden, selbst wenn die Activity zerstört oder das Gerät abgeschaltet wird. Wir vereinbaren dazu ein private Attribut

private SharedPreferences prefs;

Das Attribut initialisieren wir in der onCreate-Methode . Dann schauen wir nach, ob in prefs ein Text aus dem früheren Leben der Activity erhalten ist. Diesen Text weisen wir dann dem Eingabefeld edit zu:

edit = (EditText) findViewById(R.id.word);
prefs = getSharedPreferences("StartActivityPrefs", MODE_PRIVATE );
String userText = prefs.getString("userText","");
edit.setText(userText);

Die Activity ist immer dann zerstörungsgefährdet, wenn die Methode onPause aufgerufen wird. Daher ist diese Methode auch der richtige Ort um alles zu retten, was wichtig ist:

@Override
public void onPause(){    
  super.onPause();
  SharedPreferences.Editor editor = prefs.edit();
  editor.putString("userText", edit.getText().toString());
    editor.commit();
}

Dieser kurze Streifzug durch die Klasse Activity gibt uns einige Eindrücke über die Möglichkeiten dieses Typs. Selbstverständlich hat Activity noch weitere Fähigkeiten, die man ruhig einmal mit Hilfe der zugehörigen API-Dokumentation ausprobieren sollte.

Einzelnachweise

Bearbeiten
  1. http://developer.android.com/reference/android/app/Activity.html
  2. http://developer.android.com/reference/android/widget/TextView.html
  3. http://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)
  4. http://developer.android.com/reference/android/app/Activity.html#findViewById(int)
  5. http://developer.android.com/reference/android/view/View.html
  6. http://developer.android.com/reference/android/widget/EditText.html
  7. http://developer.android.com/reference/android/widget/Button.html
  8. http://developer.android.com/reference/android/view/View.OnClickListener.html
  9. http://developer.android.com/reference/android/view/View.OnClickListener.html#onClick(android.view.View)
  10. http://developer.android.com/reference/android/view/View.html#setOnClickListener(android.view.View.OnClickListener)
  11. http://developer.android.com/reference/android/app/Activity.html#onCreateOptionsMenu(android.view.Menu)
  12. http://developer.android.com/reference/android/view/Menu.html#add(int,%20int,%20int,%20java.lang.CharSequence)
  13. http://developer.android.com/reference/android/app/Activity.html#onOptionsItemSelected(android.view.MenuItem)
  14. http://developer.android.com/reference/android/widget/Toast.html
  15. http://developer.android.com/reference/android/widget/Toast.html#makeText(android.content.Context,%20java.lang.CharSequence,%20int)
  16. http://developer.android.com/reference/android/content/Context.html
  17. http://developer.android.com/reference/android/widget/Toast.html#LENGTH_LONG
  18. http://developer.android.com/reference/android/widget/Toast.html#LENGTH_SHORT
  19. http://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)
  20. http://developer.android.com/reference/android/app/Activity.html#onStart()
  21. http://developer.android.com/reference/android/app/Activity.html#onRestart()
  22. http://developer.android.com/reference/android/app/Activity.html#onResume()
  23. http://developer.android.com/reference/android/app/Activity.html#onPause()
  24. http://developer.android.com/reference/android/app/Activity.html#onStop()
  25. http://developer.android.com/reference/android/app/Activity.html#onDestroy()
  26. http://developer.android.com/reference/android/content/SharedPreferences.html