Background
I am writing an Android app whose main function is tracking the user's location and making an alert when the user gets near some point. Therefore I need to update the user's location at regular intervals, and these intervals should get smaller as the user comes closer to the target. So when the user is within, say, 1 km of the target, I want the location to be updated every 20 seconds and so on, until the user arrives.
Problem
When I test it (provider = LocationManager.NETWORK_PROVIDER), a call to requestLocationUpdates(provider, minTime, minDistance, locationListener) with any minTime < 45000 has the same effect as minTime = 45000, i.e. I get updates with an interval of exactly 45 seconds.
I know the minimum time parameter is only a "hint", but it is not taken as a hint by my app. I get updates with the interval specified until that interval passes below 45 seconds. It seems as though a minimum time of 45 seconds between location updates is hardcoded into Android, but that would be kind of odd. Plus I have never heard of this problem before, and I have not been able to find it addressed here on Stackoverflow.
Because I am not able to get frequent updates, my workaround (for now) is to manually call requestLocationUpdates whenever a new location is needed, and then just use the first available location. To do this at small intervals I use handler.postDelayed(myRunnable, updateInterval) to delay the calls, and myRunnable then takes care of calling requestLocationUpdates. However, this method only works about 50 (apparently random) percent of the time.
Does anybody know of the problem, and is there a way to fix it? Or is my only option to set minTime = 0 and just hope for the best?
Source code
Here is the source code for myRunnable, whose run() method I manually call regularly with handler.postDelayed(myRunnable, updateInterval):
public class MyRunnable implements Runnable {
private LocationManager manager;
private LocationListener listener;
#Override
public void run() {
// This is called everytime a new update is requested
// so that only one request is running at a time.
removeUpdates();
manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
listener = new LocationListener() {
#Override
public void onLocationChanged(Location loc) {
location = loc;
latitude = loc.getLatitude();
longitude = loc.getLongitude();
accuracy = Math.round(loc.getAccuracy());
handler.sendMessage(Message.obtain(handler, KEY_MESSAGE_LOCATION_CHANGED));
checkForArrival();
}
// Other overrides are empty.
};
if(!arrived)
manager.requestLocationUpdates(provider, updateInterval, 0, listener);
}
/**
* Removes location updates from the LocationListener.
*/
public void removeUpdates() {
if(!(manager == null || listener == null))
manager.removeUpdates(listener);
}
// Another method for "cleaning up" when the user has arrived.
}
And here is my handler:
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch(msg.what) {
case KEY_MESSAGE_LOCATION_CHANGED:
if(myRunnable != null) {
myRunnable.removeUpdates();
handler.postDelayed(myRunnable, updateInterval);
}
break;
}
}
};
Additional info
The whole location updating thing runs in a service.
I have read the doc several times, Google'd the problem, and tried various other workarounds. Nothing quite does it.
I have logged the damn out of this thing, and the only exciting thing to see is a big fat "ignore" to my frequent location requests. All the right methods are called.
Any help will be very much appreciated!
You are completely right, the minimum time 45 seconds is harcoded in Android.
This seems to be a NetworkLocationProvider class source code, when it was still in Android core:
http://www.netmite.com/android/mydroid/frameworks/base/location/java/com/android/internal/location/NetworkLocationProvider.java
Look at the variable:
private static final long MIN_TIME_BETWEEN_WIFI_REPORTS = 45 * 1000; // 45 seconds
And the method:
#Override
public void setMinTime(long minTime) {
if (minTime < MIN_TIME_BETWEEN_WIFI_REPORTS) {
mWifiScanFrequency = MIN_TIME_BETWEEN_WIFI_REPORTS;
} else {
mWifiScanFrequency = minTime;
}
super.setMinTime(minTime);
}
Now NetworkLocationProvider is out of the Android core, you can find it in NetworkLocation.apk in /system/app
You can find an explanation of why is out of the core here:
https://groups.google.com/forum/?fromgroups=#!topic/android-platform/10Yr0r2myGA
But 45 seconds min time seems to still be there.
Look at this NetworkProvider decompilation:
http://android.fjfalcon.com/xt720/miui-trans/apk-decompiled/NetworkLocation/smali/com/google/android/location/NetworkLocationProvider.smali
.line 149
const-wide/32 v4, 0xafc8
iput-wide v4, p0, Lcom/google/android/location/NetworkLocationProvider;->mWifiScanFrequency:J
As you might guess if you convert 0xafc8 to decimal you get 45000 milliseconds
I haven't found an explanation of why 45 seconds. I suppose there will be reasons like avoiding service overloading or other uses they don't want.
In fact, there is a 100 request courtesy limit to Geolocation API:
https://developers.google.com/maps/documentation/business/geolocation/#usage_limits
But they don't seem to respect this rule in Google Maps app. If you open it and you only active network location you can notice that yout location is updated much more frequently than 45 seconds.
I noticed this line suspiciously frequent (33 times a second) in logcat when Google Maps is open:
02-20 17:12:08.204: V/LocationManagerService(1733): getAllProviders
I guess Google Maps is also calling removeUpdates() and requestLocationUpdates() again to obtain a new position.
So I think there is no fix and this is the best you can do if you want to get network locations over one in 45 seconds.
You can set the minTime to any value. However, you will only get an update once a new location is available. The network only updates every 45 sec or so on every phone I own. This seems to be a limitation of the Network Provider. If you want more frequent updates use the GPS provider. Depending on the GPS hardware you should get a maximum update rate around 4Hz.
I was having a similar issue. I put a call to locationManager.requestSingleUpdate() at the end of onLocationChanged() and it forced back to back updates. You could set a delay command then execute requestSingleUpdate, making sure to register the containing locationListener.
I was trying to create a GPS clock but the updates were inconsistent updating anywhere from 1-5 seconds or so. but it might work for another application.
Related
I have developed an android application which took the current location using GPS every 10 seconds, and send it to the server using socket. To achieve this I used postDelayed method to keep getting current location and send it to server.
myRunnable = new Runnable() {
#Override
public void run() {
Location mCurrentLocation =getCurrentLocation();
if(mCurrentLocation != null)
sendCurrentLocationToServer(mCurrentLocation);
Handler.postDelayed(this, 10000);
}};
public Location getCurrentLocation(){
Location currentLocation = myLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
return currentLocation;
}
but this code makes the device to overheat, and consumes the battery quickly, is there another way to achieve the same result and reduce the overheat?
Thanks
First off, don't use getLastKnownLocation. Not only will it normally return null, looping calling it is an EXTREMELY inefficient way of doing things. Instead, requestLocationUpdates, and it will call you when it has a new location.
Second, don't send the location to the server every 10 seconds. Sending it that frequently causes your phone to keep the radio (either wifi or cellular) on constantly, which is causing your heat issue. Sending data generates heat. Reduce it to once every minute or so, and send up heading and speed data with the location. If you need a more exact location the server can use speed and heading to programmatically calculate a probable location- if needed. Most likely you don't even really need that.
is LocationListener.onLocationChanged method is better than any other way of getting Location.
if so is there a better way to use it, so as to get better battery performance.
I wouldn't know of an alternative to LocationListener.onLocationChanged , maybe you can yourself shed some light on this ?
As for the battery issue, you can call requestLocationUpdates() just less often to avoid battery draining.
This call for this contains a variable minDistance (in milliseconds), you should use this one. Please note that observing minDistance was not mandatory until android 4.3 .
For devices with versions prior to 4.3 , you need to
1) stop calling updates in onLocationChanged
#Override
public void onLocationChanged(Location location) {
mLocationManager.removeUpdates(mLocationListener);
}
2) override the method run to introduce a new interval.
#Override
public void run() {
// getting location...
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
// ...once per hour
mHandler.postDelayed(onRequestLocation, DateUtils.HOUR_IN_MILLIS);
}
For further reference , see the docu for requestLocationUpdates()
I have a timer that runs every second. Every second I get the GPS location and do other stuffs.
I am wondering which way is better:
1- Request a single location update and then get the last known location
private void timeout(){
String data[] =new String[DATA_LENGTH];
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null);
Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
.
.
.
}
2- Start Location listener and then just get the last known location whenever my timer expire
OnCreate(){
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mlocListener);
}
private void timeout(){
String data[] =new String[DATA_LENGTH];
Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
.
.
.
}
Thank you
PS: Note that battery is not a concern to me as per the requirement of the product
requestSingleUpdate is meant to be single, if you need to query the GPS frequently you should definitely go with option 2.
Keep a global Location object in memory, use it in you other stuff and update it whenever your listener gets an update from the LocationManager.
You can listen for changes via requestLocationUpdates - the code below is a quick-n-dirty example (untested). Remember, you have to have location services turned on to use this.
LocationListener locGPSListener= new LocationListener() {...}
LocationListener locNetworkListener= new LocationListener() {...}
mgr = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// listens using GPS for location
mgr .requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locGPSListener);
// uses towers for location
mgr .requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locNetworkListener);
...
What approach is better, depends on
Androids GPS behaviour and
your Application.
ad 1. if explicitly getting a location delivers a more recent fix, than this is an advantage, because:
ad 2. if your application don't want the android filtering behaviour, and you can filter it yourself better, then this would be better for your app.
Example: (is for ios, but may apply here too:) if I drive with my car to a traffic signal, and do a harsh breaking, then ios still shows 5 km/h speed, although I am standing still. This I call unwanted filtering.
This has all nothing to do with battery: if you get the location via message or if you query it is the same from battery point of view. It smore a software design issue: (events vs. polling)
A difference would only be if GPS is disabled, but disabling GPS makes only sense if it can be disabled for long time.
I've been using the fused location provider since its release and I am pretty happy with it (way better than the old system). But I ran into a peculiar problem when using geofencing in combination with LocationClient.lastKnownLocation(). The setup is as follows:
I drop several geofences around some home location (with increasing ranges). When I get the intent that a fence is crossed I retrieve the last known location from LocationClient and work with it. Apart from than I also registered for regular location updates with update mode PRIORITY_BALANCED_POWER_ACCURACY.
Most of the times this works perfectly fine, but sometimes this happens:
Time 000 s - (Lat,Lon,Accuracy) = (48.127316,11.5855167,683.0)
Time 120 s - (Lat,Lon,Accuracy) = (48.1260497,11.5731745,31.823)
Time 300 s - (Lat,Lon,Accuracy) = (48.1217455,11.5641666,143.81)
Time 420 s - (Lat,Lon,Accuracy) = (48.1189942,11.559061,36.0)
Time 600s - (Lat,Lon,Accuracy) = (48.127316,11.5855167,683.0)
Notice that all these locations are retrieved by getLastKnownLocation(). What seems fishy here is that the first and the last location are identical (even in the other attributes), to be more specific:
* intent at time 0: *
component: ComponentInfo{package.Class}
key [location]: Location[mProvider=fused,mTime=1373524391934,mLatitude=48.127316,mLongitude=11.5855167,mHasAltitude=false,mAltitude=0.0,mHasSpeed=false,mSpeed=0.0,mHasBearing=false,mBearing=0.0,mHasAccuracy=true,mAccuracy=683.0,mExtras=Bundle[mParcelledData.dataSize=352]]
* intent at time 600: *
component: ComponentInfo{package.Class}
key [location]: Location[mProvider=fused,mTime=1373524994871,mLatitude=48.127316,mLongitude=11.5855167,mHasAltitude=false,mAltitude=0.0,mHasSpeed=false,mSpeed=0.0,mHasBearing=false,mBearing=0.0,mHasAccuracy=true,mAccuracy=683.0,mExtras=Bundle[mParcelledData.dataSize=352]]
* note the ~600 s difference in the timestamp *
I do not understand how this can happen, as there have been locations in between that were both more recent and more accurate. Also the new timestamp on an old location makes me curious... apparently similar things happened when using the old API, but this new location provider is just called fused, so I can not distinguish GPS from WPS from sensors... If it is the cell tower switching problem (outlined in the linked question concerning the old API) then why would the phone connect to a "far away" tower if it has seen closer towers?
Why is this happening?
The first and last points were gotten using cell triangulation. The error/accuracy is typical of cell-based location, and it looks like the Google power saving logic decided that switching to cell would be OK, even as you say its recent history included points much closer.
Aw, SHUCKS! I got this too today... And I moved to the new Google Play Services location precisely to AVOID this... And I was so thrilled up until just now when I got it too. You may or may not know that the old one had these kind of problems, and it was a pain.
There are lots of threads regarding this, including one of my own :(
Why is locationmanager returning old location fixes with new gettime-timestamp?
I guess the only thing to do is avoid using cached location...
Instead of polling, one can work around one or more sources of inaccuracy using this subscription mechanism.
LocationListener locListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
if (location == null)
return;
// process these:
// location.getLatitude();
// location.getLongitude();
// location.getAccuracy();
...
}
...
}
((LocationManager) getSystemService(Context.LOCATION_SERVICE)
.requestLocationUpdates(LocationManager.GPS_PROVIDER,
minTimeMilliSec,
minDistanceMeters,
locListener));
I try to get the correct speed in updates for the function onLocationChanged, this is my class:
public class LocationService extends Service implements LocationListener {
Putting the minTime on 6000 does not help, it wil keep updating constantly, what am i doing wrong?
public void requestLocationUpdates (String provider, long minTime, float minDistance, LocationListener listener, Looper looper) {
Greetings
The minTime is just a hint for the LocationProvider, and it doesn't mean that your location listener will be called once every 6 seconds. You will receive more location updates, and its up to your code to pick the most accurate one.
Monitor the GPS icon on your phone. A call to requestLocationUpdates will trigger the GPS to pinpoint your location, and it will send one or more location updates to the locationlistener if it's able to get a fix. (At this point, your GPS icon should be animated as it searches for a location).
During that time, your locationlistener may receive several location updates. Your code can go and pick the most accurate location, and process only that one.
After the GPS has sent the location update(s) to your listener, there should be a period of inactivity. (your GPS icon should disappear for a couple of seconds). This period of inactivity should correspond with your minTime. The status of the GPS will also change, as it will be put into TEMPORARILY_UNAVAILABLE.
After that, the same process is repeated. (The GPS becomes AVAILABLE, and you'll again receive one or more location updates).
Also take into account, if the GPS is unable to get a location fix, the GPS icon will remain active for more then 6 seconds, but you won't be receiving location updates.
You can also monitor the status of your GPS provider through your listener, via the following method :
public void onStatusChanged(String provider, int status, Bundle extras) {}
The status is one of the following constants defined on android.location.LocationProvider
public static final int OUT_OF_SERVICE = 0;
public static final int TEMPORARILY_UNAVAILABLE = 1;
public static final int AVAILABLE = 2;
Have a look at Understanding the LocationListener in Android for an example on the minTime behavior, and a scenario (including some logging) to help you understand what's going on.
Keep in mind that tweaking the minTime and minDistance parameters on the LocationManager, and acting upon GPS status updates will allow you to fine-tune your user location development.
6000 in milliseconds equals 6 seconds, and it may seems like continiously updating.
From Android dev guide "minTime under 60000ms are not recommended"
Maybe it is worth to increase it to 60000ms