Start Magazin Android-Apps mit GPS nutzen

Android-Apps mit GPS nutzen

Moderne Smartphones oder auch Tablets haben ihre starke Verbreitung nicht nur der permanenten Erreichbarkeit via Mail, SMS, Twitter und Facebook zu verdanken. Ein weiteres starkes Feature ist die Positionsbestimmung ? sei es in Form einer Navigationslösung, einer App für den Sportler oder auch nur als Orientierungshilfe.

Aus diesem Grund ist es wenig überraschend das der Umgang mit dem LocationManager des Android Systems immer wieder in den einschlägigen Foren für Entwickler nach oben rutscht. Wir stellen Ihnen in diesem Artikel in Form einer kompletten Beispiel Anwendung ein Skelett einer App vor. Diese Demo-App ist als solche komplett lauffähig. Der Quellcode lässt sich für eigene Bedürfnisse erweitern oder in Teilen für eine eigene App übernehmen

Servicekraft

In der Android Umgebung ist für die Bestimmung der aktuellen Position der LocationManager [1] zuständig. Dieser stützt sich auf zwei Informationsquellen.

Das GPS Feature ist eine Hardware Komponente die in den meisten Android Geräten verbaut wird. Mit Erscheinen von kostengünstigen Tablets ist diese Hardware aber nicht mehr unbedingt in jedem Device enthalten.

Trotz fehlendem GPS Feature kann Android weiterhin eine aktuelle Position bestimmen. Dazu nimmt es die Positionsbestimmung an Hand der umgebenden WLAN-Infrastruktur vor. Diese Ortung weist üblicherweise eine geringere Genauigkeit als GPS auf. In manchen Gegenden, in denen keine WLAN Infrastruktur in der Nähe vorhanden ist, kann eine Positionsbestimmung ohne GPS somit komplett ausfallen.

Wie genau eine aktuelle Ortung ist, wird durch viele Einflüsse bestimmt. Der "Blick" des Gerätes auf die GPS-Satelliten oder die Entfernung und die Anzahl der WLAN -Strukturen im Umfeld sind hier die Taktgeber. In welcher zeitlichen Frequenz oder zu welchen Distanzen eine Messung zu erfolgen hat, gibt hingegen der Programmierer der Anwendung vor. Android kann, muss aber nicht, auf diese gewünschten Einstellungen eingehen. Diese etwas "zickige" Eigenschaft zieht sich übrigens wie ein roter Faden durch Android und muss vom Entwickler berücksichtigt werden.

Beide Positionsbestimmungen (GPS und WLAN) können sich gegenseitig ersetzen. Der Benutzer legt somit unter Android fest, ob eine Ortung via WLAN oder via GPS erfolgt. Da beide Systeme sicherheitshalber entsprechende Rechte benötigen, zeigt unsere Beispiel-App während der Installation einen entsprechenden Hinweis an und der Benutzer muss die Rechte bestätigen.

Der LocationManager ist die Komponente, die dem Entwickler gegenüber beide Systeme kapselt. Wie jede andere Komponente des Android Systems benötigen GPS und WLAN Strom. Das GPS-System zieht ziemlich kräftig am Akku. WLAN kommt mit weniger Ressourcen aus. Aus diesem Grund ist es erforderlich, die Aktivierung und das Abschalten der Ortung mit Hilfe des LocationManagers sehr dosiert vorzunehmen.

Die kleine Demo Anwendung (Abbildung 1) erlaubt das An- sowie Abschalten des LocationManagers. Dies geschieht durch zwei Buttons auf der einzig sichtbaren Activity dieser Anwendung. Die Buttons sind dazu wechselseitig sichtbar.

Abbildung 1: Die Beispielanwendung in Betrieb. Über den Stop-Button schalten Sie die Ortung aus.
Abbildung 1: Die Beispielanwendung in Betrieb. Über den Stop-Button schalten Sie die Ortung aus.

Ist die Positionsbestimmung aktiviert, dann werden aktuelle Geschwindigkeiten (Speed und Pace) sowie Locations auf der Activity angezeigt.

Drei derzeit statische Werte regeln einige Merkmale der App. Das Abschalten des Bildschirms ist unterbunden solange der LocationManager aktiv ist. Des weiteren wird vom LocationManager spätestens alle fünf Sekunden bzw. sieben Meter eine aktuelle Positionsbestimmung angefordert. Diese drei Werte sind in der Datei MyPreferenceActivity als statische Variablen hinterlegt. In einer realen Anwendung würde man diese Werte als einstellbare Optionen dem Benutzer anbieten.

In diversen echten Anwendungen wurde mit den verschiedenen Werten für den LocationManager bereits reichlich Erfahrung gesammelt. Die sieben Meter sind in etwa die Genauigkeit die man im Schnitt ohnehin so erwarten kann. Für eine Fußgänger- oder Radfahrer-Anwendung liefern diese Werte akzeptable Ergebnisse. Für eine PKW-Anwendung hingegen wäre das Intervall zu hoch. Hier kann der Entwickler die Messwerte auf 1000 Meter respektive 10 Sekunden erhöhen. Man riskiert mit diesen höheren Werten zwar eine gewisse Unschärfe, die zum Beispiel bei der Ermittlung der zurückgelegten Strecke auftritt, aber da Entfernungsangaben hierzulande ohnehin auf den Kilometer gerundet werden, relativiert sich das in den meisten Fällen.

Es lohnt sich also mit dem Platz in einer lokalen Datenbank, in der die Location Points gespeichert werden, und somit auch mit dem Akku schonend umzugehen.

Eine Demo Anwendung, wie die hier vorgestellte, ist nicht sehr aufwändig (Abbildung 2). Neben den Anwendungstexten (strings.xml, deutsch sowie englisch), einem Layout (main.xml) den Standard-Android-Icons aus der Entwicklungsumgebung (ic_launcher.png, diverse Auflösungen) benötigt man nur noch wenig Java-Programmcode. Diverse Einstellungen für die Zielsysteme liefert die Manifest Datei.

Abbildung 2: Der Eclipse-Projektbaum der Beispielanwendung.
Abbildung 2: Der Eclipse-Projektbaum der Beispielanwendung.

Der sichtbare Teil der Anwendung besteht nur aus einer Activity (Main.java, main.xml). Sie gibt bei aktiviertem LocationManager diverse Texte aus. Neben der Geschwindigkeit (Pace in min/km, Speed in km/h) zeigt die Activity die zurückgelegte Entfernung sowie die aktuelle Position (Längengrad, Breitengrad) an.

Stille Post

Jedes Android Projekt benötigt eine Manifest-Datei. Sie regelt unter anderem die benötigten Ressourcen und erforderlichen Rechte und gibt Infos zur aktuellen Version der App. Die Manifest-Datei der Demo enthält unter anderem diese drei deklarierten Rechte. Sie sorgen dafür, dass der Benutzer während der Installation der App aufgefordert wird, seine Erlaubnis für diese Rechte zu erteilen: ACCESS_COARSE_LOCATION erklärt die Positionsbestimmung per WLAN während ACCESS_FINE_LOCATION die Positionsbestimmung durch das GPS Feature regelt. Die Erlaubnis den Bildschirm permanent angeschaltet zu lassen verbirgt sich hinter dem WAKE_LOCK-Recht.

Listing 1

Erforderliche Rechte

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

Für Geräte ohne GPS-Feature würde eine solche App im Market niemals zum Download angeboten, denn das Recht ACCESS_FINE_LOCATION setzt implizit GPS voraus und verweigert den Download auf Geräten ohne GPS. Mit dem uses-feature kann diese implizite Einschränkung ausgehebelt werden. Das uses-feature teilt dem Play Store mit, dass diese App trotz fehlendem GPS-Feature zum Download angeboten werden soll. Auf Geräten ohne GPS-Feature funktioniert dann halt nur die WLAN-Ortung. Hier würde es sich anbieten, wenn die App den Benutzer zum Start der App darauf aufmerksam macht, dass manche Positionsdaten nicht die Qualität einer GPS-Messung erreichen.

Listing 2

GPS Hardware nicht erforderlich

<uses-feature
  android:name="android.hardware.location.gps"
  android:required="false" />

Eine Besonderheit ist das Vorhandensein der Klasse MyApplication die von der Standardklasse Application abgeleitet wurde (Listing 3). Dieses Konstrukt garantiert, dass vor dem Aufbau der ersten sichtbaren Activity eine bestimmte Klasse einmalig instanziert wird. Diese Klasse muss dann aber im Manifest hinterlegt werden. In den einschlägigen Foren gibt es immer wieder lange Diskussionen zum Für und Wider dieses Ansatzes. Die Java-Fraktion verweist selbstverständlich und zu recht auf die Nutzung von Singletons. Android-Kenner nehmen ohne Hemmung die Application-Klasse, da diese garantiert genau einmal vorkommt. In langjähriger Praxis hat sich jedenfalls mit diesem Konstrukt eine sehr nützliche Möglichkeit für zentrale Daten eröffnet.

Listing 3

App Start ändern

<application
  ...
  android:name="MyApplication" >

Die Klasse MyApplication kann verschiedenen Objekte eine gewisse Persistenz bieten. Im Beispiel werden hier einmalig vor Anwendungsstart die statischen Optionen eingerichtet. Viele reale Anwendungen nutzen dieses Konstrukt um an zentraler Stelle zum Beispiel eine zentrale Datenbankverbindung zu halten. Wie unsere App die Klasse nutzt, zeigt Listing 4.

Listing 4

Zentrale Application Instanz

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    MyPreferenceActivity.createSettings(getApplicationContext());
  }
}

Zum guten Schluss teilt man Android noch den Namen eines service mit (Listing 5). Services sind eine Besonderheit im Android-Universum [2]. Während Activities/Fragments für die Interaktion mit dem Benutzer ausgelegt sind, werkeln Services im Hintergrund. Es ist nicht vorgesehen, dass der Benutzer unmittelbaren Einfluss auf einen Service hat. Services sind für langlaufende Tätigkeiten gedacht. Dabei laufen sie trotzdem im selben UI-Thread wie die Activities der selben Anwendung. Soll in einem Service ein langlaufender Task durchgeführt werden, so muss der Entwickler auch in einem Service einen Thread dafür einrichten. Der Service der Demo-App benötigt keine zusätzlichen Threads, da er zu keinem Zeitpunkt langwierige Aufgaben durchführt. Eintreffende Aktualisierungen des LocationManagers werden kurz berechnet und sofort an die Activity weitergeleitet.

Ein Service hat den Vorteil, dass er weiterläuft, obwohl der Benutzer alle Activities der App in den Hintergrund verschoben hat. Die Beispielanwendung startet einen Service, sobald der Benutzer auf den Button Start drückt. Drückt der Benutzer danach auf den Stop-Button, beendet die App den Dienst. Der Benutzer kann so die Activity verlassen und sich einer anderen App zuwenden – der Service läuft derweil weiter.

Listing 5

Service bekanntgeben

<service android:name="MyService"></service>

Es gibt verschiedene Arten von Services und verschiedene Arten um einen Dienst zu starten. Im Beispiel handelt es sich um einen lokalen Service, der ausschließlich mit seiner eigenen Anwendung kommuniziert. Der Dienst wird von der Activity mit startService gestartet und mit stopService wieder beendet (Listing 6).

Listing 6

Service dynamisch starten

public static final String TAG = "de.asltd.androiduser.gps.MyService";
private void processStartService() {
  Intent intent = new Intent(context, MyService.class);
  intent.addCategory(MyService.TAG);
  intent.putExtra("APPNAME", appName);
  startService(intent);
}

Der Dienst durchläuft die Methoden onCreate und onStartCommand. Hier wird in den Preferences der App vermerkt, dass der Service aktiv ist, eine Notification in der Informationsleiste ausgegeben, der LocationManager gestartet und, wenn gewünscht, das Abschalten des Bildschirms unterbunden. Dem System wird mit dem Flag START_REDELIVER_INTENT mitgeteilt, dass nach einem Abschuss des Dienstes und automatischem Neustart (was durchaus passieren kann), ein identischer Intent an den neuen Service übergeben werden soll (Listing 7).

Listing 7

Der Service startet

@Override
public void onCreate() {
  context = getApplicationContext();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
  if (intent != null) {
    Bundle bundleExtras = intent.getExtras();
    if (bundleExtras != null) {
      appName = bundleExtras.getString("APPNAME");
      MyPreferenceActivity.setServiceActive(context, true);
      processStartNotification();
      processLocationListener();
      if (MyPreferenceActivity.isLockScreen()) {
        processStartWakeLock();
      }
    }
  }
  return START_REDELIVER_INTENT;
}

Beender der Nutzer die zugehörige Activity, dann gibt es so ohne Weiteres keine Möglichkeit den Service wieder zu stoppen. Deshalb zeigt die Beispiel-App in der Informationsleiste ein Icon an. Aktiviert der Benutzer den zugehörigen Eintrag, dann erscheint die Activity wieder im Vordergrund. Dort sieht der Benutzer die aktuellen Daten und kann mit Hilfe des Stop-Buttons den Dienst beenden (Abbildung 3). Dazu bereitet der Programmierer einen Intent vor, der dann nach der Auswahl des Eintrags in der Informationsleiste gefeuert wird. Listing 8 zeigt den passenden Code.

Abbildung 3: Im Hintergrund erfolgt der Zugriff auf die Anwendung mittels der Informationsleiste.
Abbildung 3: Im Hintergrund erfolgt der Zugriff auf die Anwendung mittels der Informationsleiste.

Listing 8

Einrichten einer Notification

public static final int MYSERVICE_NOTIFICATION = 1;
private void processStartNotification() {
  Intent intent = new Intent(this, Main.class);
  intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
  PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
  Notification notification = new Notification(R.drawable.ic_launcher, appName, System.currentTimeMillis());
  notification.setLatestEventInfo(this, appName, getResources().getString(R.string.txt_showactivity), pendingIntent);
  notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  notificationManager.notify(MYSERVICE_NOTIFICATION, notification);
}

Der WakeLock unterbindet das Abschalten des Displays. Diese einstellbare Sicherheitsmaßnahme soll eigentlich verhindern, dass das Display als größter Stromverbraucher den Akku leert. Bei einer Positionsbestimmung während einer Radfahrt oder einer Autofahrt verfügt der Benutzer üblicherweise über eine externe Stromversorgung in Form eines externen Akkus oder des Zigarettenanzünder. In solchen Fällen soll das Display angeschaltet bleiben.

Listing 9

Abschalten des Displays unterbinden

void processStartWakeLock() {
  PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
  if (powerManager != null) {
    wakeLock = powerManager.newWakeLock(PowerManager.ON_AFTER_RELEASE | PowerManager.SCREEN_DIM_WAKE_LOCK, MYSERVICE_WAKELOCK);
    if (wakeLock != null) {
      wakeLock.acquire();
    }
  }
}

Einmal gestartet, liefert der LocationManager dem Dienst in den gewünschten Intervallen neue Positionsangaben. Diese treffen in der Methode onLocationChanged ein. Da die Beispielanwendung auf GPS- sowie WLAN-Daten zugreifen möchte, werden beide Listener gestartet.

Listing 10

LocationListener anschalten

private void processLocationListener() {
  locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
  locationListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
      //
    }
  };
  processStartGpsListener();
  processStartNetworkListener();
}
void processStartGpsListener() {
  if (locationManager != null) {
    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
      MyPreferenceActivity.getTimeInterval() * 1000,
      MyPreferenceActivity.getDistanceInterval(),
      locationListener);
  }
}
private void processStartNetworkListener() {
  if (locationManager != null) {
    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
  }
}

Wie kommen nun die permanenten Updates des LocationListeners zur Anzeige? Die Updates fallen im Dienst an und es besteht nach dem in diesem Beispiel gewählten Ansatz keine direkte Verbindung zwischen Service und Activity. Auch hier gibt es verschiedene Lösungsansätze, von denen die Beispiel-App einen wählt: Broadcasts. Der Dienst verpackt die eintreffenden Location Updates nach kurzer Kalkulation in einem Intent und schickt diesen mittels Broadcast an die Activity.

Listing 11

Broadcast an Activity vorbereiten

Intent intent = new Intent();
intent.putExtra("LATITUDE", location.getLatitude());
intent.putExtra("LONGITUDE", location.getLongitude());
intent.putExtra("SPEED", location.getSpeed());
intent.putExtra("SUMDISTANCE", sumDistance);
intent.putExtra("SUMTIME", sumTime);
intent.setAction(Main.MyBroadcastReceiver.TAG);
sendBroadcast(intent);

Damit die Activity diese Broadcasts empfangen kann, muss ein BroadcastReceiver in der Activity eingerichtet werden. In der entsprechenden inneren Klasse der Activity trifft der Broadcast im onReceive ein und wird dort auf den Schirm gebracht. Solche BroadcastReceiver müssen Sie dynamisch starten und wieder löschen, wie Listing 12 zeigt.

Listing 12

Broadcasts-Empfang

public class MyBroadcastReceiver extends BroadcastReceiver {
  public static final String TAG = "de.asltd.androiduser.gps.Main.MyBroadcastReceiver";
  @Override
  public void onReceive(Context context, Intent intent) {
    if (intent != null) {
      Bundle bundleExtras = intent.getExtras();
      if (bundleExtras != null) {
        //
      }
    }
  }
}
private void processStartBroadcastReceiver() {
  IntentFilter intentFilter = new IntentFilter(Main.MyBroadcastReceiver.TAG);
  broadcastReceiver = new MyBroadcastReceiver();
  if (broadcastReceiver != null && intentFilter != null) {
    registerReceiver(broadcastReceiver, intentFilter);
  }
}

Die Zustellung von Daten mittels Broadcasts ist nicht unbedingt garantiert. Unter heftiger Systemlast kann schon einmal ein Broadcast verloren gehen. In solchen Fällen ist es aber wahrscheinlich das auch andere Systeme betroffen sind. Zudem ist in einer Anwendung, in der fortlaufend eine Positionsbestimmung durchgeführt wird, das Fehlen einer einzigen Position nicht so tragisch.

Wie unter Android üblich unterstützt die Demo App bereits Mehrsprachigkeit (Abbildung 4) und die Erweiterbarkeit durch Stile (Abbildung 5) vorbereitet. Das ist der einzuhaltende Standard und erleichtert bei einem eintretenden Erfolg der App die Erweiterung mit einfachsten Mitteln.

Abbildung 4: Die App ist bereits Vorbereitet für Mehrsprachigkeit.
Abbildung 4: Die App ist bereits Vorbereitet für Mehrsprachigkeit.

Abbildung 5: Wenige Styles regeln das Aussehen der Demo.
Abbildung 5: Wenige Styles regeln das Aussehen der Demo.

Fazit

Mit dem hier vorgestellten Skelett einer Anwendung lässt sich eine App erstellen, die mit Positionsbestimmungen arbeitet. Der Aufwand ist gering und das erforderliche KnowHow hält sich in Grenzen. Die Beispiel-App zeigt recht schön, wie effektiv und bestens strukturiert sich das Android Ökosystem dem Nutzer präsentiert. Die Standardsprache Java, ein in höchstem Maße optimiertes Klassengerüst gepaart mit manchmal wackeligen Entwicklungswerkzeugen (Eclipse) helfen dem Entwickler, in kürzester Zeit hochwertige Apps zu erstellen. Den Quellcode zu diesem Projekt finden Sie unter [3] auf der Android-User-Homepage zum Download.

Kommentiere den Artikel

Please enter your comment!
Please enter your name here