Android上的LocationManager API似乎对一个只需要偶尔粗略估计用户位置的应用程序来说有点麻烦。

我正在开发的应用程序本身并不是一个定位应用程序,但它确实需要获取用户的位置,以便显示附近企业的列表。它不需要担心用户是否在移动或类似的事情。

以下是我想做的:

向用户显示附近位置的列表。预加载用户的位置,以便在“活动X”中需要它时,它将可用。我并不特别关心更新的准确性或频率。只要不太远,只要抓住一个位置就足够了。也许如果我想变得更漂亮,我会每隔几分钟左右更新一次位置,但这不是一个很大的优先事项。适用于任何具有GPS或网络位置提供商的设备。

这似乎并不难,但在我看来,我必须组建两个不同的位置提供商(GPS和NETWORK),并管理每个提供商的生命周期。不仅如此,我还必须在多个活动中复制相同的代码以满足#2。过去,我曾尝试使用getBestProvider()将解决方案简化为仅使用一个位置提供程序,但这似乎只提供了最好的“理论”提供程序,而不是实际会为您提供最佳结果的提供程序。

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


当前回答

要获取并显示用户的当前位置,还可以使用MyLocationOverlay。假设您的活动中有一个mapView字段。要显示用户位置,只需执行以下操作:

myLocationOverlay = new MyLocationOverlay(this, mapView);
myLocationOverlay.enableMyLocation();
mapView.getOverlays().add(myLocationOverlay);

这将从GPS或网络获取当前位置。如果两者都失败,enableMyLocation()将返回false。

至于该区域周围事物的位置,ItemizedOverlay应该可以做到这一点。

我希望我没有误解你的问题。祝你好运

其他回答

以下是我所做的:

首先,我检查启用了哪些提供程序。有些可能在设备上禁用,有些可能在应用程序清单中禁用。如果有任何提供程序可用,我将启动位置侦听器和超时计时器。在我的例子中,这是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,那么您可以使用网络提供的值。

还有一种方法是使用LocationClienthttp://developer.android.com/training/location/retrieve-current.html.但它需要在用户设备上安装Google Play Services apk。

使用Fedor的解决方案,我经历了多次执行回调gotLocation。当gotLocation方法“足够长”时,这似乎是由于重写的LocationListener.onLocationChanged方法中的竞争条件所致。我不确定,但我猜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);
    }
}

我从定制的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是一个简单的类,具有两个财产:纬度和经度。

实际上,我们可以使用两个提供商(GPS和网络)。他们只是分享一个公共听众:

locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 10 * 1000, (float) 10.0, listener);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 90 * 1000, (float) 10.0, listener);

这是必要的,因为总是需要及时调用OnLocationChanged()方法。

我已经创建了一个小应用程序,并逐步描述以获取当前位置GPS坐标。

以下URL中的完整示例源代码:


获取当前位置坐标,城市名称-在Android中


查看其工作原理:

我们只需要在清单文件中添加此权限<uses permission android:name=“android.permission.ACCESS_FINE_LOCATION”></使用权限>并像这样创建LocationManager实例LocationManager LocationManager=(LocationManager)getSystemService(Context.LOCATION_SERVICE);检查GPS是否已启用然后实现LocationListener和Get CoordinatesLocationListener LocationListener=新的MyLocationListeners();locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,5000,10,locationListener);下面是要做的示例代码


/*----------Listener class to get coordinates ------------- */
private class MyLocationListener implements LocationListener {

    @Override
    public void onLocationChanged(Location loc) {
        editLocation.setText("");
        pb.setVisibility(View.INVISIBLE);
        Toast.makeText(
            getBaseContext(),
            "Location changed: Lat: " + loc.getLatitude() + " Lng: "
                + loc.getLongitude(), Toast.LENGTH_SHORT).show();
        String longitude = "Longitude: " + loc.getLongitude();
        Log.v(TAG, longitude);
        String latitude = "Latitude: " + loc.getLatitude();
        Log.v(TAG, latitude);
        /*-------to get City-Name from coordinates -------- */
        String cityName = null;
        Geocoder gcd = new Geocoder(getBaseContext(), Locale.getDefault());
        List<Address> addresses;
        try {
            addresses = gcd.getFromLocation(loc.getLatitude(),
                loc.getLongitude(), 1);
            if (addresses.size() > 0)
                System.out.println(addresses.get(0).getLocality());
            cityName = addresses.get(0).getLocality();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String s = longitude + "\n" + latitude + "\n\nMy Current City is: "
            + cityName;
        editLocation.setText(s);
    }

    @Override
    public void onProviderDisabled(String provider) {}

    @Override
    public void onProviderEnabled(String provider) {}

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {}
}

在过去一年多的时间里,我使用GPS_PROVIDER和NETWORK_PROVIDER的组合来获取当前位置,它运行得很好,但从过去几个月开始,我在经过长时间的延迟后才获取位置,所以我改用了最新的API FusedLocationProviderClient,它运行的很好。

下面是我使用FusedLocationProviderClient编写的获取当前位置的类。在下面的代码中,我使用了一个计时器等待一段时间以获取当前位置,我安排了计时器15秒的延迟,您可以根据您的情况进行更改。

private static FusedLocationService ourInstance;
private final LocationRequest locationRequest;
private FusedLocationProviderClient mFusedLocationClient;
private Location mLastLocation;
private Context context;
private FindOutLocation findOutLocation;
private boolean callbackTriggered = false;
private Timer timer;

public static FusedLocationService getInstance(Context pContext) {

    if (null == ourInstance) ourInstance = new FusedLocationService(pContext);

    return ourInstance;
}

private FusedLocationService(Context pContext) {
    context = pContext;
    mFusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
    locationRequest = getLocationRequest();
    requestLocation(context);
}

public Location getLastKnownLocation() {
    return mLastLocation;
}

private void requestLocation(Context context) {

    if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        return;
    }
    mFusedLocationClient.requestLocationUpdates(locationRequest, mLocationCallback, null);
    mFusedLocationClient.getLastLocation().addOnSuccessListener(location -> {
        if (location != null) {
            mLastLocation = location;
            triggerCallback(mLastLocation);
        }
    });
}

private LocationRequest getLocationRequest() {
    LocationRequest locationRequest = new LocationRequest();
    long INTERVAL = 10 * 1000;
    long FASTEST_INTERVAL = 5 * 1000;
    locationRequest.setInterval(INTERVAL);
    locationRequest.setFastestInterval(FASTEST_INTERVAL);
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    return locationRequest;
}

private LocationCallback mLocationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(LocationResult locationResult) {
        for (Location location : locationResult.getLocations()) {
            if (location != null) mLastLocation = location;
        }
        if (null != mLastLocation) triggerCallback(mLastLocation);
    }
};

public static abstract class FindOutLocation {
    public abstract void gotLocation(Location location);
}

@SuppressLint("MissingPermission")
public void findLocation(FindOutLocation findOutLocation) {
    long TIMER_TIME_OUT = 15 * 1000;
    this.findOutLocation = findOutLocation;
    callbackTriggered = false;

    try {
        requestLocation(context);
        timer = new Timer();
        timer.schedule(new GetLastLocation(context), TIMER_TIME_OUT);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private class GetLastLocation extends TimerTask {
    Context context;

    GetLastLocation(Context context) {
        this.context = context;
    }

    @Override
    public void run() {
        triggerCallback(mLastLocation);
    }
}

private void triggerCallback(Location location) {
    if (null != location) mLastLocation = location;
    if (!callbackTriggered && null != findOutLocation) {
        callbackTriggered = true;
        removeLocationUpdates();
        findOutLocation.gotLocation(location);
        findOutLocation = null;
    }
}

private void removeLocationUpdates() {
    if (null != timer) timer.cancel();
    if (null != mFusedLocationClient)
        mFusedLocationClient.removeLocationUpdates(mLocationCallback);
}
}

这是从活动中调用的,下面是代码

    FusedLocationService.FindOutLocation findOutLocation = new FusedLocationService.FindOutLocation() {
        @Override
        public void gotLocation(Location currentLocation) {
            if (currentLocation != null) {
                /*TODO DO SOMETHING WITH CURRENT LOCATION*/
            }
        }
    };
    FusedLocationService.getInstance(this).findLocation(findOutLocation);

在AndroidManifest.xml中添加以下条目

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />