Für Android-Mobiltelefone lassen sich mit der Eclipse-Entwicklungsumgebung und dem Google-SDK ziemlich einfach eigene Programme schreiben. Wir zeigen am Beispiel eines GPS-Trackers, wie das geht.
README
Dieser Artikel führt Sie am Beispiel eines GPS-Trackers in die Programmierung mit dem Android-SDK und Eclipse ein.
Mit dem Android-SDK und dem Eclipse-Plugin Handy-Anwendungen zu entwickeln ist recht einfach, denn die Tools funktionieren weitgehend reibungslos, sind leicht bedienbar, die API ist gut durchdacht und dokumentiert. Dieser Workshop zeigt, wie Sie einen GPS-Tracker programmieren, der die Geo-Koordinaten des eigenen Wegs aufzeichnet und das Logfile auf einen Webserver hochlädt.
Um das Ganze nachzuvollziehen brauchen Sie nicht unbedingt ein Android-Handy, denn das Entwicklerpaket enthält auch einen Simulator. Neben dem Android-Entwicklungskit (Software Development Kit / SDK) bietet Google ein Eclipse-Plugin an, das dem Android-Entwickler die Arbeit erleichtert. Haben Sie die Eclipse-Umgebung in Version 3.3 oder 3.4 von [1] heruntergeladen und entpackt, wechseln Sie ins Verzeichnis eclipse
und geben dort den Befehl ./eclipse
ein.
Schließt man den blauen Begrüßungs-Reiter, zeigt Eclipse den normalen Workspace, der in mehrere Bereiche unterteilt ist. Das Android-Plugin installieren Sie, indem Sie mit dem Menüpunkt Help | Install New Software den Plugin-Manager starten. Geben Sie im oberen Feld https://dl-ssl.google.com/android/eclipse/
ein und klicken Sie dann rechts daneben auf Add. Eclipse aktualisiert seine Datenbank und bietet dann im Feld darunter die Developer Tools zur Installation an. Klicken Sie sie an und folgen Sie jeweils mit Next den weiteren Schritten des Wizards bisr zur erfolgreichen Installation.
Das SDK-Starterpaket laden Sie von der Website [2] und entpacken es, zum Beispiel im Home-Verzeichnis. Mittlerweile existieren mehrere Android-Versionen und für jede gibt es passende SDK-Dateien. Wenn Sie also zum Beispiel nur für Geräte mit Android 2.1 entwickeln wollen, laden Sie das passende SDK herunter, können aber auf die anderen verzichten. Der Artikel beschreibt im Folgenden die Installation und Nutzung unter Linux, Sie können aber auch unter Windows oder Mac OS X entwickeln.
Wechseln Sie zur Installation in das Tools-Unterverzeichnis des Android-SDK und führen Sie das Administrationsprogramm android
aus, mit dem Sie die eigentlichen SDK-Pakete herunterladen:
$ cd android-sdk-linux_86 $ ./tools/android Starting Android SDK and AVD Manager No command line parameters provided, launching UI.
Es erscheint das Fenster aus Abbildung 1, in dem Sie die gewünschten SDKs auswählen und installieren. Auf dem im Beispiel verwendete Mobiltelefon Vodafone 845 beispielsweise läuft Android 2.1. Aktualisieren Sie unter Installed Packages mit Update All die verfügbaren Pakete und wählen Sie dann die gewünschten für Download und Installation aus. Legen Sie abschließend noch unter Virtual Devices ein Simulatorprofil für die jeweilige Android-Version an.

Das Tool android
besitzt noch eine Menge weiterer Funktionen, zum Beispiel können Sie mit android update sdk
die SDKs aktualisieren.
Wenn Sie nun Eclipse starten, müssen Sie ihm noch über Window | Preferences mitteilen, wo die Android-Dateien liegen. Geben Sie im folgenden Fenster unter Android und SDK Location den passenden Pfad ein. Wenn Sie nun Window | Android SDK and AVD Manager aufrufen, sollten SDK und virtuelles Android-Gerät eingetragen sein. Klicken Sie nun auf File | New | Project oder das entsprechende Icon links oben im Eclipse-Fenster, finden Sie in der erscheinenden Liste den Android-Projekttyp.
Füllen Sie im folgenden Dialog die Felder für Namen, Klassennamen und die so genannte Activity (das einfach ein anderer Name für eine Android-Standardanwendung) aus (Abbildung 2), erzeugt Eclipse ein komplettes Gerüst für ein Android-Programm. Ein Klick auf den Button mit grünen Icon oder den entsprechenden Menüpunkt Run
startet das Programm auf dem Emulator. Haben Sie ein echtes Android-Handy angeschlossen, bietet Eclipse an, das Programm direkt auf der Hardware auszuführen.

Um Programme über USB auf einem echten Telefon zu installieren, müssen Sie eventuell noch ein paar kleinere Hürden überwinden. Es nämlich kann sein, dass das Handy auf einem Entwicklungsrechner mit Linux nur als USB-Massenspeicher auftaucht, aber von den Android-Tools nicht gefunden wird. Das ist ein Indiz dafür, dass es sich – ähnlich wie USB-UMTS-Sticks – in einem Modus befindet, der dem PC-Betriebssystem die passenden Treiber zur Verfügung stellt. Unter Linux müssen Sie es also mit usb_modeswitch
in den anderen Betriebsmodus versetzen. Beim im Beispiel verwendeten Vodafone 845 (Huawei U8210) funktionierte es mit folgendem Aufruf:
$ sudo usb_modeswitch -v 0x12d1 -p 0x1031 -s 20 -M "55534243123456780600000080000601000000000000000000000000000000"
In neueren Versionen von USB-Modeswitch ist das Gerät auch schon in der Device-Datenbank enthalten. Laden Sie sich im Zweifelsfall den Quellcode der neuesten Version herunter und compilieren Sie sie selbst (dazu brauchen Sie die Libusb-Entwicklungspakete).
Damit ist das Problem aber leider nicht nicht ganz gelöst, denn das Vodafone 845 gibt keine Seriennummer aus, womit das Android-Tool adb
wiederum nicht umgehen kann: Es zeigt dann als Gerätename ??????????
an. Ein Patch [3] für das Programm umgeht das Problem und verwendet statt der Seriennummer das USB-Device:
./tools/adb devices List of devices attached noserial-/dev/bus/usb/001/036 device
Hello World
Im automatisch erzeugten Beispielcode wird das Grundgerüst einer Android-Anwendung ersichtlich. Die eigene Klasse gpstracker
erweitert die Systemklasse Activity
, die für Standard-GUI-Anwendungen vorgesehen ist. Der wenige Rest steckt in der Callback-Methode onCreate()
, die abläuft, wenn die Anwendung initialisiert ist. Nachdem einem Aufruf der gleichnamigen Callback-Methode der Elternklasse bleibt nur noch, die aktuelle Ansicht auf die Layout-Komponente main
zu setzen. Diese Art von Ressourcen hat Eclipse in der Datei R.java
abgelegt, die es auch ständig aktualisiert. Manuelle Änderungen können also zu Konflikten führen. Der Entwickler sollte stattdessen nur die entsprechenden XML-Dateien bearbeiten, aber dazu später mehr.
Um das klassische Hello World auf dem Handyschirm zu bringen, braucht es nicht mehr als eine TextView
, die man dann zur aktuellen Ansicht der Anwendung macht. Den darzustellenden String übergibt die Methode setText()
dem TextView
-Objekt:
TextView tv = new TextView(this); tv.setText("Hello Android"); setContentView(tv);
Will man nun statt eines langweiligen Texts den aktuellen Standort ausgeben, kommt das Konzept des Listeners ins Spiel. An vielen Stellen dienen sie als Schnittstelle für regelmäßig oder unregelmäßig eintreffende Ereignisse, zum Beispiel Benutzereingaben. Der Programmierer registriert für bestimmte Ereignisse eigene Funktionen als Listener, die dann ablaufen, wenn das Ereignis eintrifft. Die Android-Plattform stellt sicher, dass die eigene Anwendung so nicht unnötig Rechenzeit verbraucht, sondern schläft, aber beim Ereignis prompt aufwacht und reagiert.
Die Android-API bietet einfache Funktionen, um die für Android-Telefone typische Hardware zu benutzen. So genügen wenige Zeilen, um den aktuellen GPS-Standort zu erfahren. Man erzeugt einen neuen LocationProvider
und registriert über eine seiner Methoden eine eigene Funktion – hier locationListener
, die in einem festgelegten Intervall aufgerufen wird (Listing 1).
Listing 1
LocationProvider
LocationProvider provider = lm.getProvider("gps"); lm.requestLocationUpdates("gps", 60000, // 1min 1, // 10m locationListener);
Neben GPS gibt es auch noch andere LocationProvider
, die zum Beispiel – etwas ungenauer – den Standort aus der Funkzelle ableiten. Der eigene LocationListener
muss dann nur noch über die Methoden des Location-Objekts geographische Breite und Länge auslesen und dem TextView
-Objekt übergeben (Listing 2).
Listing 2
LocationListener
private final LocationListener locationListener = new LocationListener() { public void onLocationChanged(Location l) { TextView tv = new TextView(gpstracker.this); tv.setText("lat: " + l.getLatitude() + " lon: " + l.getLongitude()); setContentView(tv); } ...
Weil dieser Code ad hoc eine anonyme Klasse erzeugt, muss sich die TextView
über gpstracker.this
auf die äußere Klasse beziehen. Um dem Emulator ein funktionierendes GPS-Gerät vorzugaukeln verbinden Sie sich auf dem Entwicklungsrechner über telnet localhost 5554
mit dem emulierten Handy und geben dann in dessen Konsole beispielsweise geo fix 20 40
ein.
GUI
Bisher werden die Koordinaten einfach nur aneinandergehängt und im Standard-View ausgegeben. Für echte Anwendungungen ist es natürlich unverzichtbar, eine richtige GUI aufzubauen. Normalerweise wird man diese nicht im Quellcode erzeugen, sondern in einer separaten XML-Datei, die deren Aufbau beschreibt. Die Standard-Datei für das Layout der Android-Anwendung ist main.xml
im Verzeichnis res/layout
. So beschreibt die XML-Datei aus Abbildung 3 zusätzlich ein Layout mit zwei Textviews für Latitude und Longitude. Die Views verwenden zur Steuerung der Textgröße einen eigene Stil namens SpecialStyle
, der wiederum in der Datei res/values/style.xml
definiert ist (Abbildung 4). Die XML-Dateien lassen sich übrigens in Eclipse entweder direkt bearbeiten oder mit Hilfe des Ressource-Editors in einem speziellen Reiter.
Rechte
Damit ein Android-Programm auf die GPS-Hardware zugreifen darf, muss sie bestimmte Rechte anfordern, die der Anwender ihr bei der Installation gewähren kann (wie auch Zugriff auf Benutzerdaten und so weiter). Der Weg darüber führt über die Datei AndroidManifest.xml
im Wurzelverzeichnis des Projekts. Dort legt der Programmierer mit ACCESS_FINE_LOCATION
fest, dass die Software die GPS-Funktion verwenden will (Abbildung 5). Die dort ebenfalls angeforderte ACCESS_INTERNET
wird später für den Upload der Log-Datei benötigt.

Ausweg
Wer die Beispielanwendung auf Emulator oder Handy ablaufen lässt, wird feststellen, dass es keine Möglichkeit gibt, das Programm zu beenden. Mit wenigen Zeilen entsteht ein eigenes Menü mit einem Exit-Button. Die auskommentierten Zeilen zeigen, wie ein Menü mit mehreren Einträgen aussieht (Listing 3). Der passende Event-Handler beendet das Programm, wenn der Anwender den Menüpunkt 0 auswählt:
Listing 3
Menü
@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(Menu.NONE, 0, 0, "Exit"); //menu.add(Menu.NONE, 1, 1, "Settings"); //menu.add(Menu.NONE, 2, 2, "Other"); return true; } public boolean onOptionsItemSelected(MenuItem item){ switch (item.getItemId()) { case 0: finish(); } return false; }
Nun soll das bisher entstandene Android-Programm so erweitert werden, dass es den zurückgelegten Weg registriert und als Datei abspeichert. Diese Daten lassen sich dann zum Beispiel mit der Google-Maps-API in einer Karte einblenden. Im Prinzip muss nur die Methode, die ohnehin die Koordinaten ausgibt, so erweitert werden, dass sie jedes Mal den aktuellen Datensatz von geographischer Länge und Breite in eine Datei speichert. Doch der Teufel steckt bekanntlich im Detail.
Zuerst braucht es zum Speichern der Koordinaten eine Datei, die man etwas anders als in normalen Java-Anwendungen öffnet, um den besonderen Umständen der Android-Plattform Rechnung zu tragen: Aus Sicherheitsgründen steckt jede Anwendung in einer Sandbox, die sie vor dem Zugriff anderer Programme schützt. Um in dieser Umgebung eine Datei zu öffnen, stellt die Android-API den statischen Aufruf Context.openFileOutput()
zur Verfügung. Diese lässt sich dann wie in Java gewohnt in einen BufferedOutputStream
übernehmen und weiterverarbeiten (Listing 4).
Listing 4
Datei schreiben
try { bOut = new BufferedOutputStream(openFileOutput("location.dat", MODE_PRIVATE)); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }
Alternativ ließen sich Daten auch auf der eingesteckten SD-Card speichern oder die Datei mit anderen Rechten anlegen, zum Beispiel MODE_WORLD_READABLE
.
Besonders bequem ist es hier, von den Fähigkeiten der Eclipse-IDE beziehungsweise des Android-Plugins Gebrauch zu machen: Es genügt, die Zeile bOut = new BufferedOutputStream ..
zu tippen. Eclipse moniert dann das Fehlen eines Try-Catch-Blocks und bietet an, ihn automatisch zu erstellen. Das gleiche kann der Programmierer auch über einen Kontextmenü-Eintrag erreichen.
Um den weiter oben geschriebenen LocationListener
nicht unnötig aufzublähen, wandert der Code, der die Koordinaten ausgibt, in eine eigene Funktion printSaveLocation
. Statt wie vorher die Koordinaten einfach zu verketten und in eine Textview zu schreiben, erhält nun jede Koordinate eine eigene Textview. Das ermöglicht es, in einer zukünftigen Version der Software, das Design der ausgegebenen Werte über Styles einzeln zu verändern.
Der Code in Listing 5 schreibt die beiden Werte, durch Kommas getrennt, in die vorher geöffnete Datei. Vor ihnen steht noch der aktuelle Zeitstempel, den man ebenfalls vom GPS-Satelliten erhält. Der obligatorische Try-Catch-Block ist hier nicht wiedergegeben.
Listing 5
Länge und Breite ausgeben
((TextView) tv_longitude).setText(String.valueOf(l.getLatitude())); ((TextView) tv_latitude).setText(String.valueOf(l.getLongitude())); bOut.write((String.valueOf(l.getTime()) + ":" + String.valueOf(l.getLatitude()) + "," + String.valueOf(l.getLongitude())+" ").getBytes());
Bleibt nur noch, in der Methode, die beim Auswählen des Exit
-Menüpunkts abläuft, die Datei wieder zu schließen.
Um das Programm im Emulator zu testen, verbindet man sich per telnet localhost 5554
mit der Konsole und gibt dort zum Beispiel geo fix 10 20
ein. Das sollte man ein paar mal mit anderen Werten machen und dann die Anwendung im Emulator über das Exit-Menü beenden.
Das emulierte Dateisystem ist über den Aufruf von adb shell
erreichbar. Wechselt man dort ins Verzeichnis /data/data/de.mobileos.gpstracker/files
, findet sich auch tatsächlich die Datei location.dat
, deren Inhalt der Befehl cat
ausgibt:
# cat location.dat 123871680000:20.003333333333,10.0016666666666 123871680100:50.008333333333,40.0066666666666 123871680200:20.003333333333,30.0050000000000
Die Upload-Funktion für die Datei greift auf die Standard-Java-Klassen für URLs und das HTTP-Protokoll zurück. Der Handler für den Upload-Menüpunkt öffnet die Datei mit openFileInput("location.dat"...)
und übergibt den resultierenden FileInputStream
an die Upload-Funktion. Diese öffnet mit URL.openConnection()
eine Verbindung zum Webserver und schickt dann solange Daten an ihn, solange sie aus der lokalen Datei lesen kann. Etwas schwierig ist es lediglich, das Format des HTTP-Multipart-Uploads hinzubekommen, denn es erfordert, dass man an der richtigen Stelle einen eindeutigen String für die den Trenner (Boundary) schickt. Auf dem Webserver muss entsprechend ein Skript laufen, das die Daten in Empfang nimmt, zum Beispiel in PHP oder einer beliebigen anderen Programmiersprache.
Listing 6 zeigt noch einmal das gekürzte Listing des GPS-Trackers im Zusammenhang, Abbildung 6 die fertige Anwendung auf dem Android-Telefon. Das Eclipse-Projekt zum Nachbauen oder Erweitern finden Sie auf der Heft-DVD.
Listing 6
GPS Tracker
public class gpstracker extends Activity { View tv_longitude; View tv_latitude; FileOutputStream fOut; BufferedOutputStream bOut; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE); tv_longitude = this.findViewById(R.id.longitude); tv_latitude = this.findViewById(R.id.latitude); try { bOut = new BufferedOutputStream(openFileOutput("location.dat", MODE_PRIVATE)); } catch (FileNotFoundException e) { e.printStackTrace(); } try { lm.requestLocationUpdates("gps", 60000, 1, locationListener); } catch (Exception e) { e.printStackTrace(); } Location l = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (l != null) { printSaveLocation(l); } } void printSaveLocation(Location l) { ((TextView) tv_longitude).setText(String.valueOf(l.getLatitude())); ((TextView) tv_latitude).setText(String.valueOf(l.getLongitude())); try { bOut.write((String.valueOf(l.getTime()) + ":" + String.valueOf(l.getLatitude()) + "," + String.valueOf(l.getLongitude())+" ").getBytes()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private final LocationListener locationListener = new LocationListener() { public void onLocationChanged(Location l) { printSaveLocation(l); } public void onProviderDisabled(String provider){} public void onProviderEnabled(String provider) {} public void onStatusChanged(String provider, int status, Bundle extras) {} }; @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(Menu.NONE, 0, 0, "Exit"); menu.add(Menu.NONE, 1, 1, "Upload"); return true; } void uploadFile(FileInputStream fin, String filestr) { String urlstr = "http://website/upload.php"; String boundary = "-------------------XYZ12345XYZ"; String imgheader = "Content-Disposition: form-data; name="uploadedfile"; filename="" + filestr + "" " + "Content-Type: text/plain " + " "; try { URLConnection uc = (HttpURLConnection)(new URL(urlstr)).openConnection(); uc.setDoOutput(true); uc.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); OutputStream out = uc.getOutputStream(); out.write(("--"+boundary+" ").getBytes()); out.write(imgheader.getBytes()); byte[] buf = new byte[4096]; int len; while ((len = fin.read(buf)) > 0) { out.write(buf, 0, len); } out.write(("--"+boundary+" ").getBytes()); out.flush(); out.close(); urlin.close(); } catch(MalformedURLException e) { e.printStackTrace(); } catch(IOException e) { e.printStackTrace(); } } // Menue-Handling public boolean onOptionsItemSelected(MenuItem item){ switch (item.getItemId()) { case 0: try { uploadFile(openFileInput("location.dat"), "location.dat"); } catch (FileNotFoundException e) { e.printStackTrace(); } finish(); case 1: try { uploadFile(openFileInput("location.dat"), "location.dat"); } catch (FileNotFoundException e) { e.printStackTrace(); } } return false; } }
Infos
- Eclipse: http://www.eclipse.org/downloads
- Android SDK: http://developer.android.com/sdk
- Patch für adb: https://review.source.android.com/#change,13552