How to implement FusedLocationProviderClient as a Service? - android

Since this the new Android 8 update came out there are many limitations imposed on how background services work.
So my app needs to fetch in background the user's location regularly and for that, I considered using FusedLocationProviderClient suggested by Android Website.
After the location has been fetched I just need to some simple work with the info.
Right now I developed this sample of code:
private fun updateLocation(){
//Location Request
val mLocationRequest = LocationRequest();
mLocationRequest.smallestDisplacement = 1000f;
mLocationRequest.interval = 5*60*1000;
mLocationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
//Location Provider
val mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
//Check Location Permission
if(PermissionChecker.checkSelfPermission(this,ACCESS_FINE_LOCATION)==PackageManager.PERMISSION_GRANTED)
//Request Location
mFusedLocationProviderClient.requestLocationUpdates(mLocationRequest,locationCallback,null);
}
private val locationCallback = object:LocationCallback(){
override fun onLocationResult(locationResult: LocationResult?) {
doSomeWork()
}
}
So I have a question:
Q. Should I use a Scheduled Job each time I want to update the location?
If I want my app to get updates even if the activity has been closed, it sounds like a Service's job right? But with this new updates, my service doesn't run forever so it will be killed sooner or later.
Using a regularly scheduled job, the service will be launched when the conditions are met but it seems kinda weird to schedule regular jobs to update the location instead of using a unique service that makes use of the Interval and SmallestDisplacement for new updates.
The requestLocationUpdates() invoke his callback every time the LocationRequest's conditions are met (interval and/or smallestDisplacement in this sample). That's what I'm trying to accomplish, I want a job that requests for location updates, not multiple jobs rescheduled as I need the updates that request for the location.
Is there any other better way to accomplish what I'm trying to say or I need this new approach of scheduled jobs?
Anyways, sorry for this messy post, it's my first question on Stack Overflow
Thanks :)

Use the Geofencing API for that. That gives you an intent that you can handle in an IntentService. No notification required.
All you need to do is to setup a new geofence with 1000m radius at the current position and wait for the user to leave.

If you want your service run in small periods you should use Bound Service or Foreground Service. If you want some specific Job triggered depending on some condition or significant amount of time you should use JobScheduler.

Related

Send Location Updates to Server in Background

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")
}

Best practice for background task execute at fixed interval of 10 minutes

I have an app for managing employees and need to track their location at fixed interval of 10 minutes during work hours
what is the best solution for this work manager,services or job scheduler
i already tried services but when app is forced close it doesn't run and lose employee location
For tracking location periodically,
Approximately for X mins - Recommended way :
Use Fused Location provider and set time appropriately. It gives you location update callbacks frequently and is Android's recommended way of getting location. Set your interval using this
Ensure you have location permission. Invoke location permission dialog if you don't have permission
Exact for X mins
Use sticky foreground service to track location continuously without your app getting killed. Check sample here
See Example below
For Fused Location provider
#Override
protected void onResume() {
super.onResume();
if (requestingLocationUpdates) {
startLocationUpdates();
}
}
private void startLocationUpdates() {
fusedLocationClient.requestLocationUpdates(createLocationRequest,
locationCallback,
Looper.getMainLooper());
}
Create your location request parameters as below :
protected static LocationRequest createLocationRequest() {
LocationRequest mLocationRequest = LocationRequest.create();
mLocationRequest.setInterval(LOCATION_UPDATE_TIME_INTERVAL_MINS * 60 * 1000);
mLocationRequest.setFastestInterval(LOCATION_UPDATE_FATEST_TIME_INTERVAL_MINS * 60 * 1000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
return mLocationRequest;
}
Check detailed documentation for Receive periodic location updates here
Posting from Commonsware answer here :
When doing stuff periodically in the background — JobScheduler, WorkManager, AlarmManager, FCM push messages, etc. — you have to take into account that your process might not be around when it is time for you to do your work. Android will fork a process for you, but it is "starting from scratch". Anything that your UI might have set up in memory, such as a database, would have been for some prior process and might not be set up in the new process.

Scanning for beacons and getting location periodically in the foreground

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.

Excessive Alarm Manager wakeups in android with Google Play Services Location

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.

Consistent GPS updates - AlarmManager or requestLocationUpdates()?

I need to get GPS location updates every minute (battery life isn't a problem as the device(s) will be charged within the vehicles). It's a company delivery app, with vehicle tracking, used in conjunction with Google maps, to track journeys for delivery planners etc.
I've used both AlarmManager and LocationManager.requestLocationUpdates() with a listener. The later seems a bit ropey and going by the documentation, it may or may not give a location. But I've seen that even though the minTime is set to 1 minute and distance is 0/1, I'm still only seeing updates once or twice per 5 min period.
private void startGeoLocationSender()
{
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new GeoLocationListener(this);
// MINIMUM TIME TO REQUEST , MIN DISTANCE TO REQUEST, LISTENER
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
AppSettings.COLLECTOR_PROC_1_MINS,
AppSettings.COLLECTOR_DISTANCE_1_M, locationListener);
}
So, is it worth me just going back to an AlarmManager and forcing an update manually for it to be reliable? Or is there another method, which is 100% reliable given a specific update time? What is better suited to my requirement?
PS. I'm very aware of the other '000s of topics on the subject, but I've never seen such a Good Vs Evil trend. Some folk swear by location listeners, and others only AlarmManagers.
I am not sure of a better way, so I would recommend AlarmManager.
If you use only LocationListener you can't ensure that will receive update in interval time needed.
I really think that the best option is the AlarmManager or a Scheluder Service with a trigger to your LocationListener.

Categories

Resources