Android上的LocationManager API似乎对一个只需要偶尔粗略估计用户位置的应用程序来说有点麻烦。
我正在开发的应用程序本身并不是一个定位应用程序,但它确实需要获取用户的位置,以便显示附近企业的列表。它不需要担心用户是否在移动或类似的事情。
以下是我想做的:
向用户显示附近位置的列表。预加载用户的位置,以便在“活动X”中需要它时,它将可用。我并不特别关心更新的准确性或频率。只要不太远,只要抓住一个位置就足够了。也许如果我想变得更漂亮,我会每隔几分钟左右更新一次位置,但这不是一个很大的优先事项。适用于任何具有GPS或网络位置提供商的设备。
这似乎并不难,但在我看来,我必须组建两个不同的位置提供商(GPS和NETWORK),并管理每个提供商的生命周期。不仅如此,我还必须在多个活动中复制相同的代码以满足#2。过去,我曾尝试使用getBestProvider()将解决方案简化为仅使用一个位置提供程序,但这似乎只提供了最好的“理论”提供程序,而不是实际会为您提供最佳结果的提供程序。
有没有更简单的方法来实现这一点?
实际上,我们可以使用两个提供商(GPS和网络)。他们只是分享一个公共听众:
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 10 * 1000, (float) 10.0, listener);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 90 * 1000, (float) 10.0, listener);
这是必要的,因为总是需要及时调用OnLocationChanged()方法。
public static Location getBestLocation(Context ctxt) {
Location gpslocation = getLocationByProvider(
LocationManager.GPS_PROVIDER, ctxt);
Location networkLocation = getLocationByProvider(
LocationManager.NETWORK_PROVIDER, ctxt);
Location fetchedlocation = null;
// if we have only one location available, the choice is easy
if (gpslocation != null) {
Log.i("New Location Receiver", "GPS Location available.");
fetchedlocation = gpslocation;
} else {
Log.i("New Location Receiver",
"No GPS Location available. Fetching Network location lat="
+ networkLocation.getLatitude() + " lon ="
+ networkLocation.getLongitude());
fetchedlocation = networkLocation;
}
return fetchedlocation;
}
/**
* get the last known location from a specific provider (network/gps)
*/
private static Location getLocationByProvider(String provider, Context ctxt) {
Location location = null;
// if (!isProviderSupported(provider)) {
// return null;
// }
LocationManager locationManager = (LocationManager) ctxt
.getSystemService(Context.LOCATION_SERVICE);
try {
if (locationManager.isProviderEnabled(provider)) {
location = locationManager.getLastKnownLocation(provider);
}
} catch (IllegalArgumentException e) {
Log.i("New Location Receiver", "Cannot access Provider " + provider);
}
return location;
}
最近进行了重构以获得代码的位置,学习了一些好的想法,最终实现了一个相对完善的库和演示。
//request all valid provider(network/gps)
private boolean requestAllProviderUpdates() {
checkRuntimeEnvironment();
checkPermission();
if (isRequesting) {
EasyLog.d("Request location update is busy");
return false;
}
long minTime = getCheckTimeInterval();
float minDistance = getCheckMinDistance();
if (mMapLocationListeners == null) {
mMapLocationListeners = new HashMap<>();
}
mValidProviders = getValidProviders();
if (mValidProviders == null || mValidProviders.isEmpty()) {
throw new IllegalArgumentException("Not available provider.");
}
for (String provider : mValidProviders) {
LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
if (location == null) {
EasyLog.e("LocationListener callback location is null.");
return;
}
printf(location);
mLastProviderTimestamp = location.getTime();
if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
finishResult(location);
} else {
doLocationResult(location);
}
removeProvider(location.getProvider());
if (isEmptyValidProviders()) {
requestTimeoutMsgInit();
removeUpdates();
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
};
getLocationManager().requestLocationUpdates(provider, minTime, minDistance, locationListener);
mMapLocationListeners.put(provider, locationListener);
EasyLog.d("Location request %s provider update.", provider);
}
isRequesting = true;
return true;
}
//remove request update
public void removeUpdates() {
checkRuntimeEnvironment();
LocationManager locationManager = getLocationManager();
if (mMapLocationListeners != null) {
Set<String> keys = mMapLocationListeners.keySet();
for (String key : keys) {
LocationListener locationListener = mMapLocationListeners.get(key);
if (locationListener != null) {
locationManager.removeUpdates(locationListener);
EasyLog.d("Remove location update, provider is " + key);
}
}
mMapLocationListeners.clear();
isRequesting = false;
}
}
//Compared with the last successful position, to determine whether you need to filter
private boolean isNeedFilter(Location location) {
checkLocation(location);
if (mLastLocation != null) {
float distance = location.distanceTo(mLastLocation);
if (distance < getCheckMinDistance()) {
return true;
}
if (location.getAccuracy() >= mLastLocation.getAccuracy()
&& distance < location.getAccuracy()) {
return true;
}
if (location.getTime() <= mLastProviderTimestamp) {
return true;
}
}
return false;
}
private void doLocationResult(Location location) {
checkLocation(location);
if (isNeedFilter(location)) {
EasyLog.d("location need to filtered out, timestamp is " + location.getTime());
finishResult(mLastLocation);
} else {
finishResult(location);
}
}
//Return to the finished position
private void finishResult(Location location) {
checkLocation(location);
double latitude = location.getLatitude();
double longitude = location.getLongitude();
float accuracy = location.getAccuracy();
long time = location.getTime();
String provider = location.getProvider();
if (mLocationResultListeners != null && !mLocationResultListeners.isEmpty()) {
String format = "Location result:<%f, %f> Accuracy:%f Time:%d Provider:%s";
EasyLog.i(String.format(format, latitude, longitude, accuracy, time, provider));
mLastLocation = location;
synchronized (this) {
Iterator<LocationResultListener> iterator = mLocationResultListeners.iterator();
while (iterator.hasNext()) {
LocationResultListener listener = iterator.next();
if (listener != null) {
listener.onResult(location);
}
iterator.remove();
}
}
}
}
完整代码:https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java
*每次请求完成位置时,最好删除更新,否则手机状态栏将始终显示定位图标。
在过去一年多的时间里,我使用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" />
对@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方法之前,必须授予所需的位置权限。