很久以前,我编写了一个需要知道设备位置的 Android 应用程序。为了抽象对设备位置的访问,我编写了一个类,该类管理所有与位置相关的事物,存储当前设备的位置,并在 GPS 或 Internet 状态发生变化时调用主要活动以通知用户。
该应用程序一直在所有设备上运行,直到我购买了 Lollipop 随附的三星 Galaxy A5 2016。它适用于我在旧 Android 版本上测试过的所有 Jellybean 设备,但在 A5 2016 上,用户会收到 GPS 状态更改的通知,但方法 onLocationChanged() 不能正常工作,因为存储在此类上的位置始终为空. 为什么这个获取位置的课程在所有设备上都有效,而现在在 Lollipop 上它停止工作了?真是令人沮丧。Android 应用程序应该是向前兼容的。这是管理位置的类的代码,它已停止在 Lollipop 上工作。在 Lollipop 之前,位置曾经存储在类型为 的类的实例属性上Location
,但从 Lollipop 开始,位置不再存储。
package bembibre.personlocator.logic.locationservices;
import android.content.Context;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.SystemClock;
import bembibre.personlocator.activities.MainActivity;
import bembibre.personlocator.logic.Action;
import bembibre.personlocator.logic.internet.InternetManager;
/**
* Clase singleton que permite averiguar la localización del usuario en cada
* momento.
*
* @author misines
*
*/
public class MyLocationManager {
/**
* Única instancia que puede existir de este objeto (es un singleton).
*/
private static MyLocationManager instance = new MyLocationManager();
private static int GPS_INTERVAL = 3000;
/**
* Actividad que llama a este objeto y que necesita conocer la localización
* del usuario.
*/
private MainActivity activity;
/**
* Objeto de Android que permite acceder a la localización del usuario.
*/
private LocationManager locationManager;
/**
* Objeto que se encarga de escuchar cambios de localización basada en red.
*/
private LocationListener networkLocationListener;
/**
* Objeto que se encarga de escuchar cambios de localización basada en GPS.
*/
private LocationListener gpsLocationListener;
private int networkStatus = LocationProvider.OUT_OF_SERVICE;
private int gpsStatus = LocationProvider.OUT_OF_SERVICE;
private EnumGpsStatuses status = EnumGpsStatuses.BAD;
/**
* Este atributo contiene la última localización del usuario determinada
* por red (no muy exacta) o <code>null</code> si no se ha podido
* determinar (por ejemplo porque no hay Internet).
*/
private Location networkLocation;
/**
* Este atributo contiene la última localización del usuario determinada
* por GPS (es más exacta que por red) o <code>null</code> si no se ha
* podido determinar (por ejemplo porque no está activado el GPS).
*/
private Location gpsLocation;
private Long gpsLastLocationMillis;
private boolean networkProviderEnabled = false;
public static MyLocationManager getInstance() {
return MyLocationManager.instance;
}
private void setNetworkLocation(Location location) {
this.networkLocation = location;
}
private void setGpsLocation(Location location) {
this.gpsLocation = location;
}
/**
* Método que es llamado cuando el estado de alguno de los proveedores de
* localización de los que depende esta clase ha cambiado de estado.
*/
private void onStatusChanged() {
switch(this.gpsStatus) {
case LocationProvider.AVAILABLE:
this.status = EnumGpsStatuses.GOOD;
break;
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE:
default:
switch(this.networkStatus) {
case LocationProvider.AVAILABLE:
this.status = EnumGpsStatuses.SO_SO;
break;
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE:
default:
this.status = EnumGpsStatuses.BAD;
}
}
if (this.activity != null) {
this.activity.onGpsStatusChanged(this.status);
}
}
private void setNetworkStatus(int status) {
this.networkStatus = status;
this.onStatusChanged();
}
private void setGpsStatus(int status) {
this.gpsStatus = status;
this.onStatusChanged();
}
private class MyGPSListener implements GpsStatus.Listener {
public void onGpsStatusChanged(int event) {
boolean isGPSFix;
switch (event) {
case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
if (MyLocationManager.this.gpsLastLocationMillis != null) {
isGPSFix = (SystemClock.elapsedRealtime() - MyLocationManager.this.gpsLastLocationMillis) < 3 * MyLocationManager.GPS_INTERVAL;
} else {
isGPSFix = false;
}
if (isGPSFix) { // A fix has been acquired.
MyLocationManager.this.setGpsStatus(LocationProvider.AVAILABLE);
} else { // The fix has been lost.
MyLocationManager.this.setGpsStatus(LocationProvider.OUT_OF_SERVICE);
}
break;
case GpsStatus.GPS_EVENT_FIRST_FIX:
// Do something.
MyLocationManager.this.setGpsStatus(LocationProvider.AVAILABLE);
break;
}
}
}
/**
* Inicializa este objeto para que empiece a funcionar y trate de
* determinar la localización del dispositivo. Además inicializa al
* <code>InternetManager</code>, de modo que una vez que se haya llamado a
* este método, InternetManager estará disponible en todo momento para ver
* si la conexión a Internet funciona o hacer pruebas a dicha conexión.
*
* @param activity
*/
public void start(final MainActivity activity) {
this.activity = activity;
// Acquire a reference to the system Location Manager
this.locationManager = (LocationManager) this.activity.getSystemService(Context.LOCATION_SERVICE);
// Define a listener that responds to location updates
this.networkLocationListener = new LocationListener() {
public void onLocationChanged(Location location) {
// Called when a new location is found by the network location
// provider.
MyLocationManager.this.setNetworkLocation(location);
MyLocationManager.this.networkProviderEnabled = true;
InternetManager.getInstance().makeInternetTest(activity);
}
public void onStatusChanged(String provider, int status, Bundle extras) {
MyLocationManager.this.setNetworkStatus(status);
}
public void onProviderEnabled(String provider) {
MyLocationManager.this.networkProviderEnabled = true;
InternetManager.getInstance().makeInternetTest(activity);
}
public void onProviderDisabled(String provider) {
MyLocationManager.this.networkProviderEnabled = false;
MyLocationManager.this.setNetworkStatus(LocationProvider.OUT_OF_SERVICE);
}
};
this.gpsLocationListener = new LocationListener() {
public void onLocationChanged(Location location) {
// Called when a new location is found by the network location
// provider.
MyLocationManager.this.setGpsLocation(location);
MyLocationManager.this.gpsLastLocationMillis = SystemClock.elapsedRealtime();
//MyLocationManager.this.setGpsStatus(LocationProvider.AVAILABLE);
}
public void onStatusChanged(String provider, int status, Bundle extras) {
MyLocationManager.this.setGpsStatus(status);
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
MyLocationManager.this.setGpsStatus(LocationProvider.OUT_OF_SERVICE);
}
};
// Register the listener with the Location Manager to receive location
// updates
try {
this.locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 3000, 0, this.networkLocationListener
);
this.locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, GPS_INTERVAL, 0, this.gpsLocationListener
);
} catch (Exception e) {
e.printStackTrace();
}
this.locationManager.addGpsStatusListener(new MyGPSListener());
/*
* Hay que inicializar al InternetManager y decirle que avise a este
* objeto cada vez que el Internet vuelva o se pierda. Para ello usamos
* dos objetos Action.
*/
Action action1 = new Action() {
@Override
public void execute(String string) {
MyLocationManager.getInstance().internetHasBeenRecovered();
}
};
Action action2 = new Action() {
@Override
public void execute(String string) {
MyLocationManager.getInstance().internetHasBeenLost();
}
};
InternetManager.getInstance().initialize(activity, action1, action2);
}
public void stop() {
if (this.locationManager != null) {
if (this.networkLocationListener != null) {
this.locationManager.removeUpdates(this.networkLocationListener);
}
if (this.gpsLocationListener != null) {
this.locationManager.removeUpdates(this.gpsLocationListener);
}
}
this.activity = null;
}
/**
* Devuelve la última localización conocida basada en red o
* <code>null</code> si no hay.
*
* @return la última localización conocida basada en red o
* <code>null</code> si no hay.
*/
public Location getNetworkLocation() {
Location result;
if (this.networkLocation == null) {
result = this.gpsLocation;
} else {
result = this.networkLocation;
}
return result;
}
/**
* Si el gps está disponible y tenemos una posición guaradada basada en GPS
* entonces la devuelve. En caso contrario intenta devolver la última
* localización basada en red, y si tampoco está disponible, devuelve
* <code>null</code>.
*
* @return la localización más precisa que esté disponible en este momento.
*/
public Location getFinestLocationAvailable() {
Location result;
switch(this.gpsStatus) {
case LocationProvider.AVAILABLE:
if (this.gpsLocation == null) {
result = this.networkLocation;
} else {
result = this.gpsLocation;
}
case LocationProvider.TEMPORARILY_UNAVAILABLE:
case LocationProvider.OUT_OF_SERVICE:
default:
result = this.networkLocation;
}
return result;
}
/**
* Devuelve el estado actual del GPS.
*
* @return el estado actual del GPS.
*/
public EnumGpsStatuses getStatus() {
return this.status;
}
/**
* Método al que tenemos que llamar siempre que nos enteremos de que
* tenemos Internet, para que se sepa que la localización por red funciona.
*/
public void internetHasBeenRecovered() {
if (this.networkProviderEnabled) {
this.setNetworkStatus(LocationProvider.AVAILABLE);
} else {
this.setNetworkStatus(LocationProvider.OUT_OF_SERVICE);
}
this.onStatusChanged();
}
/**
* Método al que tenemos que llamar siempre que nos enteremos de que la
* conexión a Internet se ha perdido, para que este objeto se dé cuenta de
* que el servicio de localización por red ya no funciona.
*/
public void internetHasBeenLost() {
this.setNetworkStatus(LocationProvider.OUT_OF_SERVICE);
this.onStatusChanged();
}
}