Programmieren auf Android

Sascha Henke

Dalvik Virtual Machine

Das Android-System beruht in erster Linie auf der Dalvik Virtual Machine1, einer von Dan Bornstein entwickelten Portierung der Java Virtual Machine (JVM) für mobile Endgeräte. So beruht diese im Gegensatz zur JVM auf einer virtuellen Registermaschine anstatt eines übliche Kellerautomaten. Weiterhin werden beim Kompilieren alle .class Dateien in eine .dex (Dalvik Executable) Datei zusammengeführt, wobei einige Optimierungen vorgenommen werden, um zum Beispiel den Speicherbedarf zu minimieren.

Activities

Eine Activity ist der elementare Bestandteil einer Applikation um eine Interaktionsmöglichkeit mit dem Nutzer zu schaffen. Innerhalb einer Activity kann mittels setContentView die Oberfläche angelegt bzw. importiert werden, welche üblicherweise den gesamten Schirm ausfüllt oder in eine andere Activity eingebettet ist. In der Lebensdauer einer Activity können verschiedene Ereignisse auftreten, welche den Status der Activity ändern, so kann z.B. ein Druck auf den Home Button, oder ein eingehender Anruf eine vorher im Vordergrund befindliche Activity in den Hintergrund schieben. Um diese Einflüsse in der Applikation berücksichtigen zu können, gibt es den Activity Lifecycle. Wenn nun eine Klasse von Activity erbt, so kann diese die Methoden onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy() und onRestart() überschreiben um auf einen Wechsel des Zustands angemessen reagieren zu können. Der Aufruf von onStop() sei hier gesondert aufgeführt, da dieser nicht zwangsläufig direkt vom Nutzer ausgeführt werden muss. onStop() kann ausgeführt werden wenn eine weitere Activity durch onResume() wieder in der Vordergrund tritt und diese überdeckt. Dies kann ebenfalls durch eine neu gestartete Activity geschehen.

Abb. 1: Activity Lifecycle

Das vorangehende Bild2 zeigt die verschiedenen Phasen einer Activity und die möglichen Nachfolgezustände. Der Lebenszyklus einer Activity geschieht zwischen dem ersten Aufruf von onCreate() und dem Aufruf von onDestroy(), welcher alle genutzten Ressourcen der Activity wieder freigibt. Der sichtbare Lebenszyklus geschieht zwischen den Aufrufen von onStart() und onStop(), diese Methoden können in der Laufzeit der Activity mehrfach aufgerufen werden, je nachdem wie oft diese in den Vorder- bzw. Hintergrund geschoben wird. Während dieser Zeit kann der Nutzer die Activity im Vordergrund sehen auch wenn in dieser Zeit noch ggf. keine Interaktionen möglich sind. Eine Interaktionsmöglichkeit ist lediglich zwischen den Aufrufen onResume() und onPause() möglich.

Um innerhalb einer Activity in eine andere zu wechseln werden sogenannte Intents benötigt. Ein Intent ist hierbei ein abstraktes Objekt das zum Starten einer Activity, Benachrichtigung eines Broadcastreciever oder zum Kommunizieren mit einem Service dient. So kann mit dem folgenden Beispiel:

Activity activity = this;
intent = new Intent(currentActivity.this, nextActivity.class);
startActivity(intent);
activity.finish();

von der aktuellen Activity currentActivity in die nextActivity gewechselt werden. Mit dem Aufruf von activity.finish() wird sichergestellt, dass die aktuelle Activity beendet wird, bzw. onDestroy() aufgerufen wird, damit diese vom Garbage-Collector abgeräumt werden kann und der Speicherplatz wieder freigegeben wird. Der Zeitpunkt des Abräumens ist auch hierbei vom System bestimmt.

Services

Ein Service3 ist eine Komponente die es ermöglicht langwierige Operation ohne Nutzerinteraktion oder Funktionalität für andere Applikationen bereitzustellen. Hierbei sei jedoch erwähnt das ein Service kein separater Prozess ist. Standardmäßig läuft der Service innerhalb des selben Prozesses wie die Applikation dem er angehört.

Ein Service ist somit eine Hilfsmöglichkeit dem System mitzuteilen das eine Operation im Hintergrund ausgeführt werden soll. Dazu dient die Funktion Context.startService() um eine Verzahnung des Services mit der Applikation zu schaffen, bis dieser explizit angehalten wird. Per Context.bindService() kann man eine persistente Verbindung zu einem Service erhalten. Falls dieser noch nicht gestartet wurde, so wird bei dem Aufruf der Service angestoßen und bleibt in diesem Falle so lange bestehen, wie noch eine Verbindung vorhanden ist.

Manifest

Um eine Activity oder einen Service starten zu können, müssen diese in dem Android-Manifest aufgeführt sein. Das Android Manifest ist ein XML Dokument, welches die essentiellen Bestandteile einer Android Applikation enthält. Dieses Dokument enthält:

  • Den Java Package namen
  • Komponenten der Applikation:
    • Activities
    • Services
    • Broadcast Reciever
    • Content Provider
  • Permissions, die das Nutzen von geschützten API-Elementen erlaubt
  • Minimales API-Level
  • Libraries die enthalten sein müssen

Die Permissions 4 in einem Manifest erlauben einer Applikation den Zugriff auf geschützte API-Schnittstellen, über die der Nutzer bei der Installation der Applikation informiert wird. Diese Permissions umfassen den Zugriff auf GPS-Daten, Kamera, Internet und einiges mehr.

User Interface

Die Nutzeroberfläche des Androids besteht aus zwei Grundelementen:

  • Views: Die Basiselemente der Oberfläche. Die Klasse View stellt die Basisklasse der sog. "widgets" dar, welche die voll implementierten UI-Elemente repräsentieren.
  • Viewgroups: Eine Viewgroup stellt einen Container für die verschiedenen Views dar und dient als Basisklasse für die "Layouts", welche die Positionierung der Views ermöglichen.

Viewgroups können beliebig verschachtelt werden und somit einen Hierarchiebaum aufgebauen. Beim Zeichnen der Oberfläche fordert vom Wurzelknoten abwärts jede Viewgroup ihre Kindknoten auf sich zu Zeichnen. Hierbei können die Views zwar eine Größe und Position definieren, jedoch hängt das finale Aussehen eines Elementes stärker von den Parametern des zugehörigen Vaterknoten, also dem Layout, ab.

Vordefinierte Layouts:

  • Linear Layout, ermöglicht eine lineare Anordnung der Kindelemente.
  • Relative Layout, ermöglicht die Positionierung von Elementen relativ zueinander.
  • Table Layout, stellt die Kindelemente in Zeilen und Spalten dar.
  • Grid View, die Kindelemente werden automatisch durch einen ListAdapter in ein zweidimensionales, scrollbares Grid eingefügt.
  • Tab Layout, diese Viewgroup besteht aus drei Elementen. Dem TabHost, welcher der Wurzelknoten in der Hirarchie ist, den Tabwidget der innerhalb eines Framelayout den Inhalt der View enthält. Durch den Tabhost kann man nun in diesem Layout je nach Implementation zwischen verschiedenen Views oder Activities hin und herwechseln.
  • List View, die Kindelemente werden hierbei als skrollbare Liste dargestellt.

Der typische Weg eine Oberfläche in Android zu definieren ist per XML-Layout-Datei. Hierbei werden in XML-Syntax die Viewgroups und Views geschachtelt und mit Parametern für die Ausmaße, Position und weiterem versehen. Ein wichtiger Parameter innerhalb der beiden Elemente ist die ID. Mit dieser ID kann innerhalb der XML-Datei Bezug auf ein anderes Element hergestellt werden. Weiterhin kann über die ID innerhalb einer Activity auf ein View-Element bzw. dessen Events reagiert werden oder es können in einer Viewgroup dynamisch neue Elemente hinzugefügt werden.

Eine weitere Eigenheit in der UI-Gestaltung auf dem Android ist die Benutzung von NinePatch Grafiken. Soll eine View, z.B. ein Button, mit einem Bild versehen werden so ist die Nutzung von NinePatch Bildern empfohlen, da diese neben dem üblichen Bild auch noch einen 1 Pixel großen Rand besitzen, der das Verhalten beim Skalieren der Grafik definiert.

Abb. 2: NinePatch
Das nebststehende Bild 5 zeigt die Erzeugung einer NinePatch Grafik. In der Mitte sieht man das eigentliche Bild, der Pixelrand am linken und oberen Rand definiert den Bereich der bei einer Skalierung vergrößert bzw. verkleinert werden darf. Weiterhin kann optional am unteren und rechten Rand der Grafik noch ein Padding-Bereich angegeben werden. So wird nun durch den oberen und linken Rand der skalierbare Bereich und durch den rechten und unteren Rand der Bereich definiert in welchem der Inhalt der View angezeigt werden soll. Wenn der Inhalt nicht in diese Padding Region passt, so wird die View soweit skaliert bis der Bereich groß genug ist.

Beispiel

Um das Anlegen einer Oberfläche per XML und dynamischen hinzufügen zu verdeutlichen sei hier ein Beispiel aufgezeigt, welches zur Darstellung des Inventories auf dem Android dient. Zunächst wurde ein XML-Layout erstellt welches als Grundgerüst dient.

<?xml version="1.0" encoding="utf-8"?>
 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent" 
	android:layout_height="fill_parent"
	android:orientation="vertical"
	android:keepScreenOn="true">
 
	<HorizontalScrollView android:id="@+id/inventory_scrollview"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent">
 
		<LinearLayout android:id="@+id/inventory_root"
			android:layout_width="fill_parent"
			android:layout_height="fill_parent"
			android:orientation="horizontal"/>	
 
	</HorizontalScrollView>
 
</RelativeLayout>

Hier dient als Wurzel-Viewgroup ein Relative Layout welches durch die Parameter fill_parent den gesamten zur Verfügung stehenden Platz ausfüllt. Weiterhin ist nun eine HorizontalScrollView verschachtelt mit einem LinearLayout, dies führt dazu, dass wenn nun dynamische Elemente in das LinearLayout eingefügt werden, so sind diese durch die HorizontalScrollView immer erreichbar, selbst wenn die Größe des Bildschirms nicht reicht um alle Elemente gleichzeitig anzuzeigen. Um nun dynamisch Elemente in das Inventory hinzufügen zu können wird zunächst einmal die ID der Viewgroup benötigt in dem die neuen Elemente eingefügt werden sollen. So kann man nun in der Inventory Activity per

root = (RelativeLayout) RelativeLayout.inflate(this, R.layout.inventory, null);
[...]
LinearLayout content = (LinearLayout)root.findViewById(R.id.inventory_root);

die Oberfläche zeichnen und eine Referenz zur obersten Viewgroup erzeugen und anhand dieser das LinearLayout bekommen in dem die neuen Elemente eingefügt werden sollen. In diesem Falle wird nun eine Schleife mit allen vorhandenen Items durchlaufen und diese in die View eingefügt:

[...]
for(Class<? extends Item> type: inventoryClasses){
    [...]
    entry = inventoryModel.getInventory(type);
    LinearLayout ll = (LinearLayout)View.inflate(this, R.layout.inventory_item, null);
 
    // ImageButton des Userinventories
    imageButton = (ImageButton)ll.findViewById(R.id.inventory_user_image);
    imageButton.setTag(type);
    imageButton.setImageResource(entry.imageResource);
 
    [...]
    content.addView(ll);
    [...]
}

Hierbei wird nun in der Schleife ermittelt um welches Item es sich handelt und mit Hilfe des inventory_item Layouts wird dann die Oberfläche eines Gegenstandes erzeugt. So wird nun mit Hilfe von findViewByID weiter innerhalb des inventory_item Layouts navigiert um den enthaltenen imageButton dem Typ entsprechend anzupassen. Bei diesem Handelt es sich um den Knopf zum aktivieren des Items auf Nutzerseite.

Werkzeuge

Emulator

Das Android SDK umfasst einen Emulator, welcher das Testen einer Applikation ohne ein vorhandenes Gerät ermöglicht. Dieser Emulator ist eine auf der freien virtuellen Maschine Quick Emulator (QEMU) basierende Applikation, die eine Simulation einer mobilen ARM Maschine mit kompletten Android-Betriebssystem zur Verfügung stellt. Der Emulator ahmt hierbei die meisten Eigenschaften eines Smartphones nach. Um nun eine Applikation auf verschiedenen Geräten simulieren zu können, bietet der Emulator die Möglichkeit mehrere Android Virtual Devices (AVDs) anzulegen, die verschiedene Hardware, so wie Auflösung, SD-Karten Support und so weiter besitzen.
Der Emulator bietet weiterhin die Möglichkeit vordefinierte GPS-Daten an das simulierte Gerät zu senden, sodass innerhalb einer Applikation der LocationManager bei einer Anfrage auf die GPS-Position diese vordefinierten Koordinaten ausgibt. Auf diese Weise ist es auch möglich die Lagesensoren des Gerätes zu manipulieren. Jedoch unterliegt der Emulator auch einigen Limitierungen:

  • Keine richtigen Anrufe sind emulierbar.
  • Keine Unterstützung von USB Verbindungen.
  • Keine Kameraaufzeichnungen möglich.
  • Keine SD-Karten Einlegen oder Auswurf simulierbar.
  • Keine Bluetooth Unterstützung.
  • Keine Möglichkeit den Batterieladestatus zu verändern.

Übersicht

Zusätzlich zum Emulator enthält das Android-SDK einige Werkzeuge zum Entwickeln bzw. Debuggen einer Android-Applikation. Nachfolgend ein kleiner Ausschnitt der gängigsten Tools die im Installationsverzeichniss des Android-SDK zu finden sind:

  • Dalvik Debug Monitor: Hiermit können die einzelnen Threads auf dem Emulator überwacht werden, sowie die aktuelle Heap-Belegung, aktuelle Allokationen und Debug-Logs eingesehen werden.
  • Hierarchy Viewer: Hiermit kann die Baumstruktur des aktuell betrachteten Layouts angezeigt und überprüft werden.
  • Draw 9-patch: Erlaubt das Erstellen von NinePatch Grafiken.
  • sqlite3: Ermöglicht das Auslesen und Manipulieren der internen SQLite Datenbank
  • Monkey: Der Monkey dient zum Stress-testen einer Applikation. Dieser generiert eine zufällige Reihe von Events, so wie Klicks, Gesten, Druck auf Hardwareknöpfen usw..

Weiterhin wird ein Eclipse Plugin zur Verfügung gestellt, welches einige Tools wie den Dalvik Debug Monitor und den Emulator in die Entwicklungsumgebung integrieren.


1 http://www.dalvikvm.com/ - Dalvik Virtual Machine

2 http://developer.android.com/reference/android/app/Activity.html - Android API, Beschreibung des Activity Lifecycle

3 http://developer.android.com/reference/android/app/Service.html - Android API, Beschreibung des Service

4 http://developer.android.com/reference/android/Manifest.permission.html -Android API, Auflistung der Permissions

5 http://developer.android.com/guide/topics/graphics/2d-graphics.html - Android API, 2D Graphics Guide


Page last modified on March 21, 2011, at 03:57 PM