Android-Programmierung GPS
Willemers Informatik-Ecke

GPS

Das GPS (Global Positioning System) kann die aktuelle Position mit der Hilfe von Satelliten bestimmen. GPS liefert die Werte Longitude, Latitude. Außerdem die Höhe in Metern und die Zeit als koordinierte Weltzeit (UTC) in Millisekunden seit 1.1.1970.

Aus mehreren Messwerten kann Android die Geschwindigkeit in m/s und die Ausrichtung (bearing) errechnen.

Für die Bestimmung werden mindestens vier Satelliten benötigt. Android kann eine etwas ungenauere Position allerdings auch aus den Informationen des Netzwerkproviders empfangen.

Permissions

Der Zugriff auf den Standort ist eine "dangerous permission". Die Berechtigung für den Zugriff muss nicht nur im Manifest verankert, sondern explizit beim Benutzer erfragt werden.

Eine detaillierte Beschreibung erfolgt an anderer Stelle.

Berechtigung im Manifest

Der Zugriff auf die Location muss im Manifest deklariert werden. Dazu wird ein Eintrag mit uses-permission angelegt.

...
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
ACCESS_FINE_LOCATION steht für GPS. Die gröbere Auflösung kommt vom Mobilfunkanbieter und wird mit ACCESS_COARSE_LOCATION angesprochen.

Permission prüfen

Damit diese Frage nicht dauernd gestellt wird, kann zunächst mit checkSelfPermission geprüft werden, ob die Erlaubnis nicht schon zuvor gegeben wurde.
if (ActivityCompat.checkSelfPermission(MainActivity.this,
        Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED) {
Neben FINE muss auch COARSE abgefragt werden. Daraus ergibt sich:
if (ActivityCompat.checkSelfPermission(MainActivity.this,
        Manifest.permission.ACCESS_FINE_LOCATION)
    != PackageManager.PERMISSION_GRANTED
    ||
    ActivityCompat.checkSelfPermission(MainActivity.this,
        Manifest.permission.ACCESS_COARSE_LOCATION)
    != PackageManager.PERMISSION_GRANTED) {
        // bitte den Anwender um Erlaubnis
        ActivityCompat.requestPermissions(MainActivity.this,
            new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
                    Manifest.permission.ACCESS_FINE_LOCATION},
            123);
}

Permissions erfragen

Gibt es keine Permission, muss der Benutzer gefragt werden, bevor auf GPS zugegriffen werden darf. Das erfolgt durch den Aufruf von requestPermissions:
ActivityCompat.requestPermissions(MainActivity.this,
        new String[]
                { Manifest.permission.ACCESS_COARSE_LOCATION,
                  Manifest.permission.ACCESS_FINE_LOCATION},
        123);
Der zweite Parameter ist ein String-Array, das alle angeforderten Permissions aufführt. Der dritte Parameter ist eine Zahl, an der Android diese Anfrage im Callback wieder erkennt.

Antwort auf den Dialog

Der Dialog blockiert nicht die App. Stattdessen wird die Antwort des Anwenders ein Ereignis auslösen, dass durch die Methode onRequestPermissionsResult gefangen wird. Erst dort ist man sicher, dass man den GPS-Listener in Betrieb nehmen kann.
@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[],
                                       int[] grantResults) {
    // Requests können für mehrere Ressourcen eingetroffen sein.
    // Prüfe erst einmal, ob es mein Request-Code ist
    if (requestCode==myRequestCode)
    {
        if (grantResults.length == 1
        && grantResults[0] == PackageManager.PERMISSION_GRANTED)
        {
             // Zugriff erlaubt, dennoch noch einmal fragen
            if (ContextCompat.checkSelfPermission(this,
                    Manifest.permission.ACCESS_FINE_LOCATION)
                    == PackageManager.PERMISSION_GRANTED)
            {
                // Starte die GPS-Anfragen (siehe unten)
                locationManager.requestLocationUpdates(
                        LocationManager.GPS_PROVIDER, 0, 0,
                        locationListener);
            }
        } else {
            // Zugriff verboten
        }
    } else {
        super.onRequestPermissionsResult(requestCode, permissions,grantResults);
    }
}

LocationManager und LocationListener

Anschließend kann mit dem Sammeln der GPS-Daten begonnen werden. Dazu schaltet der LocationManager den LocationListener scharf.

Der LocationManager ist ein Service von Android. Er muss zunächst initialisiert werden.

private LocationManager locationManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ...
    locationManager = (LocationManager) this.getSystemService( Context.LOCATION_SERVICE);
Damit ist der Service erreichbar. Die eigentlichen GPS-Ereignisse werden wie alle anderen Ereignisse unter Android durch einen Listener gefangen, der in diesem Fall LocationListener heißt.

Wie immer bei Listenern gibt es mehrere Möglichkeiten. Die Activity-Klasse kann den LocationListener implementieren oder man legt eine Instanz von LocationListener an, was dazu führt, dass man auch dessen abstrakte Methoden sofort implementieren muss.

Die Verbindung zwischen dem Location-Manager und dem Location-Listener schlägt die Methode requestLocationUpdates. Der letzte Parameter ist der Listener. Falls die Activity-Klasse LocationListener implementiert, ist dieser this.

requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime, minDist, listener)
Das Abschalten der Updates erfolgt mit removeUpdates.
locationManager.removeUpdates(locationListener);

Ist GPS eingeschaltet?

Feststellen, ob Location überhaupt eingeschaltet ist (ab Android 9):
if ( ! locationManager.isLocationEnabled() ) {
    // GPS ist ausgeschaltet
Alternative:
if (manager.isProviderEnabled(Context.LOCATION_SERVICE)) {

Das Interface LocationListener

Der LocationListener ist ein Interface für den Empfang aller Ereignisse rund um eine Location. Sie erzwingt die folgenden Methoden: Davon interessiert idR nur die erste Methode, also onLocationChanged.

Die Klasse Location

Die Location trägt den gefundenen Ort. Diesen erfährt das Programm in der Methode onLocationChanged des LocationListeners als Parameter. Hier erfährt man alle Informationen über den gefundenen Standort.
    @Override
    public void onLocationChanged(Location location) {
        String str =
            "Long: " + location.getLongitude() +
            " Lat: " + location.getLatitude() +
            " Höhe: " + location.getAltitude() +
            " Genauigkeit: " + location.getAccuracy() +
            " Zeitstempel: " + location.getTime();
        tv.setText(str);
    }

Activity

Das folgende Programm aktiviert das GPS und löst alle Sekunde bei 10 Metern Positionsunterschied ein onLocationChanged aus. Diese Updates werden bei onPause gestoppt, um keinen zu hohen Akkuverbrauch zu haben.

Auch die anderen Ereignisse des LocationListeners werden gefangen und angezeigt.

package de.willemer.gpstester;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

public class MainActivity extends AppCompatActivity implements LocationListener {

    final String TAG = "GPStest";
    LocationManager lm;
    TextView tv;

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tvGps);
        lm = (LocationManager) getSystemService(LOCATION_SERVICE);
    }

    @Override
    protected void onResume() {
        if (ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED
            || ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_COARSE_LOCATION)
                == PackageManager.PERMISSION_GRANTED)
        {
            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 10, this);
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        // GPS braucht sehr viel Akku, also stoppen, wenn nicht gebraucht
        lm.removeUpdates(this);
        super.onPause();

    }

    @Override
    protected void onStop() {
        finish();
        super.onStop();

    }

    @Override
    public void onLocationChanged(Location location) {
        tv.setText(location.toString());
        double north = location.getLatitude();
        double east  = location.getLongitude();
    }

    @Override
    public void onStatusChanged(String s, int status, Bundle bundle) {
        String msg = "Status changed";
        switch (status) {
            case LocationProvider.OUT_OF_SERVICE:
                msg = "Status: Außer Betrieb";
                break;
            case LocationProvider.TEMPORARILY_UNAVAILABLE:
                msg = "Status: derzeit nicht verfügbar";
                break;
            case LocationProvider.AVAILABLE:
                msg = "Status: Verfügbar";
                break;
        }
        Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onProviderEnabled(String s) {
        Toast.makeText(this, "GPS Aktiviert", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onProviderDisabled(String s) {
        // Settings aufrufen, wenn kein Provider vorhanden
        Intent intent = new Intent(
                android.provider.Settings.
                ACTION_LOCATION_SOURCE_SETTINGS);
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                       String permissions[],
                                       int[] grantResults) {
        // Requests können für mehrere Ressourcen eingetroffen sein.
        // Prüfe erst einmal, ob es mein Request-Code ist
        if (requestCode==myRequestCode)
        {
            if (grantResults.length == 1
            && grantResults[0] == PackageManager.PERMISSION_GRANTED)
            {
                 // Zugriff erlaubt, dennoch noch einmal fragen
                if (ContextCompat.checkSelfPermission(this,
                        Manifest.permission.ACCESS_FINE_LOCATION)
                        == PackageManager.PERMISSION_GRANTED)
                {
                    // Starte die GPS-Anfragen (siehe unten)
                    locationManager.requestLocationUpdates(
                            LocationManager.GPS_PROVIDER, 0, 0,
                            locationListener);
                }
            } else {
                // Zugriff verboten
                Toast.makeText( this,
                        "App benötigt GPS Permission!\nDie App wird beendet",
                        Toast.LENGTH_LONG).show();
                this.finish();
            }
        } else {
            super.onRequestPermissionsResult(requestCode,
                    permissions,grantResults);
        }
    }
}
Wird nur eine grobe Auflösung benötigt, wird statt der Konstante GPS_PROVIDER nun NETWORK_PROVIDER eingesetzt. Auf einem Emulator funktioniert das nur, wenn dieser die Google API enthält.

Der Bildschirm entspricht dem Hello-World. Allerdings wurde dem Label der Name tv verpasst, damit es aus der Activity heraus mit Ausgaben versorgt werden kann.

Eine Landkarte mit OpenStreetMap

Der Dienst BigMap kann eine Landkarte der Umgebung als PNG-Datei erzeugen.

In die Landkarte mit Scrollrad oder über die Plus- und Minussymbole hineingehen. Durch Draggen der Maus kann die Position verschoben werden.

Ist der gewünschte Ausschnitt gefunden, dann Submit klicken.

Auf diese Weise enthält der Dateiname die Koordinaten des Kartenausschnitts.