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.
Related
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.
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.
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.
I'm using the FusedLocationApi to get updates. But in spite of setting the LocationRequest.setInterval to say 500 or even 200 millis, I only get one update per second. I'd like to get two a second if possible (to do a moving average of speed). Is there a hard limit?
In my onCreate for the Activity:
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.addApi(AppIndex.API).build();
In my 'onConnected' method:
this.locationRequest = new LocationRequest();
this.locationRequest.setInterval(200);
this.locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.FusedLocationApi.requestLocationUpdates(this.googleApiClient, this.locationRequest, this);
I also have a separate LocationManager I only use to get status changes via addGpsStatusListener() and those seem to work OK.
Thanks
As per documentations
This interval is inexact. You may not receive updates at all (if no location sources are available), or you may receive them slower than requested.
You can try to use public LocationRequest setFastestInterval (long millis). The documentation states:
he fastest rate that that you will receive updates can be controlled with setFastestInterval(long). By default this fastest rate is 6x the interval frequency.
Also make sure you request ACCESS_FINE_LOCATION. The docs also states that:
Applications with only the coarse location permission may have their interval silently throttled
As the "LocationRequest.setInterval()" docs says
The location client will actively try to obtain location updates for your application at this interval[...]
So it's possible you cannot get the location updated in the desired frequency. That's because the nature of the location retrieving techniques (GPS, WiFi, etc.) and it's very hardware dependent (and also the Google's fused location provider algorithms), and also depends on the atmospheric conditions (in the GPS case). If you want to obtain a high accuracy location, then the device needs to obtain first some samples in order to do a high accuracy estimation.
I am thinking about having two separate alarms to gather a user's location data every hour, one that goes off every 59 minutes to "connect" the client and a second to actually get the location and then subsequently disconnect the client.
In terms of battery life, is there anything else I should consider doing if getting the user's location will be the primary drain of the app? Or, is there a different approach to having two alarms? I originally only had a single alarm, but performing a (!mLocationClient.isConnected) then connect check does not give the client enough time to connect.
Thanks for your insight.
The two alarms would go off something like this:
private int PERIODIC_UPDATE = 60000*60; //gets location and disconnects every hour
private int PERIODIC_RECONNECTION_UPDATE = 60000*59; //connects 1 minute before getLocation call
Timer toReconnect = new Timer();
toReconnect.schedule(new TimerTask() {
#Override
public void run() {
mLocationClient.connect();
}
}, 5000, PERIODIC_RECONNECTION_UPDATE);
Timer theTimer = new Timer();
theTimer.schedule(new TimerTask(){
#Override
public void run() {
try {
if(!mLocationClient.isConnected()) {
mLocationClient.connect();
//This will not have much affect because cannot so quickly, will remove.
}
Location theLocation = mLocationClient.getLastLocation();
if(theLocation!=null) {
checkPostLocation(theLocation);
mLocationClient.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
}}, 5000, PERIODIC_UPDATE);
See the "Receiving Location Updates" section on the Android developer docs for a detailed discussion of this topic with the new Fused LocationProvider:
http://developer.android.com/training/location/receive-location-updates.html
This gives you the option to register an Intent with a LocationListener to the Fused LocationProvider that is automatically triggered by the internal Google Services framework when it is considered "efficient" to do so. I would trust that this framework has a much greater potential to optimize power usage, since it has a lot more knowledge of what else is going on in the system vs. an app-registered Timer.
Here are your options for registering a listener with different power priority levels, which will result in different levels of battery drain, as listed in the above docs:
PRIORITY_BALANCED_POWER_ACCURACY - Used with setPriority(int) to request "block" level accuracy. Block level accuracy is considered to be about 100 meter accuracy. Using a coarse accuracy such as this often consumes less power.
PRIORITY_HIGH_ACCURACY - Used with setPriority(int) to request the most accurate locations available. This will return the finest location available (and the greatest potential for energy drain).
PRIORITY_NO_POWER - Used with setPriority(int) to request the best accuracy possible with zero additional power consumption. No locations will be returned unless a different client has requested location updates in which case this request will act as a passive listener to those locations.
Do you actually need to track the user?
If it's just about UI, then use getLastKnownLocation(PASSIVE_PROVIDER) and you should get something semi-accurate assuming they used location services on their phone somewhere else.
If you need to actually triangulate the user, realize the different providers use different battery. Passive < Network < GPS.
The more you locate the user, the more battery with GPS taking the most battery and time.
Start the service by intent one a schedule, 1 hour or whatever, only one service necessary. Only live for a maximum of 1 minute (or less), listen on all Location providers. After the minute or accuracy is good enough, you save the result and shut down the service.