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

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

以下是我想做的:

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

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

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


当前回答

我已经创建了一个小应用程序,并逐步描述以获取当前位置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) {}
}

其他回答

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

这里有点晚了,但在这种情况下,我会使用谷歌地图API,并使用谷歌地图的纬度和经度API标记附近的位置。另外,如果你能在地图上显示他/她的位置,用户体验会更好。无需担心用户位置的更新或使用android api搜索。让谷歌地图为您处理内部内容。

@emmby可能已经在他的应用程序中解决了这个问题,但为了将来的参考,我向其他开发人员推荐的是查看谷歌地图API中的位置特定内容。

编辑:在谷歌地图中显示用户位置的链接

我不确定基于位置的服务是否可以从GPS以外的其他基础设施获取位置,但根据那篇文章,这似乎是可能的:

应用程序可以调用几种类型的定位方法。使用移动电话网络:当前单元格ID可用于识别基站收发信机(BTS)设备正在通信以及该BTS的位置。显然,这种方法的准确性取决于单元格的大小,以及可能非常不准确。GSM蜂窝可以是2到20直径千米。另外与细胞ID一起使用的技术可以精度在150米以内。使用卫星:全球定位系统(GPS),受控美国国防部使用由24颗卫星组成的星座围绕地球运行。GPS确定通过计算设备的位置时间差信号来自不同的卫星到达接受者GPS信号是编码的,所以移动设备必须配备使用GPS接收器。GPS是可能是最准确的方法(如果GPS接收机具有清晰的天空视图),但它也有一些缺点:硬件可能很昂贵电池使用时,需要冷启动后的热身对可见卫星的初步定位。它还受到“峡谷效应”的影响在城市,卫星能见度是间歇性的。使用短程定位信标:相对小面积,如单个建筑,局域网可以提供其他位置服务。例如,适当地配备的设备可以使用蓝牙短程定位。

你可以一直使用LocationManager.getLastKnownLocation(),但就像它说的那样,它可能已经过时了。

获取一般位置的一个简单方法是注册网络(通常很快)。

LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(
     LocationManager.NETWORK_PROVIDER, 1000, 1000, this);

然后做

locationManager.removeUpdates(this);

在侦听器的onLocationChanged()方法中。

这是我请求用户权限的方式。

在AndroidManifest.xml中的应用程序标记外添加这些权限请求。

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

然后在App Gradle文件中添加Google的位置依赖项。

implementation 'com.google.android.gms:play-services-location:15.0.0'

现在声明一些全局变量。

private lateinit var mFusedLocationProvider:FusedLocationProviderClient
private lateinit var mLocationCallback: LocationCallback
private lateinit var mLocationRequest: LocationRequest
private var mLocationPermissionGranted:Boolean = false

在活动的OnCreate方法中(我无法正确格式化代码,对此深表歉意)

mFusedLocationProvider = LocationServices.getFusedLocationProviderClient(this)

//Location Callback
mLocationCallback = object: LocationCallback(){
 override fun onLocationResult(p0: LocationResult?) {
  if(p0==null){
     //todo(request user to enable location from settings then remove return)
     return
  }else{
      getDeviceLocation()
       }
  }
}

//Location Request
mLocationRequest = LocationRequest.create()
mLocationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
//Set the Interval for Latest Interval Update
mLocationRequest.interval = 5000
//Set How Many Location Updated you Want
mLocationRequest.numUpdates = 1

getLocationPermission()
getDeviceLocation()

现在创建这两个函数。

 private fun getLocationPermission() {

            val permission:Array<String> = arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION,android.Manifest.permission.ACCESS_COARSE_LOCATION)
            if(ContextCompat.checkSelfPermission(applicationContext,Constant.FINE_LOCATION)== PermissionChecker.PERMISSION_GRANTED){
                if(ContextCompat.checkSelfPermission(applicationContext,Constant.COARSE_LOCATION)== PermissionChecker.PERMISSION_GRANTED){
                    mLocationPermissionGranted = true
                }
            }else{
                ActivityCompat.requestPermissions(this,permission,Constant.LOCATION_REQUEST_CODE)
            }

    }

第二种方法

private fun getDeviceLocation() {
        try{
            if(mLocationPermissionGranted){

                mFusedLocationProvider.lastLocation.addOnCompleteListener(this,{task: Task<Location> ->
                    if(task.isSuccessful){
                        var currentLocation: Location? = task.result
                        if(currentLocation!=null){

                            Log.i("Location","Latitude is ${currentLocation.latitude} and Longitude" +
                                    "${currentLocation.longitude}")
                        }

                        else
                            mFusedLocationProvider.requestLocationUpdates(mLocationRequest,mLocationCallback,null)
                    }
                })
            }
        }catch (e:SecurityException){
            Log.e("Error", "Security Exception ${e.message}")
        }
    }

对于常量.kt

class Constant{
    companion object {

        //Location Request Settings
        const val SET_INTERVAL:Long = 2000
        const val NUM_UPDATES:Int = 1

        //Location Permission
        const val FINE_LOCATION:String = android.Manifest.permission.ACCESS_FINE_LOCATION
        const val COARSE_LOCATION:String = android.Manifest.permission.ACCESS_COARSE_LOCATION
    }
}