Cómo trabajar con geofences en Android
Spanish (Español) translation by Elías Nicolás (you can also view the original English article)
Los recursos conscientes de la ubicación permiten que su aplicación interactúe con el mundo físico y son ideales para aumentar la participación del usuario. Aunque muchas aplicaciones móviles las usan, el tema de este tutorial es una característica que a menudo se pasa por alto, el geofencing.
Un geofence es un perímetro virtual establecido en un área geográfica real. Combinando la posición de un usuario con un perímetro de geofence, es posible saber si el usuario está dentro o fuera del geofence o incluso si está saliendo o ingresando al área.



Imagine una aplicación universitaria que le puede decir qué colegas y profesores están actualmente en el campus. O una aplicación para un centro comercial que recompensa a los clientes habituales. Hay muchas otras posibilidades interesantes que puedes explorar.
En este tutorial, aprenderá a usar geofences en Android creando una aplicación que muestre al usuario una notificación cuando ingrese o salga de un geofence. Es útil si tiene conocimientos previos de Google Play Services, Google Maps Android API o IntentService
. Si no lo hace, entonces puede seguir adelante, pero es posible que desee hacer una investigación sobre estos temas después de leer este tutorial.
1. Geofences en Android
En Android, hay varias formas de trabajar con geofences. Incluso podría crear su propia implementación para trabajar con geofences, pero es más fácil usar GeofencingApi
de Google.
Estas API son parte de las API Location de Google. Incluye Geofence
, GeofencingRequest
, GeofenceApi
, GeofencingEvent
, y GeofenceStatusCodes
. En este tutorial, usamos estas clases para crear y trabajar con geofences.
Interfaz Geofence
Geofence
es una interfaz que representa un área geográfica que debe ser monitoreada. Se crea utilizando el Geofence.Builder
. Durante su creación, establece la región supervisada, la fecha de caducidad del geofence, la capacidad de respuesta, un identificador y el tipo de transiciones que debe buscar.
Para mantener el consumo de energía al mínimo, se recomienda utilizar un geofence con un radio de al menos 100 metros para la mayoría de las situaciones. Si las geofences están ubicadas en el campo, debe aumentar el radio a 500 metros o más para asegurarse de que las geofences sean efectivas.
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(); |
Transiciones geofence
-
GEOFENCE_TRANSITION_DWELL
indica que el usuario ingresó al área y pasó algún tiempo allí. Es útil evitar múltiples alertas cuando el usuario ingresa y sale del área demasiado rápido. Puede configurar el tiempo de permanencia usando el parámetrosetLoiteringDelay
. -
GEOFENCE_TRANSITION_ENTER
indica cuando el usuario ingresa a la región monitoreada. -
GEOFENCE_TRANSITION_EXIT
indica cuando el usuario sale de la región.
GeofenceRequest
GeofencingRequest
recibe las geofences que deben ser monitoreadas. Puede crear una instancia utilizando un Builder
, pasando un Geofence
o una List<Geofence>
, y el tipo de notificación que se activará cuando se crean los geofence(s).
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
La clase GeofencingApi
es el punto de entrada para todas las interacciones con la API de geofencing de Google. Es parte de las API Location y depende de un GoogleApiClient
para que funcione. Utilizará GeofencingApi
para agregar y eliminar geofences.
Para agregar un geofence, llame al método addGeofence()
. Supervisa el área dada utilizando la configuración pasada a GeofencingRequest
y dispara un PendingIntent
cuando se produce una transición de geofence, entrando o saliendo del área.
1 |
PendingResult<Status> addGeofences (GoogleApiClient client, |
2 |
GeofencingRequest geofencingRequest, |
3 |
PendingIntent pendingIntent) |
Para eliminar el geofence, llame a removeGeofences()
. Puede eliminar el geofence usando su identificador de solicitud o su intento pendiente.
1 |
PendingResult<Status> removeGeofences(GoogleApiClient client, |
2 |
List<String> geofenceRequestIds) |
1 |
PendingResult<Status> removeGeofences (GoogleApiClient client, |
2 |
PendingIntent pendingIntent) |
2. Creando una aplicación de geofencing
En este tutorial, creamos una aplicación simple que supervisa la ubicación del usuario y publica una notificación cuando el usuario ingresa o sale de un área geofence. La aplicación consta de una sola Activity
y un IntentService
. También echamos un vistazo rápido a GoogleMap
, GoogleApiClient
, y FusedLocationProviderApi
, y exploramos algunas advertencias de la API de geofence.



Paso 1: Configuración del proyecto
GeofencingApi
es parte de Google Play Services. Para acceder a él, debe configurar correctamente su entorno de desarrollo y crear una instancia de GoogleApiClient
. Cree un nuevo proyecto con una Activity
, en blanco, edite el archivo build.gradle del proyecto como se muestra a continuación y sincronice su proyecto.
Paso 2: Permisos
Necesitamos establecer los permisos correctos para crear y usar geofences. Agregue el siguiente permiso al manifiesto del proyecto:
1 |
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> |
A partir de Android 6.0, la aplicación solicita permiso en tiempo de ejecución y no durante la instalación. Abordaremos esto más adelante en el tutorial.
Paso 3: Crear el diseño
El proyecto consta de un diseño, el diseño MainActity
. Contiene la latitud y longitud actuales del dispositivo, y un fragmento de GoogleMap
que muestra las geofences y la posición del usuario.
Dado que activity_main.xml es bastante sencillo, quiero concentrarme solo en el elemento MapFragment
. Puede ver el diseño completado en los archivos de origen de este tutorial.
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"/> |
Paso 4: Clave API de Google Maps
Como estamos usando un MapFragment
, necesitamos configurar e inicializar una instancia de GoogleMap
. Primero, necesitas obtener una clave API. Una vez que tenga una clave API, agréguela al manifiesto del proyecto.
1 |
<meta-data
|
2 |
android:name="com.google.android.geo.API_KEY" |
3 |
android:value="YOUR_API_KEY"/> |
Vamos a empezar con la instancia de GoogleMap
. Implemente GoogleMap.OnMapReadyCallback
, GoogleMap.OnMapClickListener
, y GoogleMap.OnMarkerClickListener
en la clase Activity
e inicialice el mapa.
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 |
}
|
Paso 5: GoogleApiClient
Para utilizar la interfaz GeofencingApi
, necesitamos un punto de entrada GoogleApiClient
. Implementemos un GoogleApiClient.ConnectionCallbacks
y un GoogleApiClient.OnConnectionFailedListener
en Activity
como se muestra a continuación.
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 |
}
|
Paso 6: FusedLocationProviderApi
También necesitamos acceder a la ubicación actual del usuario. La interfaz FusedLocationProviderApi
nos proporciona esta información y permite un gran nivel de control de la solicitud de ubicación. Esto es muy importante, ya que las solicitudes de ubicación tienen un efecto directo sobre el consumo de la batería del dispositivo.
Ahora, implementemos un LocationListener
. Verifique
si el usuario le dio a la aplicación los permisos apropiados creando la solicitud Location
y muestre su ubicación actual en la pantalla.
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 importante tener en cuenta que el LocationRequest
creado anteriormente no está optimizado para un entorno de producción. El UPDATE_INTERVAL
es demasiado corto y consumiría demasiada energía de la batería. Una configuración más realista para la producción podría ser:
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 |
}
|
Paso 7: Marcadores GoogleMap
Nuestra Activity
necesita dos marcadores diferentes. Un
locationMarker
usa la latitud y longitud que proporciona FusedLocationProviderApi
para informar la ubicación actual del dispositivo. Un geoFenceMarker
es el objetivo para la creación de geofence ya que usa el último toque dado en el mapa para recuperar su posición.
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 |
}
|
Paso 8: Creando un Geofence
Por fin, es hora de crear un geofence. Usamos el geoFenceMarker
como el punto central para el 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 |
}
|
A continuación, creamos el objeto GeofencingRequest
.
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 |
}
|
Utilizamos un objeto PendingIntent
para llamar a un IntentService
que manejará GeofenceEvent
. Creamos el GeofenceTrasitionService.class
más tarde.
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 |
}
|
También dibujamos la geofence en el mapa como referencia visual.
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 |
}
|
El método startGeofence()
es responsable de iniciar el proceso de geofencing en la clase MainActivity
.
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 |
}
|
Paso 9: Servicio de transición Geofence
Ahora podemos finalmente crear el GeofenceTrasitionService.class
mencionado anteriormente. Esta clase extiende IntentService
y es responsable de manejar GeofencingEvent
. Primero, obtenemos este evento de la intención recibida.
1 |
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); |
Luego verificamos si el tipo de transición de geofencing que tuvo lugar nos interesa. Si es así, recuperamos una lista de las geofences activadas y creamos una notificación con las acciones apropiadas.
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 |
}
|
También he implementado algunos métodos de ayuda para hacer que la implementación de la clase sea más fácil de entender.
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. Pruebas
Pruebas en un dispositivo virtual
Es mucho más sencillo probar el geofencing en un dispositivo virtual. Hay varias formas de hacerlo. En Android Studio, abra un dispositivo virtual y haga clic en el botón más opciones en la parte inferior derecha.
En la pestaña Location a la izquierda, ingrese las coordenadas para la ubicación.



Prefiero usar los comandos telnet para controlar el dispositivo virtual. Para usar esto, necesita conectarse al dispositivo desde la línea de comandos usando el siguiente comando:
1 |
telnet localhost [DEVICE_PORT]
|
El puerto del dispositivo se muestra en la ventana del dispositivo virtual. El puerto del dispositivo suele ser igual a 5554.
Es posible que necesite autorizar esta conexión utilizando su auth_token
, pero la línea de comandos le muestra dónde se encuentra. Vaya a esa ubicación y copie el token y el tipo, auth [TU_TOKEN]
.
Ahora puede establecer la ubicación del dispositivo ejecutando el siguiente comando:
1 |
geo fix [LATITUDE] [LONGITUDE] |
Conclusión
Geofencing puede ser una gran adición a su aplicación, ya que puede aumentar considerablemente la participación del usuario. Hay muchas posibilidades para explorar e incluso podría crear una experiencia sofisticada utilizando balizas interiores, como el Estimote. Con balizas interiores, usted sabe exactamente dónde ha pasado el usuario, por ejemplo, un centro comercial.
Agregar Geofencing a un proyecto es simple, pero debemos tener en cuenta el consumo de energía en todo momento. Esto significa que debemos elegir cuidadosamente el tamaño de la geofence y la tasa de actualización porque ambos afectan directamente el consumo de energía de su aplicación.
Por lo tanto, las pruebas son muy importantes para tener una idea realista del consumo de energía de su aplicación. También considere darles a los usuarios la opción de desactivar el geofencing por completo si no quieren o necesitan esta función.