Android 上的LocationManager
API 似乎对于仅需要偶尔粗略估计用户位置的应用程序来说有点痛苦。
我正在处理的应用程序本身并不是一个位置应用程序,但它确实需要获取用户的位置才能显示附近的商家列表。它不需要担心用户是否在移动或类似的东西。
这是我想做的事情:
Activity
X 中我需要它时,它将可用。 看起来它应该不那么难,但在我看来,我必须启动两个不同的位置提供商(GPS 和 NETWORK)并管理每个生命周期。不仅如此,我还必须在多个活动中复制相同的代码以满足#2。我曾尝试过使用getBestProvider()
将解决方案缩减为仅使用一个位置提供程序,但这似乎只能为您提供最好的 “理论” 提供程序,而不是实际上会为您提供最佳结果的提供程序。
有没有更简单的方法来实现这一目标?
这是我做的:
这是我如何使用我的课程:
LocationResult locationResult = new LocationResult(){
@Override
public void gotLocation(Location location){
//Got the location!
}
};
MyLocation myLocation = new MyLocation();
myLocation.getLocation(this, locationResult);
这是 MyLocation 类:
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
public class MyLocation {
Timer timer1;
LocationManager lm;
LocationResult locationResult;
boolean gps_enabled=false;
boolean network_enabled=false;
public boolean getLocation(Context context, LocationResult result)
{
//I use LocationResult callback class to pass location value from MyLocation to user code.
locationResult=result;
if(lm==null)
lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
//exceptions will be thrown if provider is not permitted.
try{gps_enabled=lm.isProviderEnabled(LocationManager.GPS_PROVIDER);}catch(Exception ex){}
try{network_enabled=lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);}catch(Exception ex){}
//don't start listeners if no provider is enabled
if(!gps_enabled && !network_enabled)
return false;
if(gps_enabled)
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);
if(network_enabled)
lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
timer1=new Timer();
timer1.schedule(new GetLastLocation(), 20000);
return true;
}
LocationListener locationListenerGps = new LocationListener() {
public void onLocationChanged(Location location) {
timer1.cancel();
locationResult.gotLocation(location);
lm.removeUpdates(this);
lm.removeUpdates(locationListenerNetwork);
}
public void onProviderDisabled(String provider) {}
public void onProviderEnabled(String provider) {}
public void onStatusChanged(String provider, int status, Bundle extras) {}
};
LocationListener locationListenerNetwork = new LocationListener() {
public void onLocationChanged(Location location) {
timer1.cancel();
locationResult.gotLocation(location);
lm.removeUpdates(this);
lm.removeUpdates(locationListenerGps);
}
public void onProviderDisabled(String provider) {}
public void onProviderEnabled(String provider) {}
public void onStatusChanged(String provider, int status, Bundle extras) {}
};
class GetLastLocation extends TimerTask {
@Override
public void run() {
lm.removeUpdates(locationListenerGps);
lm.removeUpdates(locationListenerNetwork);
Location net_loc=null, gps_loc=null;
if(gps_enabled)
gps_loc=lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if(network_enabled)
net_loc=lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
//if there are both values use the latest one
if(gps_loc!=null && net_loc!=null){
if(gps_loc.getTime()>net_loc.getTime())
locationResult.gotLocation(gps_loc);
else
locationResult.gotLocation(net_loc);
return;
}
if(gps_loc!=null){
locationResult.gotLocation(gps_loc);
return;
}
if(net_loc!=null){
locationResult.gotLocation(net_loc);
return;
}
locationResult.gotLocation(null);
}
}
public static abstract class LocationResult{
public abstract void gotLocation(Location location);
}
}
有人可能也想修改我的逻辑。例如,如果从网络提供程序获得更新,请不要停止侦听器,而是继续等待。 GPS 提供更准确的数据,因此值得等待。如果计时器过去并且您已从网络更新而不是从 GPS 更新,那么您可以使用网络提供的值。
另一种方法是使用 LocationClient http://developer.android.com/training/location/retrieve-current.html 。但它需要在用户设备上安装 Google Play Services apk。
在搜索最佳实现后,如何获得最佳的精确用户位置,我设法结合所有最好的方法,并提出以下类:
/**
* Retrieve accurate location from GPS or network services.
*
*
* Class usage example:
*
* public void onCreate(Bundle savedInstanceState) {
* ...
* my_location = new MyLocation();
* my_location.init(main.this, locationResult);
* }
*
*
* public LocationResult locationResult = new LocationResult(){
* @Override
* public void gotLocation(final Location location){
* // do something
* location.getLongitude();
* location.getLatitude();
* }
* };
*/
class MyLocation{
/**
* If GPS is enabled.
* Use minimal connected satellites count.
*/
private static final int min_gps_sat_count = 5;
/**
* Iteration step time.
*/
private static final int iteration_timeout_step = 500;
LocationResult locationResult;
private Location bestLocation = null;
private Handler handler = new Handler();
private LocationManager myLocationManager;
public Context context;
private boolean gps_enabled = false;
private int counts = 0;
private int sat_count = 0;
private Runnable showTime = new Runnable() {
public void run() {
boolean stop = false;
counts++;
System.println("counts=" + counts);
//if timeout (1 min) exceeded, stop tying
if(counts > 120){
stop = true;
}
//update last best location
bestLocation = getLocation(context);
//if location is not ready or don`t exists, try again
if(bestLocation == null && gps_enabled){
System.println("BestLocation not ready, continue to wait");
handler.postDelayed(this, iteration_timeout_step);
}else{
//if best location is known, calculate if we need to continue to look for better location
//if gps is enabled and min satellites count has not been connected or min check count is smaller then 4 (2 sec)
if(stop == false && !needToStop()){
System.println("Connected " + sat_count + " sattelites. continue waiting..");
handler.postDelayed(this, iteration_timeout_step);
}else{
System.println("#########################################");
System.println("BestLocation finded return result to main. sat_count=" + sat_count);
System.println("#########################################");
// removing all updates and listeners
myLocationManager.removeUpdates(gpsLocationListener);
myLocationManager.removeUpdates(networkLocationListener);
myLocationManager.removeGpsStatusListener(gpsStatusListener);
sat_count = 0;
// send best location to locationResult
locationResult.gotLocation(bestLocation);
}
}
}
};
/**
* Determine if continue to try to find best location
*/
private Boolean needToStop(){
if(!gps_enabled){
return true;
}
else if(counts <= 4){
return false;
}
if(sat_count < min_gps_sat_count){
//if 20-25 sec and 3 satellites found then stop
if(counts >= 40 && sat_count >= 3){
return true;
}
return false;
}
}
return true;
}
/**
* Best location abstract result class
*/
public static abstract class LocationResult{
public abstract void gotLocation(Location location);
}
/**
* Initialize starting values and starting best location listeners
*
* @param Context ctx
* @param LocationResult result
*/
public void init(Context ctx, LocationResult result){
context = ctx;
locationResult = result;
myLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
gps_enabled = (Boolean) myLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
bestLocation = null;
counts = 0;
// turning on location updates
myLocationManager.requestLocationUpdates("network", 0, 0, networkLocationListener);
myLocationManager.requestLocationUpdates("gps", 0, 0, gpsLocationListener);
myLocationManager.addGpsStatusListener(gpsStatusListener);
// starting best location finder loop
handler.postDelayed(showTime, iteration_timeout_step);
}
/**
* GpsStatus listener. OnChainged counts connected satellites count.
*/
public final GpsStatus.Listener gpsStatusListener = new GpsStatus.Listener() {
public void onGpsStatusChanged(int event) {
if(event == GpsStatus.GPS_EVENT_SATELLITE_STATUS){
try {
// Check number of satellites in list to determine fix state
GpsStatus status = myLocationManager.getGpsStatus(null);
Iterable<GpsSatellite>satellites = status.getSatellites();
sat_count = 0;
Iterator<GpsSatellite>satI = satellites.iterator();
while(satI.hasNext()) {
GpsSatellite satellite = satI.next();
System.println("Satellite: snr=" + satellite.getSnr() + ", elevation=" + satellite.getElevation());
sat_count++;
}
} catch (Exception e) {
e.printStackTrace();
sat_count = min_gps_sat_count + 1;
}
System.println("#### sat_count = " + sat_count);
}
}
};
/**
* Gps location listener.
*/
public final LocationListener gpsLocationListener = new LocationListener(){
@Override
public void onLocationChanged(Location location){
}
public void onProviderDisabled(String provider){}
public void onProviderEnabled(String provider){}
public void onStatusChanged(String provider, int status, Bundle extras){}
};
/**
* Network location listener.
*/
public final LocationListener networkLocationListener = new LocationListener(){
@Override
public void onLocationChanged(Location location){
}
public void onProviderDisabled(String provider){}
public void onProviderEnabled(String provider){}
public void onStatusChanged(String provider, int status, Bundle extras){}
};
/**
* Returns best location using LocationManager.getBestProvider()
*
* @param context
* @return Location|null
*/
public static Location getLocation(Context context){
System.println("getLocation()");
// fetch last known location and update it
try {
LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(false);
criteria.setCostAllowed(true);
String strLocationProvider = lm.getBestProvider(criteria, true);
System.println("strLocationProvider=" + strLocationProvider);
Location location = lm.getLastKnownLocation(strLocationProvider);
if(location != null){
return location;
}
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
如果启用 GPS, min_gps_sat_count
将尝试连接到min_gps_sat_count
卫星。否则返回LocationManager.getBestProvider()
位置。检查代码!
有了 Fedor 的解决方案,我经历了多次回调gotLocation
执行。这似乎是由于重写的LocationListener.onLocationChanged
方法中的竞争条件 ,当 gotLocation 方法“足够长” 时 。我不确定,但我猜removeUpdates
阻止 Looper 队列中新消息的排队,但它不会删除那些已经入队但尚未消耗的消息。因此竞争条件。
为了减少这种错误行为的可能性,可以在触发 onLocationChanged 事件之前调用 removeUpdates,但仍然存在竞争条件。
我找到的最佳解决方案是将requestLocationUpdates
替换为requestSingleUpdate
。
这是我的版本,基于 Fedor 的解决方案,使用 Handler 向 looper 线程发送消息:
public class LocationResolver {
private Timer timer;
private LocationManager locationManager;
private LocationResult locationResult;
private boolean gpsEnabled = false;
private boolean networkEnabled = false;
private Handler locationTimeoutHandler;
private final Callback locationTimeoutCallback = new Callback() {
public boolean handleMessage(Message msg) {
locationTimeoutFunc();
return true;
}
private void locationTimeoutFunc() {
locationManager.removeUpdates(locationListenerGps);
locationManager.removeUpdates(locationListenerNetwork);
Location networkLocation = null, gpsLocation = null;
if (gpsEnabled)
gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (networkEnabled)
networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
// if there are both values use the latest one
if (gpsLocation != null && networkLocation != null) {
if (gpsLocation.getTime() > networkLocation.getTime())
locationResult.gotLocation(gpsLocation);
else
locationResult.gotLocation(networkLocation);
return;
}
if (gpsLocation != null) {
locationResult.gotLocation(gpsLocation);
return;
}
if (networkLocation != null) {
locationResult.gotLocation(networkLocation);
return;
}
locationResult.gotLocation(null);
}
};
private final LocationListener locationListenerGps = new LocationListener() {
public void onLocationChanged(Location location) {
timer.cancel();
locationResult.gotLocation(location);
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListenerNetwork);
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
private final LocationListener locationListenerNetwork = new LocationListener() {
public void onLocationChanged(Location location) {
timer.cancel();
locationResult.gotLocation(location);
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListenerGps);
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
public void prepare() {
locationTimeoutHandler = new Handler(locationTimeoutCallback);
}
public synchronized boolean getLocation(Context context, LocationResult result, int maxMillisToWait) {
locationResult = result;
if (locationManager == null)
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// exceptions will be thrown if provider is not permitted.
try {
gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
} catch (Exception ex) {
}
try {
networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch (Exception ex) {
}
// don't start listeners if no provider is enabled
if (!gpsEnabled && !networkEnabled)
return false;
if (gpsEnabled)
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, locationListenerGps, Looper.myLooper());
//locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);
if (networkEnabled)
locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, locationListenerNetwork, Looper.myLooper());
//locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
timer = new Timer();
timer.schedule(new GetLastLocationTask(), maxMillisToWait);
return true;
}
private class GetLastLocationTask extends TimerTask {
@Override
public void run() {
locationTimeoutHandler.sendEmptyMessage(0);
}
}
public static abstract class LocationResult {
public abstract void gotLocation(Location location);
}
}
我从定制的 looper 线程中使用这个类,如下所示:
public class LocationGetter {
private final Context context;
private Location location = null;
private final Object gotLocationLock = new Object();
private final LocationResult locationResult = new LocationResult() {
@Override
public void gotLocation(Location location) {
synchronized (gotLocationLock) {
LocationGetter.this.location = location;
gotLocationLock.notifyAll();
Looper.myLooper().quit();
}
}
};
public LocationGetter(Context context) {
if (context == null)
throw new IllegalArgumentException("context == null");
this.context = context;
}
public synchronized Coordinates getLocation(int maxWaitingTime, int updateTimeout) {
try {
final int updateTimeoutPar = updateTimeout;
synchronized (gotLocationLock) {
new Thread() {
public void run() {
Looper.prepare();
LocationResolver locationResolver = new LocationResolver();
locationResolver.prepare();
locationResolver.getLocation(context, locationResult, updateTimeoutPar);
Looper.loop();
}
}.start();
gotLocationLock.wait(maxWaitingTime);
}
} catch (InterruptedException e1) {
e1.printStackTrace();
}
if (location != null)
coordinates = new Coordinates(location.getLatitude(), location.getLongitude());
else
coordinates = Coordinates.UNDEFINED;
return coordinates;
}
}
其中 Coordinates 是一个具有两个属性的简单类:纬度和经度。