存在的问题:
尽快在阈值范围内获取用户的当前位置,同时节省电池。
为什么这个问题是问题:
首先,android有两个提供者;网络和GPS。有时网络更好,有时GPS更好。
这里的“更好”指的是速度与准确度之比。
我愿意牺牲几米的精度,只要我能在不打开GPS的情况下立即得到位置。
其次,如果您请求更新位置更改,如果当前位置稳定,则不会发送任何内容。
谷歌有一个确定“最佳”位置的例子:http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
但是我认为它远不如它应该/可以做到的那么好。
我有点困惑为什么谷歌没有一个规范化的API的位置,开发人员不应该关心位置从哪里来,你应该指定你想要什么,手机应该为你选择。
我需要帮助的方面:
我需要找到一种确定“最佳”位置的好方法,可能是通过一些启发式或第三方库。
这并不意味着要确定最好的提供者!
我可能会使用所有的提供者,并从中选择最好的。
app背景:
该应用程序将在固定的时间间隔(比如每10分钟左右)收集用户的位置,并将其发送到服务器。
应用程序应该尽可能节省电池,位置应该有X(50-100?)米的精度。
我们的目标是以后能够在地图上绘制用户白天的路径,所以我需要足够的准确性。
Misc:
你认为期望和接受的精确度的合理值是多少?
我一直在用接受的100万和想要的30万,这要求太高了吗?
我希望以后能够在地图上绘制用户的路径。
要求100米,接受500米更好吗?
另外,现在我的GPS每次更新位置最多60秒,如果你在室内,精度可能是200米,这是不是太短了?
这是我目前的代码,任何反馈都是赞赏的(除了缺乏错误检查,这是TODO):
protected void runTask() {
final LocationManager locationManager = (LocationManager) context
.getSystemService(Context.LOCATION_SERVICE);
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER));
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
if (getLocationQuality(bestLocation) != LocationQuality.GOOD) {
Looper.prepare();
setLooper(Looper.myLooper());
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
updateBestLocation(location);
if (getLocationQuality(bestLocation) != LocationQuality.GOOD)
return;
// We're done
Looper l = getLooper();
if (l != null) l.quit();
}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
public void onStatusChanged(String provider, int status,
Bundle extras) {
// TODO Auto-generated method stub
Log.i("LocationCollector", "Fail");
Looper l = getLooper();
if (l != null) l.quit();
}
};
// Register the listener with the Location Manager to receive
// location updates
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 1, locationListener,
Looper.myLooper());
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1000, 1,
locationListener, Looper.myLooper());
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Looper l = getLooper();
if (l != null) l.quit();
// Log.i("LocationCollector",
// "Stopping collector due to timeout");
}
}, MAX_POLLING_TIME);
Looper.loop();
t.cancel();
locationManager.removeUpdates(locationListener);
setLooper(null);
}
if (getLocationQuality(bestLocation) != LocationQuality.BAD)
sendUpdate(locationToString(bestLocation));
else Log.w("LocationCollector", "Failed to get a location");
}
private enum LocationQuality {
BAD, ACCEPTED, GOOD;
public String toString() {
if (this == GOOD) return "Good";
else if (this == ACCEPTED) return "Accepted";
else return "Bad";
}
}
private LocationQuality getLocationQuality(Location location) {
if (location == null) return LocationQuality.BAD;
if (!location.hasAccuracy()) return LocationQuality.BAD;
long currentTime = System.currentTimeMillis();
if (currentTime - location.getTime() < MAX_AGE
&& location.getAccuracy() <= GOOD_ACCURACY)
return LocationQuality.GOOD;
if (location.getAccuracy() <= ACCEPTED_ACCURACY)
return LocationQuality.ACCEPTED;
return LocationQuality.BAD;
}
private synchronized void updateBestLocation(Location location) {
bestLocation = getBestLocation(location, bestLocation);
}
// Pretty much an unmodified version of googles example
protected Location getBestLocation(Location location,
Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return location;
}
if (location == null) return currentBestLocation;
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use
// the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return location;
// If the new location is more than two minutes older, it must be
// worse
} else if (isSignificantlyOlder) {
return currentBestLocation;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and
// accuracy
if (isMoreAccurate) {
return location;
} else if (isNewer && !isLessAccurate) {
return location;
} else if (isNewer && !isSignificantlyLessAccurate
&& isFromSameProvider) {
return location;
}
return bestLocation;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
我在互联网上搜索了一个更新的(去年)答案,使用谷歌建议的最新位置提取方法(使用FusedLocationProviderClient)。最后我想到了这个:
https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates
我创建了一个新项目,并复制了其中的大部分代码。繁荣。它的工作原理。我想没有任何不赞成的台词。
而且,据我所知,模拟器似乎没有GPS定位。它确实在日志中报告了这一点:“所有位置设置都满足要求。”
最后,如果你想知道(我做了),你不需要谷歌地图api密钥从谷歌开发控制台,如果你想要的只是GPS位置。
他们的教程也很有用。但我想要一个完整的一页教程/代码示例。他们的教程堆栈,但当你是新手时,你会感到困惑,因为你不知道你需要从前面的页面中获得什么。
https://developer.android.com/training/location/index.html
最后,记住这些事情:
我不仅要修改mainActivity.Java。我还必须修改Strings.xml, androidmanifest.xml和正确的build.gradle。还有activity_Main.xml(但这部分对我来说很简单)。
我需要添加像这样的依赖:实现'com.google.android.gms:play-services-location:11.8.0',并更新我的android studio SDK的设置,以包括谷歌播放服务。(文件设置外观系统设置android SDK SDK工具检查谷歌播放服务)。
更新:android模拟器似乎得到了一个位置和位置变化事件(当我改变sim设置中的值时)。但我最好的和第一个结果是在实际设备上。所以在实际设备上进行测试可能是最简单的。
我在互联网上搜索了一个更新的(去年)答案,使用谷歌建议的最新位置提取方法(使用FusedLocationProviderClient)。最后我想到了这个:
https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates
我创建了一个新项目,并复制了其中的大部分代码。繁荣。它的工作原理。我想没有任何不赞成的台词。
而且,据我所知,模拟器似乎没有GPS定位。它确实在日志中报告了这一点:“所有位置设置都满足要求。”
最后,如果你想知道(我做了),你不需要谷歌地图api密钥从谷歌开发控制台,如果你想要的只是GPS位置。
他们的教程也很有用。但我想要一个完整的一页教程/代码示例。他们的教程堆栈,但当你是新手时,你会感到困惑,因为你不知道你需要从前面的页面中获得什么。
https://developer.android.com/training/location/index.html
最后,记住这些事情:
我不仅要修改mainActivity.Java。我还必须修改Strings.xml, androidmanifest.xml和正确的build.gradle。还有activity_Main.xml(但这部分对我来说很简单)。
我需要添加像这样的依赖:实现'com.google.android.gms:play-services-location:11.8.0',并更新我的android studio SDK的设置,以包括谷歌播放服务。(文件设置外观系统设置android SDK SDK工具检查谷歌播放服务)。
更新:android模拟器似乎得到了一个位置和位置变化事件(当我改变sim设置中的值时)。但我最好的和第一个结果是在实际设备上。所以在实际设备上进行测试可能是最简单的。
这是我的解决方案,效果相当不错:
private Location bestLocation = null;
private Looper looper;
private boolean networkEnabled = false, gpsEnabled = false;
private synchronized void setLooper(Looper looper) {
this.looper = looper;
}
private synchronized void stopLooper() {
if (looper == null) return;
looper.quit();
}
@Override
protected void runTask() {
final LocationManager locationManager = (LocationManager) service
.getSystemService(Context.LOCATION_SERVICE);
final SharedPreferences prefs = getPreferences();
final int maxPollingTime = Integer.parseInt(prefs.getString(
POLLING_KEY, "0"));
final int desiredAccuracy = Integer.parseInt(prefs.getString(
DESIRED_KEY, "0"));
final int acceptedAccuracy = Integer.parseInt(prefs.getString(
ACCEPTED_KEY, "0"));
final int maxAge = Integer.parseInt(prefs.getString(AGE_KEY, "0"));
final String whichProvider = prefs.getString(PROVIDER_KEY, "any");
final boolean canUseGps = whichProvider.equals("gps")
|| whichProvider.equals("any");
final boolean canUseNetwork = whichProvider.equals("network")
|| whichProvider.equals("any");
if (canUseNetwork)
networkEnabled = locationManager
.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (canUseGps)
gpsEnabled = locationManager
.isProviderEnabled(LocationManager.GPS_PROVIDER);
// If any provider is enabled now and we displayed a notification clear it.
if (gpsEnabled || networkEnabled) removeErrorNotification();
if (gpsEnabled)
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER));
if (networkEnabled)
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
if (desiredAccuracy == 0
|| getLocationQuality(desiredAccuracy, acceptedAccuracy,
maxAge, bestLocation) != LocationQuality.GOOD) {
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
updateBestLocation(location);
if (desiredAccuracy != 0
&& getLocationQuality(desiredAccuracy,
acceptedAccuracy, maxAge, bestLocation)
== LocationQuality.GOOD)
stopLooper();
}
public void onProviderEnabled(String provider) {
if (isSameProvider(provider,
LocationManager.NETWORK_PROVIDER))networkEnabled =true;
else if (isSameProvider(provider,
LocationManager.GPS_PROVIDER)) gpsEnabled = true;
// The user has enabled a location, remove any error
// notification
if (canUseGps && gpsEnabled || canUseNetwork
&& networkEnabled) removeErrorNotification();
}
public void onProviderDisabled(String provider) {
if (isSameProvider(provider,
LocationManager.NETWORK_PROVIDER))networkEnabled=false;
else if (isSameProvider(provider,
LocationManager.GPS_PROVIDER)) gpsEnabled = false;
if (!gpsEnabled && !networkEnabled) {
showErrorNotification();
stopLooper();
}
}
public void onStatusChanged(String provider, int status,
Bundle extras) {
Log.i(LOG_TAG, "Provider " + provider + " statusChanged");
if (isSameProvider(provider,
LocationManager.NETWORK_PROVIDER)) networkEnabled =
status == LocationProvider.AVAILABLE
|| status == LocationProvider.TEMPORARILY_UNAVAILABLE;
else if (isSameProvider(provider,
LocationManager.GPS_PROVIDER))
gpsEnabled = status == LocationProvider.AVAILABLE
|| status == LocationProvider.TEMPORARILY_UNAVAILABLE;
// None of them are available, stop listening
if (!networkEnabled && !gpsEnabled) {
showErrorNotification();
stopLooper();
}
// The user has enabled a location, remove any error
// notification
else if (canUseGps && gpsEnabled || canUseNetwork
&& networkEnabled) removeErrorNotification();
}
};
if (networkEnabled || gpsEnabled) {
Looper.prepare();
setLooper(Looper.myLooper());
// Register the listener with the Location Manager to receive
// location updates
if (canUseGps)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 1,
locationListener, Looper.myLooper());
if (canUseNetwork)
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1000, 1,
locationListener, Looper.myLooper());
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
stopLooper();
}
}, maxPollingTime * 1000);
Looper.loop();
t.cancel();
setLooper(null);
locationManager.removeUpdates(locationListener);
} else // No provider is enabled, show a notification
showErrorNotification();
}
if (getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
bestLocation) != LocationQuality.BAD) {
sendUpdate(new Event(EVENT_TYPE, locationToString(desiredAccuracy,
acceptedAccuracy, maxAge, bestLocation)));
} else Log.w(LOG_TAG, "LocationCollector failed to get a location");
}
private synchronized void showErrorNotification() {
if (notifId != 0) return;
ServiceHandler handler = service.getHandler();
NotificationInfo ni = NotificationInfo.createSingleNotification(
R.string.locationcollector_notif_ticker,
R.string.locationcollector_notif_title,
R.string.locationcollector_notif_text,
android.R.drawable.stat_notify_error);
Intent intent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
ni.pendingIntent = PendingIntent.getActivity(service, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Message msg = handler.obtainMessage(ServiceHandler.SHOW_NOTIFICATION);
msg.obj = ni;
handler.sendMessage(msg);
notifId = ni.id;
}
private void removeErrorNotification() {
if (notifId == 0) return;
ServiceHandler handler = service.getHandler();
if (handler != null) {
Message msg = handler.obtainMessage(
ServiceHandler.CLEAR_NOTIFICATION, notifId, 0);
handler.sendMessage(msg);
notifId = 0;
}
}
@Override
public void interrupt() {
stopLooper();
super.interrupt();
}
private String locationToString(int desiredAccuracy, int acceptedAccuracy,
int maxAge, Location location) {
StringBuilder sb = new StringBuilder();
sb.append(String.format(
"qual=%s time=%d prov=%s acc=%.1f lat=%f long=%f",
getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
location), location.getTime() / 1000, // Millis to
// seconds
location.getProvider(), location.getAccuracy(), location
.getLatitude(), location.getLongitude()));
if (location.hasAltitude())
sb.append(String.format(" alt=%.1f", location.getAltitude()));
if (location.hasBearing())
sb.append(String.format(" bearing=%.2f", location.getBearing()));
return sb.toString();
}
private enum LocationQuality {
BAD, ACCEPTED, GOOD;
public String toString() {
if (this == GOOD) return "Good";
else if (this == ACCEPTED) return "Accepted";
else return "Bad";
}
}
private LocationQuality getLocationQuality(int desiredAccuracy,
int acceptedAccuracy, int maxAge, Location location) {
if (location == null) return LocationQuality.BAD;
if (!location.hasAccuracy()) return LocationQuality.BAD;
long currentTime = System.currentTimeMillis();
if (currentTime - location.getTime() < maxAge * 1000
&& location.getAccuracy() <= desiredAccuracy)
return LocationQuality.GOOD;
if (acceptedAccuracy == -1
|| location.getAccuracy() <= acceptedAccuracy)
return LocationQuality.ACCEPTED;
return LocationQuality.BAD;
}
private synchronized void updateBestLocation(Location location) {
bestLocation = getBestLocation(location, bestLocation);
}
protected Location getBestLocation(Location location,
Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return location;
}
if (location == null) return currentBestLocation;
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use
// the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return location;
// If the new location is more than two minutes older, it must be
// worse
} else if (isSignificantlyOlder) {
return currentBestLocation;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and
// accuracy
if (isMoreAccurate) {
return location;
} else if (isNewer && !isLessAccurate) {
return location;
} else if (isNewer && !isSignificantlyLessAccurate
&& isFromSameProvider) {
return location;
}
return bestLocation;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) return provider2 == null;
return provider1.equals(provider2);
}