ずっと前に、デバイスの位置を知る必要がある Android アプリケーションをプログラムしました。デバイスの位置へのアクセスを抽象化する目的で、すべての位置関連のものを管理し、現在のデバイスの位置を保存し、ユーザーに通知するために GPS またはインターネットの状態が変化したときにメイン アクティビティを呼び出すクラスをプログラムしました。
このアプリケーションは、Lollipop が付属する Samsung Galaxy A5 2016 を購入するまで、すべてのデバイスで動作し続けました。これは、私がテストしたすべての Jellybean デバイスと古い Android バージョンで動作しますが、A5 2016 では、ユーザーは GPS ステータスの変更を通知されますが、このクラスに保存されている場所は常に null であるため、メソッド 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();
}
}