什么是在Android上获取用户当前位置的最简单最稳健的方式?

Android上的LocationManager API似乎对于仅需要偶尔粗略近似用户位置的应用程序而言有点痛苦。

我正在开发的应用程序本身并不是一个位置应用程序,但它确实需要获取用户的位置才能显示附近的商家列表。 它不需要担心用户是否正在四处移动或类似的东西。

以下是我想要做的事情:

  • 向用户显示附近位置的列表。
  • 预先加载用户的位置,以便在Activity X中需要它时,它将可用。
  • 我并不特别关心更新的准确性或频率。 只要抓住一个位置就足够了,只要它不离开。 也许如果我想要看到我会每隔几分钟更新一次位置,但这不是一个重要的优先事项。
  • 适用于任何设备,只要它具有GPS或网络位置提供商即可。
  • 看起来应该不那么难,但在我看来,我必须启动两个不同的位置提供商(GPS和NETWORK)并管理每个生命周期。 不仅如此,我还必须在多个活动中复制相同的代码才能满足#2。 过去我尝试过使用getBestProvider()来将解决方案简化为只使用一个位置提供者,但这似乎只给你最好的“理论”提供者,而不是实际上会给你最好结果的提供者。

    有没有更简单的方法来实现这一点?


    以下是我的工作:

  • 首先,我检查启用了哪些提供程序。 有些可能在设备上被禁用,有些可能在应用程序清单中被禁用。
  • 如果有任何提供者可用,我启动位置侦听器和超时定时器。 在我的例子中是20秒,可能不够GPS,所以你可以放大它。
  • 如果我从位置侦听器获取更新,则使用提供的值。 我停止了听众和计时器。
  • 如果我没有得到任何更新和计时器,我必须使用最后已知的值。
  • 我从可用的提供者中获取最新的已知值并选择最新的值。
  • 以下是我如何使用我的课程:

    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服务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,但仍然存在争用条件。

    我发现的最佳解决方案是用requestSingleUpdate替换requestLocationUpdates

    这是我的版本,基于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);
        }
    }
    

    我从一个定制的循环线程中使用这个类,如下所示:

    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; 
        }
    }
    

    坐标是一个简单的类,有两个属性:经度和纬度。

    链接地址: http://www.djcxy.com/p/90663.html

    上一篇: What is the simplest and most robust way to get the user's current location on Android?

    下一篇: Android – Am I Doing this Right?