Android 中获取用户位置的好方法
问题:
尽快获取用户当前位置在阈值内,同时节省电池。
为什么问题是个问题:
首先,android 有两个提供程序;网络和 GPS。有时网络更好,有时 GPS 更好。
我所说的“更好”是指速度与准确率之比。
如果我能在不打开 GPS 的情况下几乎立即获得位置,我愿意牺牲几米的精度。
其次,如果您请求位置更改更新,并且当前位置稳定,则不会发送任何内容。
Google 在此处提供了确定“最佳”位置的示例: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
但我认为它远没有达到应有的程度。
我有点困惑为什么谷歌没有标准化的位置API,开发人员不应该关心位置来自哪里,你应该只指定你想要的,手机应该为你选择。
我需要帮助:
我需要找到一种好方法来确定“最佳”位置,也许通过一些启发式方法,或者通过一些第三方库。
这并不意味着确定最好的提供商!
我可能会使用所有提供商并选择其中最好的。
应用程序背景:
应用程序将以固定间隔(假设每 10 分钟左右)收集用户的位置并将其发送到服务器。
该应用程序应尽可能节省电池电量,并且位置精度应达到 X(50-100?)米。
目标是稍后能够在地图上绘制用户白天的路径,因此我需要足够的准确性。
其他:
您认为期望和可接受的精度的合理值是多少?
我一直使用 100m 作为可接受的值,并根据需要使用 30m,这要求太多了吗?
我希望稍后能够在地图上绘制用户的路径。
100m 是理想值,500m 是接受值更好吗?
另外,现在每次位置更新我的 GPS 开启时间最长为 60 秒,如果您在室内,精度可能为 200m,那么这个时间是否太短而无法获取位置?
这是我当前的代码,欢迎任何反馈(除了缺乏错误检查,即 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);
}
The problem:
Getting the user's current location within a threshold ASAP and at the same time conserve battery.
Why the problem is a problem:
First off, android has two providers; network and GPS. Sometimes network is better and sometimes the GPS is better.
By "better" I mean speed vs. accuracy ratio.
I'm willing to sacrifice a few meters in accuracy if I can get the location almost instant and without turning on the GPS.
Secondly, if you request updates for location changes nothing is sent if the current location is stable.
Google has an example of determining the "best" location here: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
But I think it's no where near as good as it should/could be.
I'm kind of confused why google hasn't a normalized API for location, the developer shouldn't have to care where the location is from, you should just specify what you want and the phone should choose for you.
What I need help with:
I need to find a good way to determine the "best" location, maybe though some heuristic or maybe through some 3rd party library.
This does not mean determine the best provider!
I'm probably gonna use all providers and picking the best of them.
Background of the app:
The app will collect the user's location at a fixed interval (let say every 10 minutes or so) and send it to a server.
The app should conserve as much battery as possible and the location should have X (50-100?) meters accuracy.
The goal is to later be able to plot the user's path during the day on a map so I need sufficient accuracy for that.
Misc:
What do you think are reasonable values on desired and accepted accuracies?
I've been using 100m as accepted and 30m as desired, is this to much to ask?
I'd like to be able to plot the user's path on a map later.
Is 100m for desired and 500m for accepted better?
Also, right now I have the GPS on for a maximum of 60 seconds per location update, is this too short to get a location if you're indoors with an accuracy of maybe 200m?
This is my current code, any feedback is appreciated (apart from the lack of error checking which is 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);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
看起来我们正在编写相同的应用程序;-)
这是我当前的实现。我的 GPS 上传器应用程序仍处于 Beta 测试阶段,因此可能有许多可能的改进。但到目前为止似乎效果很好。
编辑:这是向位置提供商请求定期更新的部分:
要考虑的事项:
不要过于频繁地请求 GPS 更新,它会耗尽电池电量。我目前
我的应用程序默认使用 30 分钟。
添加“到最后已知位置的最小距离”检查。没有这个,你的积分
当 GPS 不可用并且正在对位置进行三角测量时,将“跳跃”
来自手机信号塔。或者您可以检查新位置是否超出精度范围
距离最后一个已知位置的值。
Looks like we're coding the same application ;-)
Here is my current implementation. I'm still in the beta testing phase of my GPS uploader app, so there might be many possible improvements. but it seems to work pretty well so far.
Edit: here is the part that requests the periodic updates from the location providers:
Things to consider:
do not request GPS updates too often, it drains battery power. I currently
use 30 min as default for my application.
add a 'minimum distance to last known location' check. without this, your points
will "jump around" when GPS is not available and the location is being triangulated
from the cell towers. or you can check if the new location is outside of the accuracy
value from the last known location.
要为您的应用选择正确的位置提供程序,您可以使用 Criteria 对象:
阅读 requestLocationUpdates 了解有关如何考虑参数的更多详细信息:
更多想法
Criteria.ACCURACY_HIGH
标准应该给出低于 100m 的误差,这不如 GPS,但符合您的需求。To select the right location provider for your app, you can use Criteria objects:
Read the documentation for requestLocationUpdates for more details on how the arguments are taken into account:
More thoughts
Criteria.ACCURACY_HIGH
criterion should give you errors below 100m, which is not as good as GPS can be, but matches your needs.回答前两点:
如果启用 GPS 并且周围没有厚墙,GPS 将始终为您提供更精确的位置 em>.
如果位置没有改变,那么您可以调用 getLastKnownLocation (字符串)并立即检索位置。
使用替代方法:
您可以尝试获取正在使用的小区 ID 或所有相邻小区,
然后您可以通过多个开放数据库(例如,http://www.location-api.com/ 或 http: //opencellid.org/ )
策略是在读取时读取塔 ID 列表的位置。然后,在下一个查询(在您的应用程序中 10 分钟)中,再次阅读它们。如果至少有一些塔是相同的,那么使用 getLastKnownLocation(String) 是安全的。如果不是,则等待
onLocationChanged()
。这避免了对位置的第三方数据库的需要。您还可以尝试这种方法。Answering the first two points:
GPS will always give you a more precise location, if it is enabled and if there are no thick walls around.
If location did not change, then you can call getLastKnownLocation(String) and retrieve the location immediately.
Using an alternative approach:
You can try getting the cell id in use or all the neighboring cells
You can refer then to cell location through several open databases (e.g., http://www.location-api.com/ or http://opencellid.org/ )
The strategy would be to read the list of tower IDs when reading the location. Then, in next query (10 minutes in your app), read them again. If at least some towers are the same, then it's safe to use
getLastKnownLocation(String)
. If they're not, then wait foronLocationChanged()
. This avoids the need of a third party database for the location. You can also try this approach.这是我的解决方案,效果相当好:
This is my solution which works fairly well:
位置精度主要取决于所使用的位置提供商:
如果您追求的是准确性,那么 GPS 是您唯一的选择。
我在此处阅读了一篇关于它的内容非常丰富的文章。
至于 GPS 超时 - 60 秒应该足够了,但在大多数情况下甚至太多了。我认为 30 秒就可以了,有时甚至不到 5 秒...
如果您只需要一个位置,我建议在您的
onLocationChanged
方法中,一旦收到更新,您将取消注册听者并避免不必要地使用 GPS。Location accuracy depends mostly on the location provider used:
If it's accuracy you are looking for, then GPS is your only option.
I've read a very informative article about it here.
As for the GPS timeout - 60 seconds should be sufficient, and in most cases even too much. I think 30 seconds is OK and sometimes even less than 5 sec...
if you only need a single location, I'd suggest that in your
onLocationChanged
method, once you receive an update you'll unregister the listener and avoid unnecessary usage of the GPS.目前我正在使用它,因为这对于获取位置和计算我的应用程序的距离来说是值得信赖的……我正在将它用于我的出租车应用程序。
使用谷歌开发人员开发的融合 API,融合 GPS 传感器、磁力计、加速计,还使用 Wifi 或蜂窝位置来计算或估计位置。它还能够准确地更新建筑物内部的位置。
欲了解详细信息,请访问链接
https://developers.google.com/android/reference /com/google/android/gms/location/FusedLocationProviderApi
Currently i am using since this is trustable for getting location and calculating distance for my application...... i am using this for my taxi application.
use the fusion API that google developer have developed with fusion of GPS Sensor,Magnetometer,Accelerometer also using Wifi or cell location to calculate or estimate the location. It is also able to give location updates also inside the building accurately.
for detail get to link
https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi
我使用谷歌建议的最新位置拉取方法(使用 FusedLocationProviderClient)在互联网上搜索更新的(去年)答案。我终于找到了这个:
https://github.com/googlesamples/ android-play-location/tree/master/LocationUpdates
我创建了一个新项目并复制了大部分代码。繁荣。有用。我认为没有任何弃用的行。
另外,据我所知,模拟器似乎无法获取 GPS 位置。它确实在日志中报告了这一点:“所有位置设置都得到满足。”
最后,如果您想知道(我想知道),如果您想要的只是 GPS 位置,则不需要来自 google 开发者控制台的 google 地图 api 密钥。
他们的教程也很有用。但我想要一个完整的一页教程/代码示例,等等。他们的教程很丰富,但当您刚接触这个时会感到困惑,因为您不知道需要从前面的页面中获得哪些部分。
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 的设置以包含 google play 服务。 (文件设置外观系统设置android SDK SDK工具检查google play服务)。
更新:Android模拟器似乎确实获得了位置和位置更改事件(当我更改了SIM卡设置中的值时)。但我最好的也是第一个结果是在实际设备上获得的。因此,在实际设备上进行测试可能是最简单的。
I scoured the internet for an updated (past year) answer using the latest location pulling methods suggested by google (to use FusedLocationProviderClient). I finally landed on this:
https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates
I created a new project and copied in most of this code. Boom. It works. And I think without any deprecated lines.
Also, the simulator doesn't seem to get a GPS location, that I know of. It did get as far as reporting this in the log: "All location settings are satisfied."
And finally, in case you wanted to know (I did), you DO NOT need a google maps api key from the google developer console, if all you want is the GPS location.
Also useful is their tutorial. But I wanted a full one page tutorial/code example, and that. Their tutorial stacks but is confusing when you're new to this because you don't know what pieces you need from earlier pages.
https://developer.android.com/training/location/index.html
And finally, remember things like this:
I not only had to modify the mainActivity.Java. I also had to modify Strings.xml, androidmanifest.xml, AND the correct build.gradle. And also your activity_Main.xml (but that part was easy for me).
I needed to add dependencies like this one: implementation 'com.google.android.gms:play-services-location:11.8.0', and update the settings of my android studio SDK to include google play services. (file settings appearance system settings android SDK SDK Tools check google play services).
update: the android simulator did seem to get a location and location change events (when I changed the value in the settings of the sim). But my best and first results were on an actual device. So it's probably easiest to test on actual devices.
最近重构获取了代码的位置,学习了一些好的思路,终于实现了一个比较完善的库和Demo。
@Gryphius 的答案很好
完整实现:
https:// github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java
1.谢谢@Gryphius解决方案思路,我也分享完整的代码。
2.每次请求完成定位时,最好去掉更新,否则手机状态栏会一直显示定位图标
Recently refactored to obtain the location of the code, learn some good ideas, and finally achieved a relatively perfect library and Demo.
@Gryphius's answer is good
Complete implementation:
https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java
1.Thanks @Gryphius solution ideas, I also share the complete code.
2.Each request to complete the location, it is best to removeUpdates, otherwise the phone status bar will always display the positioning icon
根据我的经验,我发现最好使用 GPS 修复,除非它不可用。我对其他定位提供商了解不多,但我知道对于 GPS,有一些技巧可以用来提供一些贫民窟的精确测量。海拔高度通常是一个标志,因此您可以检查是否有荒谬的值。 Android 位置修复有精确度测量。此外,如果您可以看到使用的卫星数量,这也可以表明精度。
更好地了解准确性的一个有趣方法是非常快速地请求一组修复,例如约 1/秒持续 10 秒,然后休眠一两分钟。我参加过的一次演讲让我相信某些 Android 设备无论如何都会这样做。然后,您将清除异常值(我听说过这里提到的卡尔曼滤波器)并使用某种居中策略来获得单一修复。
显然,您达到的深度取决于您的要求有多难。如果您对获得最佳位置有特别严格的要求,我想您会发现GPS和网络位置就像苹果和橘子一样。此外,不同设备的 GPS 也可能存在很大差异。
In my experience, I've found it best to go with the GPS fix unless it's not available. I don't know much about other location providers, but I know that for GPS there are a few tricks that can be used to give a bit of a ghetto precision measure. The altitude is often a sign, so you could check for ridiculous values. There is the accuracy measure on Android location fixes. Also if you can see the number of satellites used, this can also indicate the precision.
An interesting way of getting a better idea of the accuracy could be to ask for a set of fixes very rapidly, like ~1/sec for 10 seconds and then sleep for a minute or two. One talk I've been to has led to believe that some android devices will do this anyway. You would then weed out the outliers (I've heard Kalman filter mentioned here) and use some kind of centering strategy to get a single fix.
Obviously the depth you get to here depends on how hard your requirements are. If you have particularly strict requirement to get THE BEST location possible, I think you'll find that GPS and network location are as similar as apples and oranges. Also GPS can be wildly different from device to device.
Skyhook (http://www.skyhookwireless.com/) 有一个位置提供程序,比 Google 提供的标准位置提供程序快得多。这可能就是您正在寻找的。我与他们没有隶属关系。
Skyhook (http://www.skyhookwireless.com/) has a location provider that is much faster than the standard one Google provides. It might be what you're looking for. I'm not affiliated with them.