I am working with a system application. It has to perform some extremely critical tasks in a service that are triggered by broadcasts from the system.
Due to the background execution limits introduced in Oreo, I've been thinking a lot about how to perform the tasks in the background.
The tasks have the following requirements:
They may not be deferred, they have to be started instantly.
They shall be started even if the phone is idle (or dozing, I haven't really understood the difference)
They are highly critical. They shall complete after being started, under all circumstances.
I looked into using IntentService. But, they recommend using JobIntentService instead due to the background execution restrictions. However, this doesn't comply with my requirement of not deferring the work. It also says that JobScheduler does not run during doze. When running on O or later, JobIntentService will perform the task as a JobService Job:
When running as a Job, it will be subject to standard JobScheduler policies for a Job with a setOverrideDeadline(long) of 0: the job will not run while the device is dozing, it may get delayed more than a service if the device is under strong memory pressure with lots of demand to run jobs.
It seems like using a regular IntentService may expose the tasks to the possibility to be stopped/killed by the system under certain circumstances, imposed by the new restrictions from Oreo and above.
The safest option seems to be to launch a foreground service. But, I don't necessarily want to show a notification during the service's lifetime. Also, I believe that if the user accidentally disables the notification channel in the settings, the service cannot be started anymore. I'm also worried about edge cases where the system may arbitrarily kill or stop my service, or simply stop my work in some other way by not honoring my wake lock etc.
I recently came across the android:persistent in the application tag in the manifest. It says it can only be used by system applications. According to this blog post from 2011 setting this attribute to true renders your application and it's services un-killable. It also implies that it can let you have a background service that is "always alive". But how does this relate to doze, battery optimizations etc? Do I still have to acquire a wake lock and whitelist my app from battery optimizations in order to continue performing the background work during doze conditions?
Thanks a lot for reading and I hope you have some valuable input. I am currently a bit confused, trying to put all the pieces together. It doesn't help that the documentation is (as per usual)... lacking.
Related
I have an app where I use a foreground service to start a number N of threads that sleep most of the time and sometimes wake up to do some measurements.
I used foreground services because I need that these measurements must be done at specific and exact time without background limitations introduced by Android 8.0.
This seems to work and from documentation seems that there are no problem, but I read also about JobScheduler.
There is an advantage to use Jobs to schedule work at specific accurate time or my solution can be used without problems.
First a fact
In JobScheduler, the System execute your Job(Task) in application's JobService
and the JobService class also extend the same Service class that we use to define Foreground Service. So by using the both, we can execute code in background
Now the main difference is, Foreground Service is always running(by showing notification to user) and consuming the battery and memory of the user even, if your threads are sleeping and no code is executing.
As it's running always you can do whatever you want precisely at any moment of time. maybe it's good for your app's point of view but it's bad for user. your app draining the battery unnecessarily and consuming the RAM.
To address this problem we got JobScheduler. you can Schedule a job to be executed based on some criteria. Your app will only wake when the criteria is met, but it's not precise.it depends on many factors like doze mode etc.
you can look more about that here
The conclusion is
If your task is not needed to be execute at exact time then you should use JobScheduler (recently WorkManager is better as it use JobScheduler internally and more advance) to save your user's battery
and according official document
WorkManager is intended for tasks that are deferrable—that is, not
required to run immediately—and required to run reliably even if the
app exits or the device restarts.
For your use case, you will be better off using a WorkManager which according to the android documentation, uses JobScheduler on API 23+ and a combination of BroadcastManager and AlarmManager on API 14 - 22.
With a WorkManager your jobs will run reliably even if your app exits or the device restarts.
https://developer.android.com/topic/libraries/architecture/workmanager
Since threads persist past the lifetime of the activity that spawn them, I can just put whatever background work I need to do on HandlerThreads for example, no need for a service. Also they will keep running when the app in in the background bypassing the new Oreo restrictions.
Am I missing something here?
Also, ever since the introduction of Doze mode and the addition of even more restrictions on background work starting Oreo, when exactly should I use a service to do background work? Apart from
scheduling tasks for future conditions, such as WIFI connection, or charging I would then use a JobScheduler. But even that can be handled through a BroadcastReceiver...
Also they will keep running when the app in in the background bypassing the new Oreo restrictions.
That isn't quite right. It's true that background threads will continue to execute for as long as your app is alive. The problem is, your app might not be alive for very long! A Service is used to indicate to the operating system, "I don't want to get torn down; I still have useful work I have to do".
From the docs:
...[A Service represents] either an application's desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use.
and
It is not a means itself to do work off of the main thread
Ultimately, when Android is deciding whether or not to keep your App around, it doesn't care about the number of threads you have running, the number of CountDownTimers that haven't finished yet, how many Runnables you have waiting in the queue, etc. It cares about whether or not you have any active application components. Is an Activity visible? Great, stick around. Is a Service started? Also great. None of the above? Maybe it's time to terminate the app.
So this also answers the question, "when exactly should I use a service to do background work?" As mentioned, a Service won't do the work for you, it'll just help you stay alive. You can try to kick off a thread in a BroadcastReceiver (note that most implicit broadcasts no longer work post-Oreo), but as soon as you return from onReceive(), your app is likely to be killed -- unless you have a Service going, too.
ADDITIONAL POST-OREO CAVEATS
Note that a Service is likely only going to help your app stay alive for "several minutes" after the app leaves the foreground (docs). The only way I am aware of to get around this is to get back into the foreground by making your Service a "foreground service."
Additionally, if you need to ensure the device remains awake until your work is completed, you'll need a component in the foreground. That is, you can still do that work in the "background" (in the sense of being "off-screen"), but you'd need a "foreground Service" (icon in the action bar). Otherwise, Doze will apply, and that tends to inhibit any WakeLocks your app is using.
I want to schedule nightly database updates. So I use new Android WorkManager. My understanding is that once scheduled it will always run in the background independently from the app's lifecycle.
Is that right? My first tests show that Work is only being performed when the app is running.
val locationWork = PeriodicWorkRequest.Builder(UpdateDatabaseWorker::class.java, 24, TimeUnit.HOURS)
.addTag("DATABASE_UPDATE_SERVICE")
.build()
WorkManager.getInstance().enqueue(locationWork)
Based on various issues reported on the WorkManager bugtracker, their documentation is not completely precise about the exact behavior of the WorkManager in such edge cases.
On certain devices, apps are force stopped when the app is cleared from task manager, so that part is expected. ... source
Unfortunately, some devices implement killing the app from the recents menu as a force stop. Stock Android does not do this. When an app is force stopped, it cannot execute jobs, receive alarms or broadcasts, etc. So unfortunately, it's infeasible for us to address it - the problem lies in the OS and there is no workaround. source
The only issue that we have come across is the case where some Chinese OEMs treat swipe to dismiss from Recents as a force stop. When that happens, WorkManager will reschedule all pending jobs, next time the app starts up. Given that this is a CDD violation, there is not much more that WorkManager can do given its a client library. source
To add to this, if a device manufacturer has decided to modify stock Android to force-stop the app, WorkManager will stop working (as will JobScheduler, alarms, broadcast receivers, etc.). There is no way to work around this. Some device manufacturers do this, unfortunately, so in those cases WorkManager will stop working until the next time the app is launched. source
With intense testing of a OneTimeWorkRequest (without constraints) on a Pixel 2 XL with stock android the behavior is the following:
Task manager close:
Work continues (after a bit)
Reboot device (work running):
Work continues after reboot done
App info "Force stop":
Work stops, will only continue when app is started again
Reboot device (work was "Force Stopped"):
Work does not continue until the app is started again
You can find a complete list of different OEM behaviors on dontkillmyapp.com. It seems the Android team also acknowledges this issue and added a test for this into their CTS test for Android Q. source
My understanding is that once scheduled it will always run in the
background independently from the app's lifecycle. Is that right?
Yes. Based on the documentation
The task is still guaranteed to run, even if your app is force-quit or
the device is rebooted.
WorkManager chooses the appropriate way to run your task based on factors such as the device API level and the app state. If WorkManager executes one of your tasks while the app is running, WorkManager can run your task in a new thread in your app's process. If your app is not running, WorkManager chooses an appropriate way to schedule a background task--depending on the device API level.
WorkManager might use JobScheduler, Firebase JobDispatcher, or AlarmManager depending on the API level. It will repect the Doze and conaider all other constraints before executing the Work. You can expect some delay in Doze mode since it could wait for maintenance window.
Note:
WorkManager is intended for tasks that require a guarantee that the system will run them even if the app exits, like uploading app data to a server. It is not intended for in-process background work that can safely be terminated if the app process goes away; for situations like that, we recommend using ThreadPools.
This is what documentation is saying:
Note: WorkManager is intended for tasks that require a guarantee that the system will run them even if the app exits, like uploading app data to a server. It is not intended for in-process background work that can safely be terminated if the app process goes away; for situations like that, we recommend using ThreadPools.
But there must be some condition. if that condition meet then WorkManager will run the task (this is important). Conditions like "only while device is charging and online"
Read this carefully, The WorkManager attempts to run your task at the interval you request, subject to the constraints you impose and its other requirements.
Here I found a good tutorial about how to use WorkManager for scheduling tasks : https://android.jlelse.eu/how-scheduling-work-with-new-android-jetpack-component-workmanager-852163f4825b
While testing the background execution limits as stated here I created a thread in my service. Something like this :
// spawn own thread
HandlerThread thread = new HandlerThread("TestServiceThread");
thread.start();
The service is started by the usual pre Oreo startService.
What I noticed while debugging in android device monitor is that the threads I create continue to live long and healthy and execute normally. Can this be a workaround to keep abusing system resources and not do things via foreground services or jobs ? or is it just a chase against the framework which will soon end ? Any comments ?
I wouldn't expect that to change. Its impossible (not just in Android, its theoretically impossible) to safely kill a thread without the thread helping out. Otherwise you could kill the thread in a condition where other threads will deadlock, have inconsistent data, or the app may just not function. That's why thread.stop() is deprecated- because there is no way to make it safe. And that's why you interrupt a thread instead, and the thread needs to monitor isInterrupted and exit cleanly.
I would be careful with this kind of approach.
Although Gabe Sechan's answer is quite valid, the danger of relying on this working is too high to pursue this. Google are clearly out to get any app that tries to do background execution and abuses the user's battery and for good reason in my opinion. Some apps just don't respect a user's battery at all.
The documentation clearly states:
An app is considered to be in the foreground if any of the following
is true:
It has a visible activity, whether the activity is started or paused.
It has a foreground service. Another foreground app is connected to
the app, either by binding to one of its services or by making use of
one of its content providers. For example, the app is in the
foreground if another app binds to its: IME Wallpaper service
Notification listener Voice or text service If none of those
conditions is true, the app is considered to be in the background.
Source:
https://developer.android.com/about/versions/oreo/background.html#services
Although it's not safe to kill a thread due to all the reasons mentioned by Gabe, Android could well kill the app off entirely (ala kill -9). I would imagine that any deadlocks would be handled by Android (not a trivial task I'm sure). Data corruption would be considered as the app's fault for not handling background execution properly.
It's a bit of a risk for Android but they've probably weighed it up and decided this is the way to go.
Also, take care of this:
Note: By default, these restrictions only apply to apps that target
Android 8.0 (API level 26) or higher. However, users can enable most
of these restrictions for any app from the Settings screen, even if
the app targets an API level lower than 26.
Source: https://developer.android.com/about/versions/oreo/background.html#overview
I have a downloader application on Android.
It shows a notification(in-progress, not dismissable) during the download
and it also catches a wakelock.
I even asks the user to disable Doze for my app.
However, battery-saving feature from various vendors seem to ignore it and kill it randomly.
Is there a way to mark my app process "busy",
so that it has a higher priority in the not-to-kill list?
Note that I'm not using a service in my app.
Regular activity spawns up a thread and download is handled from there.
Note that I'm not using a service in my app
That would be the lion's share of your problem.
Regular activity spawns up a thread and download is handled from there.
That means that Android has no idea that you are doing anything that the user would value, when you're not in the foreground. Android will happily terminate your process to free up system RAM for other processes.
Use a service, perhaps an IntentService (since it already has a background thread for you, and it automatically shuts down once your work is complete). Convert your Notification into one for startForeground() on the service.
If you are keeping a wakelock for a longish time then it is better to let the user know about it, use a foreground service as CommonsWare pointed out.
However, if your use case does not warrant any foreground behavior then I would recommend you use framework JobScheduler that plays very well with doze and app standby as well.
For earlier than API 21 you may use JobDispatcher API.
You can read the more details here.