I am using the beacon library to scan for beacons with a foreground service and a persistent notification. I have tested this on both Android 9.0 and 7.0, and the app works as expected, and sends the beacons scanned to a server every 30 seconds. Now, I am trying to add location scanning to the app, so that it retrieves location updates every 30 seconds. I am using the Google Play API, and set up a location request with an interval of 30 secs. Then, I created a FusedLocationProvider client in my application class, so I gave it my app's (not activity's) context. Then, I gave my request and the following callback to the client:
locationCallback = new LocationCallback()
{
#Override
public void onLocationResult(LocationResult locationResult)
{
if ( locationResult != null )
{
Log.d(TAG, "location acquired: " + locationResult.getLastLocation());
beaconContainer.setLocation(locationResult.getLastLocation());
}
}
};
The beaconContainer object holds a list of beacons and the latest location (and a timestap of when the latest location was acquired using LocalTime.now()), and sends these to the server every 30 seconds. At first, the app seems to work and the location timestamp is within 30 seconds of when the request to the server was sent. However, after some time has passed (and the screen has been off for some time), it seems that the onLocationResult method in the callback is not being called and the location is not being updated. For example, the server request was made at 12:34 but the location was updated at 10:21. Note that the beacon scanning is still being correctly performed as expected.
I was wondering if this is because the phone that I tested this on was stationary, or if it is because I did not use a service for location updating. To me, it seems to be the former because my app has a foreground service (ble scanner) and a persistent notification, so according to the docs, it is in the foreground and it should not be subject to background limitations. If it is the latter, how can I fuse the beacon library's foreground service with my location scanning so that they both run as expected.
Thanks.
EDIT:
Here is a screenshot of battery historian, showing how BLE is regularly and consistently being used while GPS is used for intermittent periods.
The documentation for FusedLocationProviderClient indicates that on Android 8+, if the app is not in the foreground, you will only get updates a "few times each hour". See here. This is likely because the implementation inside Google Play Services uses the JobScheduler on Android 8+ to get around background service limits, and jobs are limited to running every 15 minutes +/- 5 minutes in the background. Since Google Play Services APIs are closed source and proprietary, it is difficult to say more about its internal implementation, but it is unlikely that it takes into account that your app has a foreground service. (The Android Beacon Library, by contrast, is explicitly designed to behave differently when configured with a Foreground Service so you get more frequent updates.)
It's unclear how the FusedLocationProviderClient works differently in the background on Android 7. It may not work differently at all, and may follow the pattern described above in the background simply if your app targets SDK 26 or higher. You'd have to test to make sure -- effectively reverse engineering Google Play Services. Even if you do figure it out, the behavior might change in the next Google Play Services version, and you'll never know about it unless you reverse-engineer it again. This is the peril of using closed-source SDKs.
An alternative would be to instead use the open-source location APIs provided by Android.
LocationManager locationManager = (LocationManager)
this.getSystemService(Context.LOCATION_SERVICE);
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 400l, (float) 1000.0, this); //You can also use LocationManager.GPS_PROVIDER and LocationManager.PASSIVE_PROVIDER
}
catch (SecurityException e) {
Log.d(TAG, "Can't get location -- permission denied");
}
Of course, you'll want to adjust the accuracy and update interval to suit your needs and conserve battery. And you will certainly find dropouts in callbacks when your phone enters Doze mode. But you should be able to use the above in your Application class as you describe in your question without the annoyingly opaque behaviors added by Google Play Services.
Related
What is the best way to keep user's location in sync with server,even after the app is terminated?
here is what I found this far
using a foreground service. I already tested that but it's battery consuming, and sometimes it's killed when you lock the phone or turn off the screen.
using work manager. I also tested it, it sends the location every 15 mins,but so unreliable, and it stopped sending after a while.
Geofencing. I read that Geofencing api gets location updates more often, every couple of minutes or so, but I haven't tested it.
LocationListener. There is a method called onLocationChanged, but I don't know how would it work in background.
is there something like a broadcast receiver that will be fired only when location changed by a margin? I think this would be an optimized solution.
I am building an app that uses a feature similar to facebook nearby friends (which is stopped working long time ago) or something like tinder to display nearby people.
Currently I need this for android, but I will need it for IOS as well, I am developing a flutter app.
I already used a similar method in my app which is working:
I used foreground service to record the location, the battery consumptions are related to the frequency of location request not really the foreground service itself. indeed using LocationListener is a good option
then I am checking the foreground service regularly using workmanager and restart it when needed.
best
As discussed in the comments above, I don't really think Android allows for you to get location updates in the background even when the application is not active.
I recommend you instead try to query for location as soon as the app starts, and quickly follow up with whatever actions you need to make with it. As an option, you can also keep track of location history by caching their location as they interact with the app, in case you want to keep the database as fresh as possible.
Here's some pseudo code as a simple example:
class MainActivity : AppCompatActivity() {
override fun onCreate(..) {
queryForLocation(..)?.let {
doSomething(it)
cacheLocation(it)
} ?: Log.d(TAG, "Location = null")
}
override fun onDestroy() {
queryForLocation(..)?.let {
cacheLocation(it)
} ?: Log.d(TAG, "Location = null")
}
In the docs for FusedLocationProviderClient.getCurrentLocation() it says as follows:
This method may return locations that are a few seconds old, but never returns much older locations. This is suitable for foreground applications that need a single fresh current location.
Background apps calling this method will be throttled under background location limits, so background apps may find the method returns null locations more often.
What are the implications of "will be throttled"?
In my situation, I have a widget which updates at most every 30 mins. With appropriate location permissions granted (foreground and background), I can already access the last known location using FusedLocationProviderClient.getLastLocation() and this has been working fine.
But I'd like to explore a more active approach, using getCurrentLocation() instead of getLastLocation(). For a widget, with no app in the foreground, even passing LocationRequest.PRIORITY_HIGH_ACCURACY to getCurrentLocation(), I never see the little "location" icon (map pin icon) appearing in the status bar, like I'd expect to if the device were actively requesting a new location fix. Yes, ACCESS_FINE_LOCATION permission is requested and granted. Using this same method call in the app (in the foreground) and I do see the "location" icon.
So, for a widget is there any real benefit in using getCurrentLocation() instead of getLastLocation() in the background if it is going to be hampered significantly in some way? Sometimes it seems to take around 30 seconds to complete (with no visible sign in the status bar of any location access) and then return with a null location anyway, so what is it actually doing?
I could start a foreground service (with notification) but it seems overkill if the user is anyway going to have visibility (via the "location" icon in the status bar) that location is being actively accessed.
So what exactly is "throttling"? Just putting a limit on frequency (widget updates at most every 30 mins so it's hardly "frequent")? Or putting a limit on accuracy (e.g. no GPS)?
I think that it probably relates to these background location limits:
In an effort to reduce power consumption, Android 8.0 (API level 26) limits how frequently an app can retrieve the user's current location while the app is running in the background. Under these conditions, apps can receive location updates only a few times each hour.
I received an performance report from Android Vital on Google Play Console about Excessive Alarm Manager wakeups:
https://developer.android.com/topic/performance/vitals/wakeup.html
I use Location API from Google Play Services to request for location updates in the background. On the report it show that the excessive wake up wakeups was caused by com.google.android.location.ALARM_WAKEUP_LOCATOR from LocationListener.
Below the code snippet which causes the alarms:
private synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
/**
* Runs when a GoogleApiClient object successfully connects.
*/
#Override
public void onConnected(Bundle connectionHint) {
try {
// Set location request
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(5 * 60000);
mLocationRequest.setFastestInterval(60000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
mLocationRequest.setMaxWaitTime(30 * 60000);
// Create a pending intent to listening on location service when the update become available
Intent mIntent = new Intent(context, LocationReceiver.class);
mIntent.setAction(LocationReceiver.LOCATION_EXTRA);
mPendingIntent = PendingIntent.getBroadcast(context, 42, mIntent, PendingIntent.FLAG_CANCEL_CURRENT);
// Permission check before launching location services
if (ContextCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, mPendingIntent);
} catch (Exception e) {
e.printStackTrace();
}
}
Is this alarm wakeup linked to the Location API from google play services?
Is anyone know how to fix this issue?
The Google Location api
This is an issue with the com.google.android.location.ALARM_WAKEUP_LOCATOR waking up devices every 60 seconds and keeping them awake for up to 15 seconds, causing major battery drainage issues.
There's been fixes for phone uses via external apps and teaching users how to actually revoke permission for an app.
The Solution
The following provides the tools to reduce the battery usage of the app.
It's impossible to provide a customised solution for the app, as the solution depends upon the use case and business logic of the app and a cost benefit analysis of what you wish to achieve with the location updates.
Time intervals
It's no surprise there's battery performance issues. From the following settings:
mLocationRequest.setInterval(5 * 60000);
mLocationRequest.setFastestInterval(60000);
mLocationRequest.setMaxWaitTime(30 * 60000);
Your interval is set to update every 5 minutes (5 * 60000ms). That's 24 hours a day. If this updates successfully every 5 minutes: 12 times/hour == 288 times per day.
The fastest interval being set at 1 minute (60000). Although that is available because location is accessed elsewhere on the device, it will still use power in your app).
The maxwaittime is only 30 minutes. Which means at best the device will be woken up and polled a minimum of 48 times per day.
Increase these times.
setMaxWaitTime ... This can consume less battery and give more accurate locations,
depending on the device's hardware capabilities. You should set this
value to be as large as possible for your needs if you don't need
immediate location delivery. ...
In Android 8 Google has limited the number of requests to only a few per hour. Consider using this as a guideline in setting intervals for requests.
Limiting number of updates
The number of updates within a certain time frame can be limited. By either actively cancelling the location request once the number of updates have been used or setting an expiration on the request. This way the app can be managing by creating a request on some trigger within the app and carefully managed so that it doesn't continue endlessly. It's difficult to create a flow, as I don't know the use case for the app.
setNumUpdates (int numUpdates)
By default locations are continuously updated until the request is
explicitly removed, however you can optionally request a set number of
updates. For example, if your application only needs a single fresh
location, then call this method with a value of 1 before passing the
request to the location client.
Stop location updates
Another option is to manage stop locations updates when they are not required. The links gives examples of calling this when an activity loses focus, however it could be implemented within the app, when certain requirements are met (that's within your business logic) or even by giving the user to turn it on and off from within the app itself.
Battery optimisation
Ensure your app is not ignoring battery optimizations.
Displacement
Also managing setSmallestDisplacement (float smallestDisplacementMeters) will help to fine tune the app, depending on the business logic.
the following was written before the updated question.
How often the app updates can be set by both the developer and the user. The intervals and priorities.
For the developer.
You can set these when making a location request for example:
protected void createLocationRequest() {
LocationRequest mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(10000);
mLocationRequest.setFastestInterval(5000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
}
There is also a setting for PRIORITY_NO_POWER, which means the app will only get updates when the user asks for them.
For the user.
You will need to Prompt the User to Change Location Settings
task.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
#Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
// All location settings are satisfied. The client can initialize
// location requests here.
// ...
}
});
task.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case CommonStatusCodes.RESOLUTION_REQUIRED:
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
ResolvableApiException resolvable = (ResolvableApiException) e;
resolvable.startResolutionForResult(MainActivity.this,
REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException sendEx) {
// Ignore the error.
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
// Location settings are not satisfied. However, we have no way
// to fix the settings so we won't show the dialog.
break;
}
}
});
To manage changes in user setting use a location call back.
... you can use the new LocationCallback class in place of your existing
LocationListener to receive LocationAvailability updates in addition
to location updates, giving you a simple callback whenever settings
might have changed which will affect the current set of
LocationRequests.
Google has addressed this issue in Android 8.
In an effort to reduce power consumption, Android 8.0 (API level 26)
limits how frequently background apps can retrieve the user's current
location. Apps can receive location updates only a few times each
hour.
Note: These limitations apply to all apps used on devices running
Android 8.0 (API level 26) or higher, regardless of an app's target
SDK version.
If using the Alarm Manager.
This can be related to the Alarm Manager.
It's not a simple fix, ultimately you'll need to rewrite how you are scheduling your updates.
To at least slow down the problem you'll need to debug your code and find any instances where the Alarm Manager calls or creates schedules and reduce the intervals.
After that you will need to rewrite the entire location update scheduling part of the app.
Fix the Problem
Identify the places in your app where you schedule wakeup alarms and
reduce the frequency that those alarms are triggered. Here are some
tips:
Look for calls to the various set() methods in AlarmManager that include either the RTC_WAKEUP or ELAPSED_REALTIME_WAKEUP flag.
We recommend including your package, class, or method name in your alarm's tag name so that you can easily identify the location in your
source where the alarm was set. Here are some additional tips:
Leave out any personally identifying information (PII) in the name, such as an email address. Otherwise, the device logs _UNKNOWN
instead of the alarm name.
Don't get the class or method name programmatically, for example by calling getName(), because it could get obfuscated by Proguard.
Instead use a hard-coded string.
Don't add a counter or unique identifiers to alarm tags. The system will not be able to aggregate alarms that are set that way
because they all have unique identifiers.
After fixing the problem, verify that your wakeup alarms are working
as expected by running the following ADB command:
adb shell dumpsys alarm
This command provides information about the status of the alarm system
service on the device. For more information, see dumpsys.
If you have particular issues with any of the above, you'll need to post another question with an mcve.
To improve the app, it would involve rewriting the code as mentioned here in Best Practices. Don't use the alarm manager to schedule background tasks, and location would be considered a background task, if the phone is asleep. Plus you say it's a background task. Use JobScheduler or Firebase JobDispatcher.
If the Alarm Manager is the better choice (which it's not here) it's important to have a good read here Scheduling Repeating Alarms, but you need to Understand the Trade-offs
A poorly designed alarm can cause battery drain and put a significant load on servers.
Given the limits placed on location gathering on Android apps running in the background on O devices, what are good options for developers who want more frequent location updates?
I realize that O is still in developer preview, but there are significant changes to how location can be gathered in the background, and I want to project ahead to changes that might be needed in my apps.
Apps requesting location updates that are not in the foreground are subject to some limits when running on O devices. Location updates are still available in the background, but they may be happen less frequently than the interval specified in LocationRequest#setInterval. Developers have the following options for getting more frequent location updates:
Request updates in the foreground. This means requesting and removing updates as part of the activity lifecycle (request in onResume() and remove in onPause(), for example). Apps running in the foreground are not subject to any location limits on O devices .
Request updates using a foreground service. This involves displaying a persistent notification to users.
Use geofencing to trigger notifications based on the device’s location. If your use case relies on the device entering, dwelling, or exiting a particular area of interest, this API provides a performant way to get these notifications. See the GeofencingEvent#getTriggeringLocation[, which gets the location that triggered the geofence transition.
Use batched location updates using LocationRequest#setMaxWaitTime. With this API, locations may be provided more frequently than the non-batched API however, will be delivered in a batch after the interval specified in setMaxWaitTime (also limited to a few times an hour)
Use passive location updates: While your app is in the background, it may continue to receive location updates passively if another app in the foreground requests location updates. You can receive some of these updates by using LocationRequest#setFastestInterval with a small interval, such as 5 min.
Given that this is a large-scoped question on how to handle Android O background location restrictions, this may be relevant:
If your app needs to receive locations in the background primarily to use as a dependent input, Awareness API may be useful for you.
Geofence is one example where you want to be notified when device moves by X miles (and you do not really care about the actual location coordinates). Similarly, if you need location for things like fetching location-dependent data on server, or triggering location-dependent notifications, Awareness has a bunch of useful APIs.
For example, the weather snapshot API lets you get the weather at device location without you having to request locations. TimeFence has APIs for waking-up apps at instants based on timezone or sunrise/set etc at device location.
I am in the process of creating a custom Phonegap plugin for Android that monitors location both when the app is in the foreground and when it is backgrounded. Google documentation on using the FusedLocationProviderAPI is remarkably clear. The process I have worked out thus far is as follows
Ensure that the API is available
GoogleApiAvailability api = GoogleApiAvailability.getInstance();
int code = api.isGooglePlayServicesAvailable(ctxt);
return (code == ConnectionResult.SUCCESS);
Define a LocationListener with assigned callbacks to handle the results retured by the requestLocationUpdates method.
Create a LocationRequest
Here is where things become slightly unclear
setInterval - the interval at which the app wants location updates
setFastestInterval - the interval at which it will consume updates if available.
setSmallestDistance & setPriorty - clear enough
setNumUpdates - how this works is not clear to me. Reading between the lines I am assuming that if I use setInterval(60000) and setNumUpdates(1000) the system will keep sending back location updates for the next 6000 minutes or until such time as the app is backgrounded/shutdown or I/the user cancels location updates.
But then this begs the question - what does the app need to do to be a good citizen. I am assuming that would have to be something like this
Record the PendingResult being returned by the requestLocationUpdates call.
Detect when the onPause event occurs
call PendingResultt.cancel() prior to letting the app go to the background
I'd be much obliged if someone could comment on the correctness of this workflow.
A related issue - The documentation for PendingResult states
It is the responsibility of the caller or callback receiver to release any resources associated with the returned result.
It is not clear to me what resources they are talking about here. The LocationListener.onLocationChanged event returns a Location object which I assume will be garbage collected when it goes out of scope. Presumably the PendingResult being returned by requestLocationUpdates should be canceled and then set to null when the app goes to the background. Is there anything else one needs to do by way of releasing resources?
A few hours later
I created two versions of my test app
App 1:Sets up the LocationRequest with setNumUpdates(10000). Pops up toasts on location change in the form App 1:Location is...
App 2:Sets up the LocationRequest with setNumUpdates(1). Pops up toasts on location change in the form App 2`:Location is...
I had the two apps running simultaneously and simulated position changes with the help of a really neat little app called FakeGPS. Both App1 and App2 provided me with an update when I did my first fake location change. However, all subsequent location changes were reported only by App 1.
By inference then setNumUpdates provides a mechanism for polling for updates periodically. What is slightly confusing is that the updates continue even after the app is backgrounded - though I assume that this is largely because it is at the mercy of the OS which will kill it when it deems fit.
However, all of the above is based on empirical testing. I find surprisingly little on the setNumUpdates setting.
To your question, Is update continue even if app is in background:
Ans: What ever be the case setNumUpdates is 1 or x, when your app is in background and is still registered to update, you will get the updates, unless the OS has killed your app, for memory.
The only difference that setNumUpdates does is, as you said correctly, if its set to 1, it will give only one update, unless you has reregistered again.
Link has sufficient definition for setNumUpdates
https://developers.google.com/android/reference/com/google/android/gms/location/LocationRequest.html#public-methods