I have an legacy VoIP app for which I want to provide Android's new doze and standby mode support. I have my own messaging/signaling mechanism for which I can't use Android's GCM feature. The documentation stated that white-listing the app will permit to use own signaling mechanism and keep the app alive in Doze mode.
Also I am generating keepAlive alarm using setExtract() and setRepeating() currently to keep alive the persistent connection of XMPP. If I add setExactAndAllowWhileIdle for newer version, its stated that - the Alarm will be triggered at most one in every 15 minutes. But I need to generate it 1 in every 12 seconds interval. I went through Android documentation, many threads in SO and found an article on it. It seems what I want to achieve is not completely possible by white-listing the app and using AlarmManager's new APIs.
What can I do to keep my functionality as like before? Keeping the app alive in doze & standby mode, generating alarm alert with 12 seconds interval to keep the connection alive and keep the network connection open?
White-listing the app by disabling battery optimization is keeping the app alive all time.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
Intent intent = new Intent();
intent.setAction(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
}
}
Android Manifest:
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
Although the new AlarmManager API setExactAndAllowWhileIdle is not get called within twelve seconds interval as required. The solution is - we are using JNI codes from before and we are sending keep alive by using a Timer thread to send keep alive alert in twelve seconds intervals. As the app is alive, the timer thread will be alive regardless the Alarm is working or not.
My app has a very complicated requirements for which I couldn't use GCM high priority message. But most of the VoIP apps like Skype don't disable battery optimization rather use GCM to send notification/messages in doze mode.
Related
It seems there are a few questions related to the subject topic but I haven't found a clear yes/no answer to this.
I have a foreground service that calls setExactAndAllowWhileIdle to start a BroadcastService. Below is the code in the Broadcast Receiver.
public class StepCountUpdaterAlarm extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myExampleApp::UpdateSteps");
wakeLock.acquire(3);
StepCounterHandler handler = StepCounterHandler.getInstance();
new TaskRunner().executeAsync(
new SetStepsForDay(SaveSharedPreference.getMemberId(context),
handler.getCumulativeSteps()),result -> {
handler.setAlarm(context);
if(isTimeToReset())
handler.resetStepsCounter();
});
wakeLock.release();
}
In the setExactAndAllowWhileIdle documentation it states:
When the alarm is dispatched, the app will also be added to the
system's temporary power exemption list for approximately 10 seconds
to allow that application to acquire further wake locks in which to
complete its work.
but in the Doze mode documentation it states this as a restriction:
The system ignores wake locks.
Does that mean acquiring a partial wake lock for 3 minutes in the 10 second window provided by an alarm dispatched by setExactAndAllowWhileIdle in Doze mode will effectively be useless or will it work fine?
In my case, the Broadcast Receiver will send data to my remote server via that async task, and after that it will set the alarm again and reset my steps counter. Will this work and if it wont, what are my alternatives for sending a network request in doze mode and executing follow up code?
EDIT: Testing by debugging my app shows that when forcing a device into idle mode, i still have network access and can send data to my server. The Doze mode documentation states how to force to app into the idle state which i am fairly sure is synonymous with doze mode. Yet this is supposed to be a restriction so I am clueless as to how this could be working.
A WakeLock won't help you much if the device is in Doze mode. setExactAndAllowWhileIdle will wake your device up if you have used either AlarmManager.ELAPSED_REALTIME_WAKEUP or AlarmManager.RTC_WAKEUP as the alarm type.
However, from Android 12 you need SCHEDULE_EXACT_ALARM permission to set exact alarms. And if you are planning to release the app to PlayStore there are some acceptable use cases for setting an exact alarm. Make sure your app complies with these policies.
Yes, Doze will ignore your wakelock. However with setExactAndAllowWhile Idle you will be worken up at the correct time, and you'll have that 10s window to do any processing you wish.
I've read just about every Stackoverflow answer that exists on this topic, but none of them worked.
Goal: Keep my service running 24/7, all the time
Problem: Whenever my device is on sleep mode for an hour or more, the service is killed
What I've tried to fix it:
Returning START_STICKY from onStartCommand() and using startForeground()
public int onStartCommand(Intent intent, int flags, int startId) {
notification = makeStickyNotification(); //I've simplified the irrelevant code, obviously this would be a real notification I build
startForeground(1234, notification);
return START_STICKY;
}
This works fine, and it even restarts my service whenever the device is low on memory, but it is not enough to fix the problem that occurs when my device goes to sleep for a while.
Using Alarm Manager in onCreate() of my Activity and in onStartCommand() of my Service to call a Broadcast Receiver that calls my service
Intent ll24 = new Intent(this, AlarmReceiver.class);
PendingIntent recurringLl24 = PendingIntent.getBroadcast(this, 0, ll24, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000*60, recurringLl24); // Every minute
This helps keep my service active, but again, doesn't solve my problem
Using Schedule Task Executor to keep it alive
if (scheduleTaskExecutor == null) {
scheduleTaskExecutor = Executors.newScheduledThreadPool(1);
scheduleTaskExecutor.scheduleAtFixedRate(new mainTask(), 0, 1, TimeUnit.SECONDS);
}
...
class mainTask implements Runnable {
public void run() {
// 1 Second Timer
}
}
This also just keeps the service active but doesn't keep it alive after a long sleep.
Separate task Manifest
android:launchMode="singleTop"
This did nothing
How can I (1) test this issue without having to put my phone to sleep and check every hour and (2) keep my service running despite the device going to sleep?
Your service was killed by Doze or Standby mode of Android. That was introduced in Android 6.0 (API level 23).
Doze restrictions
The following restrictions apply to your apps while in Doze:
Network access is suspended.
The system ignores wake locks.
Standard AlarmManager alarms (including setExact() and setWindow()) are deferred to the next maintenance window.
If you need to set alarms that fire while in Doze, use setAndAllowWhileIdle() or setExactAndAllowWhileIdle().
Alarms set with setAlarmClock() continue to fire normally — the system exits Doze shortly before those alarms fire.
The system does not perform Wi-Fi scans.
The system does not allow sync adapters to run. The system does not allow JobScheduler to run.
So system ignored your Alarm Clocks, Scheduler, etc.
In Android Oreo release Android defined limits to background services.
To improve the user experience, Android 8.0 (API level 26) imposes
limitations on what apps can do while running in the background.
Still if app need to run its service always, then we can create foreground service.
Background Service Limitations: While an app is idle, there are limits
to its use of background services. This does not apply to foreground
services, which are more noticeable to the user.
So create a foreground service. In which you will put a notification for user while your service is running. See this answer (There are many others)
Now what if you don't want a notification for your service. A solution is for that.
You can create some periodic task that will start your service, service will do its work and stops itself. By this your app will not be considered battery draining.
You can create periodic task with Alarm Manager, Job Scheduler, Evernote-Jobs or Work Manager.
Instead of telling pros & cons of each one. I just tell you best. Work manager is best solution for periodic tasks. Which was introduced with Android Architecture Component.
Unlike Job-Scheduler(only >21 API) it will work for all versions.
Also it starts work after a Doze-Standby mode.
Make a Android Boot Receiver for scheduling service after device boot.
I created forever running service with Work-Manager, that is working perfectly.
The murder mystery has been solved, and I know what killed my service. Here's what I did:
After I realized that startsticky, startforeground, alarmmanager, scheduleTaskExecutor, and even wakelock were unable to save my service, I realized the murderer couldn't be the Android system, because I had taken every measure possible to prevent the system from killing my service and it still would get killed.
I realized I needed to look for another suspect, since the service wasn't dying because of the system. For that, I had to run an investigation. I ran the following command:
adb shell dumpsys activity processes > tmp.txt
This would give me a detailed log of all the processes running and their system priorities. Essentially, tmp.txt would be the detective in this murder mystery.
I looked through the file with lots of detail. It looked like my service was prioritized properly by the system:
Proc #31: adj=prcp /FS trm= 0 2205:servicename.service/uID (fg-service)
The above line indicates the exact priority of a process running on the Android device. adj=prcp means the service is a visible foreground service.
At this point, I realized that my service must be encountering some error a couple hours after running, so I let it run and die. After it died, I produced a dumpsys again to examine the error:
At this point, my service wasn't listed as a task in the tmp.txt file. Excited, I scrolled to the bottom of the dumpsys and solved the mystery!
com.curlybrace.ruchir.appName.MyService$2.onForeground(MyService.java:199)
at com.rvalerio.fgchecker.AppChecker$2.run(AppChecker.java:118)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
The stack trace that caused the killing of my service was displayed right there! Essentially, a variable that would check for the foreground app being used would become null after a few hours of inactivity, which would cause an exception, and kill the service!
Key Takeaways:
If your service is getting killed, and you've done everything you can to make sure that it shouldn't be killed, perform a dumpsys and examine the nitty gritty of your device's activity process. I guarantee you will find the issue that way.
I still would like to have the bounty awarded to #Khemraj since his answer could be a great solution for someone who hasn't started their service properly. However, I am accepting this answer since it is the solution that actually fixed the issue.
onDestroy() is really unreliable and won't be called often that you want. Same for onLowMemory() callbacks. There is no way to take a guaranteed callback if android decides to kill your process or if user decides to Force Stop your app.
That's normal that than user device go to sleep mode, your service dies. Read about wakelocks. Try something like that in your service:
In manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
In service:
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"tag");
wakeLock.acquire();
But it's rly tricky for user and totally anti-pattern in android world, cuz of battery consumption.
Another option is to trigger service something like every 10 mins. Make pending intent on WakefulBroadcastReceiver(where you can start your service) and schedule it with alarm manager with flag RTC_WAKE_UP
Starting from SDK 26 a Service should have its relative "MainActivity" in foreground OR this Service should be started as in foreground using "startForegroundService()". The "startForeground()" doesn't work as expected if the target SDK is 26+ but need the other way I just explained.
After this you can use following code to Kill and restart the App from scratch (yes, even the Service is killed in this way):
Intent mStartActivity = new Intent(context, StartActivity.class);
int mPendingIntentId = 123456;
PendingIntent mPendingIntent = PendingIntent.getActivity(context, mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
System.exit(0);
Doze mode kills services to save battery. Only valid solution for you is to create a foreground service in Oreo and above.
https://developer.android.com/about/versions/oreo/background
I'm developing an app which connects to a special device via wifi. I need to make status updates in a short interval, resp. keep the status of my special device in the app up to date. FCM is not an option. My idea is to give the user two options: Fast updates using a foreground service or "slow" updates using a periodical update mechanism to save battery.
My question is about the second option. The interval should be around five minutes. Using JobScheduler therefore is not possible. But even using the AlarmManager seems not to be an option because I'm not able to get network access during the doze maintenance windows.
I thought about using a WakefulBroadcastReceiver to receive the Intent by the AlarmManager, require a WakeLock and turn my long running Service into foreground by calling startForeground(). But it seems that the startForeground() method has no effect on the Service as long as the device stays in doze mode.
I read many pages about doze and services but have no clue how to solve my problem... Does anyone got an idea?
you should use GcmTaskService. You can schedule some interval for your operations and it would work fine even in doze mode, check more information by link
You can use setAlarmClock, but it is not recommended for your purposes.
Instead you can use setExactAndAllowWhileIdle with manual re-programming with an interval of 15 minutes.
Best way: GCM.
AlarmManager on API19 has the method setExact() to set an exact alarm.
Exact means --> If I set an alarm to 2:01 pm it will be triggered at 2:01 pm
On API 23 - Marhsmwallow (6.0) there is a new method setExactAndAllowWhileIdle(), but as of the reference it is not EXACT because it will trigger only every minute and in low power idle mode only every 15 minutes.
Exact != every 15 minutes :-)
So how can I achieve an exact alarm with AlarmManager in 6.0?
If a user adds a reminder or a calendar appointment and wants to be informed 10 minutes before the event it should show the alarm EXACT 10 minutes before the event. With setExactAndAllowWhileIdle() this seems is not possible.
Reference Link:
http://developer.android.com/reference/android/app/AlarmManager.html#setExactAndAllowWhileIdle(int, long, android.app.PendingIntent)
So how can I achieve an exact alarm with AlarmManager in 6.0?
You are welcome to try setAlarmClock(), as AFAIK it is unaffected by Doze mode. Otherwise, AlarmManager is not a viable option for you. Even having your app on the battery optimization whitelist will not help, as AlarmManager behavior does not change based on the whitelist.
You are welcome to use GCM, as a high-priority message should give you an opportunity to alert the user. This, of course, requires network connectivity.
The only offline solution that I am aware of — and that I am presently testing — is to have the user add your app to the battery optimization whitelist, then use a foreground service (to try to keep your process around), a ScheduledExecutorService (for the timing), and a partial WakeLock (to keep the CPU on). This will be fairly devastating to the user's battery.
Using setExactAndAllowWhileIdle() for a one-time alarm will fire exactly on the given time even in Doze idle mode. So this probably is the way to go.
Problems start, if you want to repeat the alarm at a rate of < 15 min (or set any other at a time < 15 min away from the last one), as this will not work in Doze idle mode, where such alarms are forced to the next 15 min or are executed when idle maintenance starts, which happens for about ten minutes first after 1 hour, then after another 2 hours, then after another 4 hours and so on.
- EDIT -
As of today Nov 17, Dianne Hackborn writes in this Post's comments:
"For what it's worth, the minimum time between while idle alarms will be changing to 9 minutes at some point relatively soon (even on devices running the current Marshmallow builds)."
This doesn't change anything fundamentally though.
Here are my discussion with Ian Lake on Google+!
setExactAndAllowWhileIdle() is exact and should work.
The 15 minutes time frame is wrong in the java doc.
I was trying to create an automation system running in the background. My frequency range was between 1-15 minutes. My wish was not to use a foreground service. By looking at the name of the method "setExactAndAllowWhileIdle", I thought that yeah it is safe to go with one-time alarms, scheduling the next one when done.
However, I couldn't find a way to run code in doze mode with alarms running more frequent than 15 minutes. Instead, I choose to start a foreground service when doze mode gets activated and stop that foreground service when phone awakes. User won't be seeing your foreground notification while using his/her phone. I don't care much about the ones in doze mode.
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if(intent.getAction().equals("android.os.action.DEVICE_IDLE_MODE_CHANGED")){
if (pm.isDeviceIdleMode()) {
//startAutomationForegroundService();
} else {
//stopAutomationForegroundService();
return;
}
AutomationReceiver.completeWakefulIntent(intent);
return;
}
}
You need to register "android.os.action.DEVICE_IDLE_MODE_CHANGED" intent filter into your WakefulBroadcastReceiver. Care putting it into manifest may not help.
I've got a pretty simple app that runs two services in the background, using IntentService with AlarmManager. One is a "messaging" service that sends a JSON request and parses the response, the other polls for location from a LocationManager. The requirement here is that they run on an interval, indefinitely, until manually stopped by the user, with a button - even if the device's screen hasn't been turned on for days. The services must never stop. Battery life is not a concern.
My minimum-supported API is 4.1 and I'm testing on 4.1, 4.2, and 4.4 devices. On my Nexus 7 and GPad, 4.4.4 and 4.4.2, respectively, the services will run indefinitely and work as expected. On a Galaxy Tab 3 running 4.2, the device seemed to go to sleep after 8 hrs. or so of inactivity, and would then quit reporting location and polling. The 4.1 devices seem to do the same.
With that hunch, I added this to the messaging service to manually wake the CPU. This one runs every 60 sec. so I had hoped it would prevent the devices from ever going to sleep.
#Override
protected void onHandleIntent(Intent intent)
{
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"com.service.MESSAGE_SERVICE_WAKE_LOCK");
wl.acquire();
//poll for messages via JSON
wl.release();
}
Setting the AlarmManager like so (pi = PendingIntent...passing in the IntentService class):
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pollInterval, pi);
This appeared to fix things for my own testing, and the devices are consistently reporting location for me on 4.4 and 4.2. However, the 4.1 testers have reported that it still goes to sleep on them after about 8 hrs. I also tested it on a friend's Galaxy S5 (4.4.2) and it quit reporting location after a few hours.
This is my first app and this part has been especially frustrating. Am I doing something wrong? Any idea why these services might stop running? Happy to provide more code if that helps...thought I'd start with this, at least.
Alarms which use PendingIntent for a Service are not guaranteed to keep the device awake long enough for the Service to be started or resumed. In the case of an IntentService, the service automatically dies once it has processed all queued Intent objects. You'll need to use a PendingIntent for a BroadcastReceiver which takes a WakeLock and starts your Service. Your Service can then release the wakelock when it is done with its work, allowing the device to go back to sleep.
If you wish for a service to "always" run, you shouldn't make is a background service.
Make it a foreground service instead:
A foreground service is a service that's considered to be something
the user is actively aware of and thus not a candidate for the system
to kill when low on memory.
In order to try out how long your service can run when the OS has low RAM, you can try this sample I've made (which I've posted here, and uses this library I've made), which uses more and more RAM (real RAM, not of the heap). It causes the OS to kill other processes and eventually kill the process of the sample itself.
If you just want your services to run every X hours/minutes, you don't need this at all, and what you've found seems ok.