Wie man mit Geofences unter Android arbeitet kann
German (Deutsch) translation by Nikol Angelowa (you can also view the original English article)
Standortbezogene Ressourcen ermöglichen Ihrer Anwendung die Interaktion mit der physischen Welt und sind ideal, um die Benutzerinteraktion zu erhöhen. Obwohl viele mobile Apps sie verwenden, ist das Thema dieses Tutorials eine häufig übersehene Funktion, das Geofencing.
Ein Geofence ist ein virtueller Umkreis, der auf einem realen geografischen Gebiet festgelegt ist. Durch die Kombination einer Benutzerposition mit einem Geofence-Umkreis kann festgestellt werden, ob sich der Benutzer innerhalb oder außerhalb des Geofence befindet oder ob er den Bereich verlässt oder betritt.



Stellen Sie sich eine Universitäts-App vor, mit der Sie feststellen können, welche Kollegen und Professoren sich derzeit auf dem Campus befinden. Oder eine App für ein Einkaufszentrum, das Stammkunden belohnt. Es gibt viele andere interessante Möglichkeiten, die Sie erkunden können.
In diesem Lernprogramm erfahren Sie, wie Sie Geofences unter Android verwenden, indem Sie eine Anwendung erstellen, die dem Benutzer eine Benachrichtigung anzeigt, wenn er einen Geofence betritt oder verlässt. Es ist hilfreich, wenn Sie über Vorkenntnisse in Google Play Services, der Google Maps Android API oder IntentService
verfügen. Wenn Sie dies nicht tun, können Sie trotzdem mitmachen, aber Sie möchten möglicherweise nach dem Lesen dieses Tutorials Nachforschungen zu diesen Themen anstellen.
1. Geofences auf Android
Unter Android gibt es verschiedene Möglichkeiten, mit Geofences zu arbeiten. Sie können sogar eine eigene Implementierung für die Arbeit mit Geofences erstellen. Die Verwendung von GeofencingApi
von Google ist jedoch einfacher.
Diese APIs sind Teil der Location-APIs von Google. Es enthält Geofence
, GeofencingRequest
, GeofenceApi
, GeofencingEvent
und GeofenceStatusCodes
. In diesem Tutorial verwenden wir diese Klassen, um Geofences zu erstellen und damit zu arbeiten.
Geofence-Schnittstelle
Geofence
ist eine Schnittstelle, die ein geografisches Gebiet darstellt, das überwacht werden sollte. Es wird mit dem Geofence.Builder
erstellt. Während der Erstellung legen Sie die überwachte Region, das Ablaufdatum des Geofence, die Reaktionsfähigkeit, eine Kennung und die Art der Übergänge fest, nach denen gesucht werden soll.
Um den Stromverbrauch so gering wie möglich zu halten, wird empfohlen, in den meisten Situationen einen Geofence mit einem Radius von mindestens 100 Metern zu verwenden. Wenn sich Geofences auf dem Land befinden, sollten Sie den Radius auf 500 Meter oder mehr erhöhen, um sicherzustellen, dass die Geofences wirksam sind.
1 |
Geofence geofence = new Geofence.Builder() |
2 |
.setRequestId(GEOFENCE_REQ_ID) // Geofence ID |
3 |
.setCircularRegion( LATITUDE, LONGITUDE, RADIUS) // defining fence region |
4 |
.setExpirationDuration( DURANTION ) // expiring date |
5 |
// Transition types that it should look for
|
6 |
.setTransitionTypes( Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT ) |
7 |
.build(); |
Geofence-Übergänge
-
GEOFENCE_TRANSITION_DWELL
gibt an, dass der Benutzer den Bereich betreten und einige Zeit dort verbracht hat. Es ist nützlich, mehrere Warnungen zu vermeiden, wenn der Benutzer den Bereich zu schnell betritt und verlässt. Sie können die Verweilzeit mit dem ParametersetLoiteringDelay
konfigurieren. -
GEOFENCE_TRANSITION_ENTER
gibt an, wann der Benutzer den überwachten Bereich betritt. -
GEOFENCE_TRANSITION_EXIT
gibt an, wann der Benutzer die Region verlässt.
GeofenceRequest
Die GeofencingRequest
-Klasse empfängt die Geofences, die überwacht werden sollen. Sie können eine Instanz erstellen, indem Sie einen Builder
verwenden, einen Geofence
oder eine List<Geofence>
übergeben und die Art der Benachrichtigung festlegen, die beim Erstellen des Geofences ausgelöst wird.
1 |
GeofencingRequest request = new GeofencingRequest.Builder() |
2 |
// Notification to trigger when the Geofence is created
|
3 |
.setInitialTrigger( GeofencingRequest.INITIAL_TRIGGER_ENTER ) |
4 |
.addGeofence( geofence ) // add a Geofence |
5 |
.build(); |
GeofencingApi
Die GeofencingApi
-Klasse ist der Einstiegspunkt für alle Interaktionen mit der Geofencing-API von Google. Es ist Teil der Location-APIs und hängt von einem GoogleApiClient
ab. Mit GeofencingApi
können Sie Geofences hinzufügen und entfernen.
Um einen Geofence hinzuzufügen, rufen Sie die Methode addGeofence()
auf. Es überwacht den angegebenen Bereich anhand der an GeofencingRequest
übergebenen Einstellungen und erstellt einen PendingIntent
, wenn ein Geofence-Übergang beim Betreten oder Verlassen des Bereichs stattfindet.
1 |
PendingResult<Status> addGeofences (GoogleApiClient client, |
2 |
GeofencingRequest geofencingRequest, |
3 |
PendingIntent pendingIntent) |
Um den Geofence zu entfernen, rufen Sie removeGeofences()
auf. Sie können den Geofence entweder mithilfe seiner Anforderungskennung oder seiner ausstehenden Absicht entfernen.
1 |
PendingResult<Status> removeGeofences(GoogleApiClient client, |
2 |
List<String> geofenceRequestIds) |
1 |
PendingResult<Status> removeGeofences (GoogleApiClient client, |
2 |
PendingIntent pendingIntent) |
2. Erstellen einer Geofencing-App
In diesem Tutorial erstellen wir eine einfache Anwendung, die den Benutzerstandort überwacht und eine Benachrichtigung sendet, wenn der Benutzer einen geofenced Bereich betritt oder verlässt. Die App besteht nur aus einer Activity
und einem IntentService
. Wir werfen auch einen kurzen Blick auf GoogleMap
, GoogleApiClient
und FusedLocationProviderApi
und untersuchen einige Einschränkungen der Geofence-API.



Schritt 1: Projekteinrichtung
GeofencingApi
ist Teil der Google Play Services. Um darauf zugreifen zu können, müssen Sie Ihre Entwicklungsumgebung korrekt einrichten und eine Instanz von GoogleApiClient
erstellen. Erstellen Sie ein neues Projekt mit einer leeren Activity
, bearbeiten Sie die build.gradle-Datei des Projekts wie unten gezeigt und synchronisieren Sie Ihr Projekt.
Schritt 2: Berechtigungen
Wir müssen die richtigen Berechtigungen festlegen, um Geofences zu erstellen und zu verwenden. Fügen Sie dem Manifest des Projekts die folgende Berechtigung hinzu:
1 |
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> |
Ab Android 6.0 bittet die App zur Laufzeit und nicht während der Installation um Erlaubnis. Wir werden dies später im Tutorial ansprechen.
Schritt 3: Erstellen des Layouts
Das Projekt besteht aus einem Layout, dem MainActivity
-Layout. Es enthält den aktuellen Breiten- und Längengrad des Geräts sowie ein GoogleMap
-Fragment, in dem die Geofences und die Position des Benutzers angezeigt werden.
Da activity_main.xml ziemlich einfach ist, möchte ich mich nur auf das MapFragment
-Element konzentrieren. Sie können sich das fertige Layout in den Quelldateien dieses Tutorials ansehen.
1 |
<!--GoogleMap fragment-->
|
2 |
<fragment xmlns:android="http://schemas.android.com/apk/res/android" |
3 |
android:name="com.google.android.gms.maps.MapFragment" |
4 |
android:id="@+id/map" |
5 |
android:layout_width="match_parent" |
6 |
android:layout_height="match_parent"/> |
Schritt 4: Google Maps API-Schlüssel
Da wir ein MapFragment
verwenden, müssen wir eine GoogleMap
-Instanz einrichten und initialisieren. Zunächst müssen Sie einen API-Schlüssel erhalten. Wenn Sie einen API-Schlüssel haben, fügen Sie ihn dem Projektmanifest hinzu.
1 |
<meta-data
|
2 |
android:name="com.google.android.geo.API_KEY" |
3 |
android:value="YOUR_API_KEY"/> |
Beginnen wir mit der GoogleMap
-Instanz. Implementieren Sie GoogleMap.OnMapReadyCallback
, GoogleMap.OnMapClickListener
und GoogleMap.OnMarkerClickListener
in der Activity
-Klasse und initialisieren Sie die Karte.
1 |
public class MainActivity extends AppCompatActivity |
2 |
implements
|
3 |
OnMapReadyCallback, |
4 |
GoogleMap.OnMapClickListener, |
5 |
GoogleMap.OnMarkerClickListener |
6 |
{
|
7 |
|
8 |
private static final String TAG = MainActivity.class.getSimpleName(); |
9 |
|
10 |
private TextView textLat, textLong; |
11 |
private MapFragment mapFragment; |
12 |
private GoogleMap map; |
13 |
|
14 |
@Override
|
15 |
protected void onCreate(Bundle savedInstanceState) { |
16 |
super.onCreate(savedInstanceState); |
17 |
setContentView(R.layout.activity_main); |
18 |
textLat = (TextView) findViewById(R.id.lat); |
19 |
textLong = (TextView) findViewById(R.id.lon); |
20 |
|
21 |
// initialize GoogleMaps
|
22 |
initGMaps(); |
23 |
}
|
24 |
|
25 |
// Initialize GoogleMaps
|
26 |
private void initGMaps(){ |
27 |
mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map); |
28 |
mapFragment.getMapAsync(this); |
29 |
}
|
30 |
|
31 |
// Callback called when Map is ready
|
32 |
@Override
|
33 |
public void onMapReady(GoogleMap googleMap) { |
34 |
Log.d(TAG, "onMapReady()"); |
35 |
map = googleMap; |
36 |
map.setOnMapClickListener(this); |
37 |
map.setOnMarkerClickListener(this); |
38 |
}
|
39 |
|
40 |
// Callback called when Map is touched
|
41 |
@Override
|
42 |
public void onMapClick(LatLng latLng) { |
43 |
Log.d(TAG, "onMapClick("+latLng +")"); |
44 |
}
|
45 |
|
46 |
// Callback called when Marker is touched
|
47 |
@Override
|
48 |
public boolean onMarkerClick(Marker marker) { |
49 |
Log.d(TAG, "onMarkerClickListener: " + marker.getPosition() ); |
50 |
return false; |
51 |
}
|
52 |
}
|
Schritt 5: GoogleApiClient
Um die GeofencingApi
-Oberfläche verwenden zu können, benötigen wir einen GoogleApiClient
-Einstiegspunkt. Implementieren Sie in der Activity
einen GoogleApiClient.ConnectionCallbacks
und einen GoogleApiClient.OnConnectionFailedListener
wie unten gezeigt.
1 |
public class MainActivity extends AppCompatActivity |
2 |
implements
|
3 |
GoogleApiClient.ConnectionCallbacks, |
4 |
GoogleApiClient.OnConnectionFailedListener, |
5 |
OnMapReadyCallback, |
6 |
GoogleMap.OnMapClickListener, |
7 |
GoogleMap.OnMarkerClickListener { |
8 |
|
9 |
// ...
|
10 |
private GoogleApiClient googleApiClient; |
11 |
|
12 |
@Override
|
13 |
protected void onCreate(Bundle savedInstanceState) { |
14 |
|
15 |
// ...
|
16 |
|
17 |
// create GoogleApiClient
|
18 |
createGoogleApi(); |
19 |
}
|
20 |
|
21 |
// Create GoogleApiClient instance
|
22 |
private void createGoogleApi() { |
23 |
Log.d(TAG, "createGoogleApi()"); |
24 |
if ( googleApiClient == null ) { |
25 |
googleApiClient = new GoogleApiClient.Builder( this ) |
26 |
.addConnectionCallbacks( this ) |
27 |
.addOnConnectionFailedListener( this ) |
28 |
.addApi( LocationServices.API ) |
29 |
.build(); |
30 |
}
|
31 |
}
|
32 |
|
33 |
@Override
|
34 |
protected void onStart() { |
35 |
super.onStart(); |
36 |
|
37 |
// Call GoogleApiClient connection when starting the Activity
|
38 |
googleApiClient.connect(); |
39 |
}
|
40 |
|
41 |
@Override
|
42 |
protected void onStop() { |
43 |
super.onStop(); |
44 |
|
45 |
// Disconnect GoogleApiClient when stopping Activity
|
46 |
googleApiClient.disconnect(); |
47 |
}
|
48 |
|
49 |
// GoogleApiClient.ConnectionCallbacks connected
|
50 |
@Override
|
51 |
public void onConnected(@Nullable Bundle bundle) { |
52 |
Log.i(TAG, "onConnected()"); |
53 |
}
|
54 |
|
55 |
// GoogleApiClient.ConnectionCallbacks suspended
|
56 |
@Override
|
57 |
public void onConnectionSuspended(int i) { |
58 |
Log.w(TAG, "onConnectionSuspended()"); |
59 |
}
|
60 |
|
61 |
// GoogleApiClient.OnConnectionFailedListener fail
|
62 |
@Override
|
63 |
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { |
64 |
Log.w(TAG, "onConnectionFailed()"); |
65 |
}
|
66 |
}
|
Schritt 6: FusedLocationProviderApi
Wir müssen auch auf den aktuellen Standort des Benutzers zugreifen. Die FusedLocationProviderApi
-Schnittstelle gibt uns diese Informationen und ermöglicht eine umfassende Kontrolle der Standortanforderung. Dies ist sehr wichtig, da Standortanforderungen einen direkten Einfluss auf den Batterieverbrauch des Geräts haben.
Implementieren wir nun einen LocationListener
. Überprüfen Sie, ob der Benutzer der Anwendung die entsprechenden Berechtigungen erteilt hat, indem Sie die Location
-Anforderung erstellen, und zeigen Sie den aktuellen Standort auf dem Bildschirm an.
1 |
public class MainActivity extends AppCompatActivity |
2 |
implements
|
3 |
// ....
|
4 |
LocationListener
|
5 |
{
|
6 |
|
7 |
private Location lastLocation; |
8 |
//...
|
9 |
|
10 |
// GoogleApiClient.ConnectionCallbacks connected
|
11 |
@Override
|
12 |
public void onConnected(@Nullable Bundle bundle) { |
13 |
Log.i(TAG, "onConnected()"); |
14 |
getLastKnownLocation(); |
15 |
}
|
16 |
|
17 |
// Get last known location
|
18 |
private void getLastKnownLocation() { |
19 |
Log.d(TAG, "getLastKnownLocation()"); |
20 |
if ( checkPermission() ) { |
21 |
lastLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient); |
22 |
if ( lastLocation != null ) { |
23 |
Log.i(TAG, "LasKnown location. " + |
24 |
"Long: " + lastLocation.getLongitude() + |
25 |
" | Lat: " + lastLocation.getLatitude()); |
26 |
writeLastLocation(); |
27 |
startLocationUpdates(); |
28 |
} else { |
29 |
Log.w(TAG, "No location retrieved yet"); |
30 |
startLocationUpdates(); |
31 |
}
|
32 |
}
|
33 |
else askPermission(); |
34 |
}
|
35 |
|
36 |
private LocationRequest locationRequest; |
37 |
// Defined in mili seconds.
|
38 |
// This number in extremely low, and should be used only for debug
|
39 |
private final int UPDATE_INTERVAL = 1000; |
40 |
private final int FASTEST_INTERVAL = 900; |
41 |
|
42 |
// Start location Updates
|
43 |
private void startLocationUpdates(){ |
44 |
Log.i(TAG, "startLocationUpdates()"); |
45 |
locationRequest = LocationRequest.create() |
46 |
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) |
47 |
.setInterval(UPDATE_INTERVAL) |
48 |
.setFastestInterval(FASTEST_INTERVAL); |
49 |
|
50 |
if ( checkPermission() ) |
51 |
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this); |
52 |
}
|
53 |
|
54 |
@Override
|
55 |
public void onLocationChanged(Location location) { |
56 |
Log.d(TAG, "onLocationChanged ["+location+"]"); |
57 |
lastLocation = location; |
58 |
writeActualLocation(location); |
59 |
}
|
60 |
|
61 |
// Write location coordinates on UI
|
62 |
private void writeActualLocation(Location location) { |
63 |
textLat.setText( "Lat: " + location.getLatitude() ); |
64 |
textLong.setText( "Long: " + location.getLongitude() ); |
65 |
}
|
66 |
|
67 |
private void writeLastLocation() { |
68 |
writeActualLocation(lastLocation); |
69 |
}
|
70 |
|
71 |
// Check for permission to access Location
|
72 |
private boolean checkPermission() { |
73 |
Log.d(TAG, "checkPermission()"); |
74 |
// Ask for permission if it wasn't granted yet
|
75 |
return (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) |
76 |
== PackageManager.PERMISSION_GRANTED ); |
77 |
}
|
78 |
|
79 |
// Asks for permission
|
80 |
private void askPermission() { |
81 |
Log.d(TAG, "askPermission()"); |
82 |
ActivityCompat.requestPermissions( |
83 |
this, |
84 |
new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, |
85 |
REQ_PERMISSION
|
86 |
);
|
87 |
}
|
88 |
|
89 |
// Verify user's response of the permission requested
|
90 |
@Override
|
91 |
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
92 |
Log.d(TAG, "onRequestPermissionsResult()"); |
93 |
super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
94 |
switch ( requestCode ) { |
95 |
case REQ_PERMISSION: { |
96 |
if ( grantResults.length > 0 |
97 |
&& grantResults[0] == PackageManager.PERMISSION_GRANTED ){ |
98 |
// Permission granted
|
99 |
getLastKnownLocation(); |
100 |
|
101 |
} else { |
102 |
// Permission denied
|
103 |
permissionsDenied(); |
104 |
}
|
105 |
break; |
106 |
}
|
107 |
}
|
108 |
}
|
109 |
|
110 |
// App cannot work without the permissions
|
111 |
private void permissionsDenied() { |
112 |
Log.w(TAG, "permissionsDenied()"); |
113 |
}
|
114 |
}
|
Es ist wichtig zu beachten, dass die oben erstellte LocationRequest
nicht für eine Produktionsumgebung optimiert ist. Das UPDATE_INTERVAL
ist zu kurz und würde zu viel Batteriestrom verbrauchen. Eine realistischere Konfiguration für die Produktion könnte sein:
1 |
private final int UPDATE_INTERVAL = 3 * 60 * 1000; // 3 minutes |
2 |
private final int FASTEST_INTERVAL = 30 * 1000; // 30 secs |
3 |
private void startLocationUpdates(){ |
4 |
Log.i(TAG, "startLocationUpdates()"); |
5 |
locationRequest = LocationRequest.create() |
6 |
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) |
7 |
.setInterval(UPDATE_INTERVAL) |
8 |
.setFastestInterval(FASTEST_INTERVAL); |
9 |
|
10 |
if ( checkPermission() ) |
11 |
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this); |
12 |
}
|
Schritt 7: GoogleMap-Marker
Unsere Activity
benötigt zwei verschiedene Marker. Ein locationMarker
verwendet den vom FusedLocationProviderApi
angegebenen Breiten- und Längengrad, um den aktuellen Standort des Geräts zu ermitteln. Ein geoFenceMarker
ist das Ziel für die Geofence-Erstellung, da er die letzte Berührung auf der Karte verwendet, um seine Position abzurufen.
1 |
@Override
|
2 |
public void onMapClick(LatLng latLng) { |
3 |
Log.d(TAG, "onMapClick("+latLng +")"); |
4 |
markerForGeofence(latLng); |
5 |
}
|
6 |
|
7 |
private void writeActualLocation(Location location) { |
8 |
// ...
|
9 |
markerLocation(new LatLng(location.getLatitude(), location.getLongitude())); |
10 |
}
|
11 |
|
12 |
private Marker locationMarker; |
13 |
// Create a Location Marker
|
14 |
private void markerLocation(LatLng latLng) { |
15 |
Log.i(TAG, "markerLocation("+latLng+")"); |
16 |
String title = latLng.latitude + ", " + latLng.longitude; |
17 |
MarkerOptions markerOptions = new MarkerOptions() |
18 |
.position(latLng) |
19 |
.title(title); |
20 |
if ( map!=null ) { |
21 |
// Remove the anterior marker
|
22 |
if ( locationMarker != null ) |
23 |
locationMarker.remove(); |
24 |
locationMarker = map.addMarker(markerOptions); |
25 |
float zoom = 14f; |
26 |
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, zoom); |
27 |
map.animateCamera(cameraUpdate); |
28 |
}
|
29 |
}
|
30 |
|
31 |
private Marker geoFenceMarker; |
32 |
// Create a marker for the geofence creation
|
33 |
private void markerForGeofence(LatLng latLng) { |
34 |
Log.i(TAG, "markerForGeofence("+latLng+")"); |
35 |
String title = latLng.latitude + ", " + latLng.longitude; |
36 |
// Define marker options
|
37 |
MarkerOptions markerOptions = new MarkerOptions() |
38 |
.position(latLng) |
39 |
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)) |
40 |
.title(title); |
41 |
if ( map!=null ) { |
42 |
// Remove last geoFenceMarker
|
43 |
if (geoFenceMarker != null) |
44 |
geoFenceMarker.remove(); |
45 |
|
46 |
geoFenceMarker = map.addMarker(markerOptions); |
47 |
}
|
48 |
}
|
Schritt 8: Erstellen eines Geofence
Endlich ist es Zeit, einen Geofence zu erstellen. Wir verwenden den geoFenceMarker
als Mittelpunkt für den Geofence.
1 |
private static final long GEO_DURATION = 60 * 60 * 1000; |
2 |
private static final String GEOFENCE_REQ_ID = "My Geofence"; |
3 |
private static final float GEOFENCE_RADIUS = 500.0f; // in meters |
4 |
|
5 |
// Create a Geofence
|
6 |
private Geofence createGeofence( LatLng latLng, float radius ) { |
7 |
Log.d(TAG, "createGeofence"); |
8 |
return new Geofence.Builder() |
9 |
.setRequestId(GEOFENCE_REQ_ID) |
10 |
.setCircularRegion( latLng.latitude, latLng.longitude, radius) |
11 |
.setExpirationDuration( GEO_DURATION ) |
12 |
.setTransitionTypes( Geofence.GEOFENCE_TRANSITION_ENTER |
13 |
| Geofence.GEOFENCE_TRANSITION_EXIT ) |
14 |
.build(); |
15 |
}
|
Als Nächstes erstellen wir das GeofencingRequest
-Objekt.
1 |
// Create a Geofence Request
|
2 |
private GeofencingRequest createGeofenceRequest( Geofence geofence ) { |
3 |
Log.d(TAG, "createGeofenceRequest"); |
4 |
return new GeofencingRequest.Builder() |
5 |
.setInitialTrigger( GeofencingRequest.INITIAL_TRIGGER_ENTER ) |
6 |
.addGeofence( geofence ) |
7 |
.build(); |
8 |
}
|
Wir verwenden ein PendingIntent
-Objekt, um einen IntentService
aufzurufen, der das GeofenceEvent
verarbeitet. Wir erstellen die GeofenceTrasitionService.class
später.
1 |
private PendingIntent geoFencePendingIntent; |
2 |
private final int GEOFENCE_REQ_CODE = 0; |
3 |
private PendingIntent createGeofencePendingIntent() { |
4 |
Log.d(TAG, "createGeofencePendingIntent"); |
5 |
if ( geoFencePendingIntent != null ) |
6 |
return geoFencePendingIntent; |
7 |
|
8 |
Intent intent = new Intent( this, GeofenceTrasitionService.class); |
9 |
return PendingIntent.getService( |
10 |
this, GEOFENCE_REQ_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT ); |
11 |
}
|
12 |
|
13 |
// Add the created GeofenceRequest to the device's monitoring list
|
14 |
private void addGeofence(GeofencingRequest request) { |
15 |
Log.d(TAG, "addGeofence"); |
16 |
if (checkPermission()) |
17 |
LocationServices.GeofencingApi.addGeofences( |
18 |
googleApiClient, |
19 |
request, |
20 |
createGeofencePendingIntent() |
21 |
).setResultCallback(this); |
22 |
}
|
Wir zeichnen den Geofence auch als visuelle Referenz auf die Karte.
1 |
@Override
|
2 |
public void onResult(@NonNull Status status) { |
3 |
Log.i(TAG, "onResult: " + status); |
4 |
if ( status.isSuccess() ) { |
5 |
drawGeofence(); |
6 |
} else { |
7 |
// inform about fail
|
8 |
}
|
9 |
}
|
10 |
|
11 |
// Draw Geofence circle on GoogleMap
|
12 |
private Circle geoFenceLimits; |
13 |
private void drawGeofence() { |
14 |
Log.d(TAG, "drawGeofence()"); |
15 |
|
16 |
if ( geoFenceLimits != null ) |
17 |
geoFenceLimits.remove(); |
18 |
|
19 |
CircleOptions circleOptions = new CircleOptions() |
20 |
.center( geoFenceMarker.getPosition()) |
21 |
.strokeColor(Color.argb(50, 70,70,70)) |
22 |
.fillColor( Color.argb(100, 150,150,150) ) |
23 |
.radius( GEOFENCE_RADIUS ); |
24 |
geoFenceLimits = map.addCircle( circleOptions ); |
25 |
}
|
Die Methode startGeofence()
ist für das Starten des Geofencing-Prozesses in der MainActivity
-Klasse verantwortlich.
1 |
@Override
|
2 |
public boolean onOptionsItemSelected(MenuItem item) { |
3 |
switch ( item.getItemId() ) { |
4 |
case R.id.geofence: { |
5 |
startGeofence(); |
6 |
return true; |
7 |
}
|
8 |
}
|
9 |
return super.onOptionsItemSelected(item); |
10 |
}
|
11 |
|
12 |
// Start Geofence creation process
|
13 |
private void startGeofence() { |
14 |
Log.i(TAG, "startGeofence()"); |
15 |
if( geoFenceMarker != null ) { |
16 |
Geofence geofence = createGeofence( geoFenceMarker.getPosition(), GEOFENCE_RADIUS ); |
17 |
GeofencingRequest geofenceRequest = createGeofenceRequest( geofence ); |
18 |
addGeofence( geofenceRequest ); |
19 |
} else { |
20 |
Log.e(TAG, "Geofence marker is null"); |
21 |
}
|
22 |
}
|
Schritt 9: Geofence Transition Service
Wir können jetzt endlich die zuvor erwähnte GeofenceTrasitionService.class
erstellen. Diese Klasse erweitert IntentService
und ist für die Behandlung des GeofencingEvent
verantwortlich. Erstens erhalten wir dieses Ereignis aus der empfangenen Absicht.
1 |
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); |
Wir prüfen dann, ob die Art des Geofencing-Übergangs für uns von Interesse ist. Wenn dies der Fall ist, rufen wir eine Liste der ausgelösten Geofences ab und erstellen eine Benachrichtigung mit den entsprechenden Aktionen.
1 |
// Retrieve GeofenceTrasition
|
2 |
int geoFenceTransition = geofencingEvent.getGeofenceTransition(); |
3 |
// Check if the transition type
|
4 |
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || |
5 |
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ) { |
6 |
// Get the geofence that were triggered
|
7 |
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences(); |
8 |
// Create a detail message with Geofences received
|
9 |
String geofenceTransitionDetails = getGeofenceTrasitionDetails(geoFenceTransition, triggeringGeofences ); |
10 |
// Send notification details as a String
|
11 |
sendNotification( geofenceTransitionDetails ); |
12 |
}
|
Ich habe auch einige Hilfsmethoden implementiert, um die Implementierung der Klasse verständlicher zu machen.
1 |
public class GeofenceTrasitionService extends IntentService { |
2 |
|
3 |
private static final String TAG = GeofenceTrasitionService.class.getSimpleName(); |
4 |
public static final int GEOFENCE_NOTIFICATION_ID = 0; |
5 |
|
6 |
public GeofenceTrasitionService() { |
7 |
super(TAG); |
8 |
}
|
9 |
|
10 |
@Override
|
11 |
protected void onHandleIntent(Intent intent) { |
12 |
// Retrieve the Geofencing intent
|
13 |
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); |
14 |
|
15 |
// Handling errors
|
16 |
if ( geofencingEvent.hasError() ) { |
17 |
String errorMsg = getErrorString(geofencingEvent.getErrorCode() ); |
18 |
Log.e( TAG, errorMsg ); |
19 |
return; |
20 |
}
|
21 |
|
22 |
// Retrieve GeofenceTrasition
|
23 |
int geoFenceTransition = geofencingEvent.getGeofenceTransition(); |
24 |
// Check if the transition type
|
25 |
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || |
26 |
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ) { |
27 |
// Get the geofence that were triggered
|
28 |
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences(); |
29 |
// Create a detail message with Geofences received
|
30 |
String geofenceTransitionDetails = getGeofenceTrasitionDetails(geoFenceTransition, triggeringGeofences ); |
31 |
// Send notification details as a String
|
32 |
sendNotification( geofenceTransitionDetails ); |
33 |
}
|
34 |
}
|
35 |
|
36 |
// Create a detail message with Geofences received
|
37 |
private String getGeofenceTrasitionDetails(int geoFenceTransition, List<Geofence> triggeringGeofences) { |
38 |
// get the ID of each geofence triggered
|
39 |
ArrayList<String> triggeringGeofencesList = new ArrayList<>(); |
40 |
for ( Geofence geofence : triggeringGeofences ) { |
41 |
triggeringGeofencesList.add( geofence.getRequestId() ); |
42 |
}
|
43 |
|
44 |
String status = null; |
45 |
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ) |
46 |
status = "Entering "; |
47 |
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ) |
48 |
status = "Exiting "; |
49 |
return status + TextUtils.join( ", ", triggeringGeofencesList); |
50 |
}
|
51 |
|
52 |
// Send a notification
|
53 |
private void sendNotification( String msg ) { |
54 |
Log.i(TAG, "sendNotification: " + msg ); |
55 |
|
56 |
// Intent to start the main Activity
|
57 |
Intent notificationIntent = MainActivity.makeNotificationIntent( |
58 |
getApplicationContext(), msg |
59 |
);
|
60 |
|
61 |
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); |
62 |
stackBuilder.addParentStack(MainActivity.class); |
63 |
stackBuilder.addNextIntent(notificationIntent); |
64 |
PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); |
65 |
|
66 |
// Creating and sending Notification
|
67 |
NotificationManager notificatioMng = |
68 |
(NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE ); |
69 |
notificatioMng.notify( |
70 |
GEOFENCE_NOTIFICATION_ID, |
71 |
createNotification(msg, notificationPendingIntent)); |
72 |
}
|
73 |
|
74 |
// Create a notification
|
75 |
private Notification createNotification(String msg, PendingIntent notificationPendingIntent) { |
76 |
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this); |
77 |
notificationBuilder
|
78 |
.setSmallIcon(R.drawable.ic_action_location) |
79 |
.setColor(Color.RED) |
80 |
.setContentTitle(msg) |
81 |
.setContentText("Geofence Notification!") |
82 |
.setContentIntent(notificationPendingIntent) |
83 |
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND) |
84 |
.setAutoCancel(true); |
85 |
return notificationBuilder.build(); |
86 |
}
|
87 |
|
88 |
// Handle errors
|
89 |
private static String getErrorString(int errorCode) { |
90 |
switch (errorCode) { |
91 |
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE: |
92 |
return "GeoFence not available"; |
93 |
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES: |
94 |
return "Too many GeoFences"; |
95 |
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS: |
96 |
return "Too many pending intents"; |
97 |
default: |
98 |
return "Unknown error."; |
99 |
}
|
100 |
}
|
101 |
}
|
3. Testen
Testen auf einem virtuellen Gerät
Es ist viel einfacher, Geofencing auf einem virtuellen Gerät zu testen. Es gibt verschiedene Möglichkeiten, dies zu tun. Öffnen Sie in Android Studio ein virtuelles Gerät und klicken Sie unten rechts auf die Schaltfläche Weitere Optionen.
Geben Sie auf der Registerkarte Standort links die Koordinaten für den Standort ein.



Ich bevorzuge die Verwendung von Telnet-Befehlen zur Steuerung des virtuellen Geräts. Um dies zu verwenden, müssen Sie über die Befehlszeile mit dem folgenden Befehl eine Verbindung zum Gerät herstellen:
1 |
telnet localhost [DEVICE_PORT]
|
Der Geräteport wird im Fenster des virtuellen Geräts angezeigt. Der Geräteport ist normalerweise gleich 5554.
Möglicherweise müssen Sie diese Verbindung mit Ihrem auth_token
autorisieren, aber die Befehlszeile zeigt Ihnen, wo sie sich befindet. Navigieren Sie zu diesem Speicherort, kopieren Sie das Token und geben Sie auth [YOUR_AUTH_TOKEN]
ein.
Sie können jetzt den Standort des Geräts festlegen, indem Sie den folgenden Befehl ausführen:
1 |
geo fix [LATITUDE] [LONGITUDE] |
Abschluss
Geofencing kann eine großartige Ergänzung Ihrer App sein, da es die Benutzerinteraktion erheblich steigern kann. Es gibt viele Möglichkeiten zu erkunden und Sie können sogar ein anspruchsvolles Erlebnis mit Indoor-Beacons wie dem Estimote schaffen. Mit Indoor-Beacons wissen Sie genau, wo der Benutzer beispielsweise ein Einkaufszentrum betreten hat.
Das Hinzufügen von Geofencing zu einem Projekt ist einfach, aber wir müssen den Stromverbrauch jederzeit im Auge behalten. Dies bedeutet, dass wir die Größe des Geofence und die Aktualisierungsrate sorgfältig auswählen müssen, da sich beide direkt auf den Stromverbrauch Ihrer Anwendung auswirken.
Das Testen ist daher sehr wichtig, um eine realistische Vorstellung vom Stromverbrauch Ihrer Anwendung zu erhalten. Erwägen Sie außerdem, Benutzern die Option zu geben, Geofencing vollständig zu deaktivieren, wenn sie diese Funktion nicht möchten oder benötigen.