7. Juni 2023
StartAppsEigener GPS-Tracker mit dem Android-SDK

Eigener GPS-Tracker mit dem Android-SDK

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.

Abbildung 1: Der SDK-Manager erlaubt es, die SDK-Pakete für unterschiedliche Android-Versionen zu installieren.
Abbildung 1: Der SDK-Manager erlaubt es, die SDK-Pakete für unterschiedliche Android-Versionen zu installieren.

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.

Abbildung 2: Die Basis-Informationen, die der Wizard braucht, um das Gerüst für eine Android-Anwendung zu erzeugen.
Abbildung 2: Die Basis-Informationen, die der Wizard braucht, um das Gerüst für eine Android-Anwendung zu erzeugen.

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.

Abbildung 3: Die XML-Datei
Abbildung 3: Die XML-Datei

Abbildung 4: Die Datei
Abbildung 4: Die Datei

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.

Abbildung 5: Damit eine Android-Anwendung zum Beispiel auf die GPS-Funktion zugreifen darf, muss sie die passenden Rechte erhalten.
Abbildung 5: Damit eine Android-Anwendung zum Beispiel auf die GPS-Funktion zugreifen darf, muss sie die passenden Rechte erhalten.

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.

Abbildung 6: Die fertige Anwendung auf dem Android-Handy.
Abbildung 6: Die fertige Anwendung auf dem Android-Handy.

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;
  }
}

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

EMPFEHLUNGEN DER REDAKTION

MAGAZIN