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解决方案的改进。我们可以使用位置管理器的requestSingleUpdate方法,而不是使用“0”时间间隔和“0”距离请求位置。更新的代码(kotlin版本)

import android.annotation.SuppressLint
import android.content.Context
import android.location.Criteria
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import java.util.*

@SuppressLint("MissingPermission")
class AppLocationProvider {

  private lateinit var timer: Timer
  private var locationManager: LocationManager? = null
  private lateinit var locationCallBack: LocationCallBack
  private var gpsEnabled = false
  private var networkEnabled = false

  private var locationListener: LocationListener = object : LocationListener {

    override fun onLocationChanged(location: Location) {
      timer.cancel()
      locationCallBack.locationResult(location)
    }

    override fun onProviderDisabled(provider: String) {}
    override fun onProviderEnabled(provider: String) {}
    override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
  }

  fun getLocation(context : Context, callBack: LocationCallBack): Boolean {
    locationCallBack = callBack
    if (locationManager == null)
      locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?

    //exceptions will be thrown if provider is not permitted.
    try {
      gpsEnabled = locationManager!!.isProviderEnabled(LocationManager.GPS_PROVIDER)
    } catch (ex: Exception) {
      ex.printStackTrace()
    }

    try {
      networkEnabled = locationManager!!.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
    } catch (ex: Exception) {
      ex.printStackTrace()
    }

    //don't start listeners if no provider is enabled
    if (!gpsEnabled && !networkEnabled)
      return false

    val criteria = Criteria()
    if (gpsEnabled) {
      criteria.accuracy = Criteria.ACCURACY_FINE
    } else {
      criteria.accuracy = Criteria.ACCURACY_COARSE
    }
    locationManager!!.requestSingleUpdate(criteria, locationListener, null)

    timer = Timer()
    timer.schedule(GetLastKnownLocation(), 5000)
    return true
  }

  inner class GetLastKnownLocation : TimerTask() {

    override fun run() {
      locationManager!!.removeUpdates(locationListener)

      var netLoc: Location? = null
      var gpsLoc: Location? = null

      if (gpsEnabled)
        gpsLoc = locationManager!!.getLastKnownLocation(LocationManager.GPS_PROVIDER)
      if (networkEnabled)
        netLoc = locationManager!!.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)

      //check which value use the latest one
      if (gpsLoc != null && netLoc != null) {
        if (gpsLoc.time > netLoc.time)
          locationCallBack.locationResult(gpsLoc)
        else
          locationCallBack.locationResult(netLoc)
        return
      }

      if (gpsLoc != null) {
        locationCallBack.locationResult(gpsLoc)
        return
      }
      if (netLoc != null) {
        locationCallBack.locationResult(netLoc)
        return
      }
      locationCallBack.locationResult(null)
    }
  }

  interface LocationCallBack {
    fun locationResult(location: Location?)
  }
}

要获取位置,只需调用getLocation方法-

AppLocationProvider().getLocation(context, object : AppLocationProvider.LocationCallBack {
  override fun locationResult(location: Location?) {
    // use location, this might get called in a different thread if a location is a last known location. In that case, you can post location on main thread
  }
})

注:在调用getLocation方法之前,必须授予所需的位置权限。

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

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

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

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

使用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_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" />