I'm trying to start an IntentService within my BOOT_COMPLETED receiver, but in Android O (API 26) I get:
java.lang.RuntimeException:
java.lang.IllegalStateException:
Not allowed to start service Intent { act=intent.action.update cmp=packageName.services.OwnService }:
app is in background
(Message is in one line, but this way it's easier readable)
How can I do this the correct way?
Here are some options that I outlined in a blog post:
Workaround #1: startForegroundService()
Your BroadcastReceiver that receives the ACTION_BOOT_COMPLETED broadcast
could call startForegroundService() instead of startService() when on Android
8.0+:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
public class OnBootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent i=new Intent(context, TestIntentService.class);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) {
context.startForegroundService(i);
}
else {
context.startService(i);
}
}
}
Note that this works, to an extent, even if your service does not actually
ever call startForeground(). You are given a window of time to get around
to calling startForeground(), "comparable to the ANR interval to do this".
If your work is longer than a millisecond but less than a few seconds,
you could skip the Notification and the startForeground() call. However,
you will get an error in LogCat:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.commonsware.myapplication, PID: 5991
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1775)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Of course, if you do not mind having a Notification briefly, you are welcome
to use startForeground() as Android expects you to, in which case you can
do background work normally, albeit with an entry showing up in the user's notification
shade.
Workaround #2: goAsync()
BroadcastReceiver has offered goAsync() since API Level 11. This allows your
receiver to do work off the main application thread, so you could get rid of the
IntentService entirely and move your code into the BroadcastReceiver.
You still only have the ANR
timeout period to work with, but you will not be tying up your main application
thread. This is better than the first workaround, insofar as it has the same
time limitation but avoids the nasty error. However, it does require some amount
of rework.
Workaround #3: JobScheduler
If your work will take more than a few seconds and you want to avoid the
Notification, you could modify your code to implement a JobService and
work with JobScheduler. This has the added advantage of only giving you
control when other criteria are met (e.g., there is a usable Internet
connection). However, not only does this require a rewrite, but JobScheduler
is only available on Android 5.0+, so if your minSdkVersion is less than 21,
you will need some other solution on the older devices.
UPDATE: Eugen Pechanec pointed out JobIntentService,
which is an interesting JobService/IntentService mashup.
You may want to check the following section of the Android O behaviour changes documentation https://developer.android.com/preview/features/background.html#services
It now limits when the app is able to start background services.
Related
I am having a situation where I get this error when I am trying to start a service from a receiver when app is closed or runs in the background.
But the docs clearly state:
The state of your BroadcastReceiver (whether it is running or not)
affects the state of its containing process, which can in turn affect
its likelihood of being killed by the system. For example, when a
process executes a receiver (that is, currently running the code in
its onReceive() method), it is considered to be a foreground process.
The system keeps the process running except under cases of extreme
memory pressure.
In other words when the app is in the foreground therefore it can theoretically start a service.
So whats the problem here?
#Override
public void onReceive(Context context, Intent intent) {
// assumes WordService is a registered service
context.startService(new Intent(context, HelloService.class));
}
Error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.testapp, PID: 26026
java.lang.RuntimeException: Unable to start receiver com.example.testapp.MyReceiver: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.example.testapp/.HelloService }: app is in background uid UidRecord{bee03a7 u0a82 RCVR bg:+1m19s133ms idle change:uncached procs:1 seq(0,0,0)}
at android.app.ActivityThread.handleReceiver(ActivityThread.java:3194)
at android.app.ActivityThread.-wrap17(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1672)
at android.os.Handler.dispatchMessage(Handler.java:106)
The more relevant documentation for your situation is in the Android 8.0 release notes.
The system distinguishes between foreground and background apps. (The definition of background for purposes of service limitations is distinct from the definition used by memory management; an app might be in the background as pertains to memory management, but in the foreground as pertains to its ability to launch services.) 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.
(emphasis added)
So, from the standpoint of starting a background service, a BroadcastReceiver is not in the foreground.
I need a component in my Android app that is best being described as a watchdog, i.e. a function that is executed every 30min +/- 5min and asserts that a certain condition is still met. The watchdog must also be executed after the device has been rebooted without the user having explicitly opened the app thereafter. The same must hold for the app's installation. The watchdog must be scheduled for periodic execution even if the app has not been explicitly opened after installation.
I understand that using WorkManager is the best or "modern" way. Without WorkManager I have to write individual code for different API levels, i.e. use BroadcastReceiver for devices with API level <27 and JobScheduler for higher API levels. WorkManager should abstract those differences away.
But I do not understand where to call WorkManager.getInstance().enqueue( myWatchdogRequest );. Using any of the main activitiy's callbacks (i.e. onCreate and similar) is not the right place, because I must not rely on the activity ever being created.
I expected that besides queuing jobs programmatically there should also be a way how to declare those jobs in the manifest and thereby announce them to the system (similar to the old-fashioned BroadcastReceiver). Actually, I would have the same problem with JobScheduler, if I decided to use that approach.
Where do I enqueue the WorkRequest “globally”?
In the first part, I simply present the solution as snippets of code without much explanation. In the second part, I elaborate on the solution, explain why it is not an exact solution, but the best possible one and point out some errors in Google documentation which led me to the question in the first place.
The Solution
The actual worker that runs every 30 minutes with 10 minutes flexibility:
public class WatchDogWorker extends Worker {
private static final String uniqueWorkName = "my.package.name.watch_dog_worker";
private static final long repeatIntervalMin = 30;
private static final long flexIntervalMin = 10;
public WatchDogWorker( #NonNull Context context, #NonNull WorkerParameters params) {
super( context, params );
}
private static PeriodicWorkRequest getOwnWorkRequest() {
return new PeriodicWorkRequest.Builder(
WatchDogWorker.class, repeatIntervalMin, TimeUnit.MINUTES, flexIntervalMin, TimeUnit.MINUTES
).build();
}
public static void enqueueSelf() {
WorkManager.getInstance().enqueueUniquePeriodicWork( uniqueWorkName, ExistingPeriodicWorkPolicy.KEEP, getOwnWorkRequest() );
}
public Worker.Result doWork() {
// Put the actual code of the watchdog that needs to be run every 30mins here
return Result.SUCCESS;
}
}
Note: a) As this worker needs to be registered for scheduling at two different points of execution (see below) in the same way, I decided that WatchDogWorker should "know" how to enqueue itself. Therefore it provides the static methods getOwnWorkRequest and enqueueSelf. b) The private, static constants are only needed once, but using constants avoids magic numbers in the code and gives a semantic meaning to the numbers.
To enqueue the WatchDogWorker for scheduling after the device has booted, the following broadcast receiver is required:
public class BootCompleteReceiver extends BroadcastReceiver {
public void onReceive( Context context, Intent intent ) {
if( intent.getAction() == null || !intent.getAction().equals( "android.intent.action.BOOT_COMPLETED" ) ) return;
WatchDogWorker.enqueueSelf();
}
}
Essentially, the whole magic is a one-liner and calls WatchDogWorker.enqueueSelf. The broadcast receiver is supposed to be called once after boot. To this end the broadcast receiver must be declared in the AndroidManifest.xml such that the Android system knows about the receiver and calls it upon boot:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="...">
...
<application>
...
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
However, this is not sufficient. If the user has freshly installed the app, we do not want to wait for the next reboot until the watchdog is scheduled for the first time, but we want it to be scheduled as soon as possible. Hence, WatchDogWorker is also enqueued, if the main activity is created.
public class MainActivity extends AppCompatActivity {
...
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Schedule WatchDogWorker (after a fresh install we must not rely on the BootCompleteReceiver)
WatchDogWorker.enqueueSelf();
}
}
Note: This solution might invoke the method WatchDogWorker.enqueueSelf multiple times. However, enqueueSelf internally calls enqueueUniquePeriodicWork with ExistingPeriodicWorkPolicy.KEEP. Hence, subsequent calls to enqueueSelf are a no-op and do no harm.
Caveat: The presented solution is only a 95%-solution. If the user never starts the app after installation, i.e. the activity is never created, the WatchDogWorker is never enqueued and does never run. Even, if the device is eventually rebooted at some future point (but the app has never been started), the "boot complete" intent is never received and the WatchDogWorker is not enqueued neither. There is no work-around for this situation. (See next chapter.)
Additional background information
The first problem that led me to the question was how to enqueue the worker, if the device has been rebooted without relying on the activity to be created. I did know about broadcast receivers and especially about the BOOT_COMPLETED-intent. But according to official Android documentation nearly all broadcast receivers have been radically disabled beginning with Android 8. This measurement was part of Google's attempt to improve the power management. In the past, broadcast receivers have allegedly been abused by many less-skilled developers to do insane things that should have been better done in some other way. (Trivial example: Misuse the AlarmManager and the corresponding broadcast receiver to wake up your app every 500ms, simply to check if there are updates available on your server.) Google's countermeasure was to simply cut-off those broadcast receivers. More precisely, a quote from the docs:
Beginning with Android 8.0 [...], the system imposes [...] restrictions on manifest-declared receivers. [...] you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that don't target your app specifically). You can still use a context-registered receiver when the user is actively using your app.
Two aspects are important: The restriction applies to intents that are implicit. Unfortunately, the BOOT_COMPLETED intent is an implicit intent according to docs. Secondly, this restriction can be overcome, but only programmatically or in other words through some executed code of your activity. Unfortunately again, this is not a workaround if the actual goal is not to rely on the activity being started by the user.
This was the point where I thought I was lost. However, there are some exceptions from the rule above and BOOT_COMPLETED belongs to this exceptions. Surpringly, the correct documentation page is called "Implicit Broadcast Exceptions" and even more surpringly is not very easy to find. Anyway, it says
ACTION_LOCKED_BOOT_COMPLETED, ACTION_BOOT_COMPLETED
Exempted because these broadcasts are only sent only once, at first boot, and many apps need to receive this broadcast to schedule jobs, alarms, and so forth.
This is exactly what is needed here and has been noticed by Google. To sum up: Yes, most implicit broadcast receivers have been abandoned, but not all and BOOT_COMPLETED is one of them. It still works and (hopefully) will work in the future.
The second problem is still open: If the user never reboots the device and never starts the app after installation at least once, the WatchDogServer is never enqueued. (This are the missing 5% of my solution to the question.) There is a ACTION_PACKAGE_ADDED-intent, but it does not help here, because the particular app that has been added never receives its "own" intent.
Anyway, the aforementioned drawback cannot be overcome and it is part of Google anti-malware campaign. (Unfortunately, I lost the link to the reference.) It is a pragmatic solution to hinder malware to silently establish background tasks. After a package has been installed it remains in some kind of "semi-installed" state. (It is called "paused" by Google, but do not confuse it with the paused-state of an activity. Here, this refers to the the state of the whole package.) The package remains in this state until the user has manually started the main activity with the android.intent.action.MAIN-intent from the launcher at least once. As long as the package is in the "paused" state, it does not receive any broadcast intents neither. In this particular case, the BOOT_COMPLETED-intent is not received on the next boot. To sum up: You cannot write an app that only consists of background tasks even if this is the whole purpose of your app. Your app requires an activity that needs to be shown to the user at least once. Otherwise nothing will ever run at all. Coincidentally, due to legal reasons most apps in most countries requires some kind of legal note or data policy anyway, so you can use the activity to statically show that. In the app's description in the Playstore ask the user to start the app (and maybe even read your text) in order to complete the installation.
My app is for now still targeting SDK 25
I'm trying to replace my background services in order to be able to target SDK 26.
In order to do so I now start my IntentService with
ContextCompat.startForegroundService()
If I understand the documentation correctly I then have 5 seconds to call startForeground() on the service.
When doing so it works fine.
But sometimes my service doesn't have any work to do, so it just exits right away (onHandleIntent() doesn't do anything) and yet the app crashes with the following error
AndroidRuntime: FATAL EXCEPTION:
Process: com.myAPPPackage, PID: 3855
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1768)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
What about the 5 seconds delay in that case?
I can see that the service onDestroy() method is called like a few ms after the call to ContextCompat.startForegroundService()
Any idea why the app is crashing like that?
Any idea why the app is crashing like that?
You are not calling startForeground().
But sometimes my service doesn't have any work to do, so it just exits right away
You need to call startForeground() first.
Or have whatever it is that is calling startForegroundService() determine whether there is any work to do first, before calling startForegroundService(). That way, you do not have to bother calling startForegroundService() if there is no need for a foreground service.
If I understand the documentation correctly I then have 5 seconds to call startForeground() on the service.
Yes, but that does not mean that you can skip calling startForeground(). You must call startForeground(). You just have five seconds in which to do so. My guess is that this is to avoid an avenue of abuse for startForegroundService().
When I implemented my widget a couple years ago, I used this advice from the App Widgets page on the development site (still there): "If your App Widget setup process can take several seconds (perhaps while performing web requests) and you require that your process continues, consider starting a Service in the onUpdate() method." I did this and it worked like a charm when built for SDKs up through 25.
It also worked on Android 8.0, but as soon as I targeted 26, the app started crashing in the onUpdate method of the widget provider when issuing the startService call. The error message had to do with trying to start a background service from another background service, and although I tried a few things in the service definition in the manifest I couldn't get it working.
I ended up doing a workaround when I decided that I didn't really need to update the widget from a service, so now just do the update directly in onUpdate. Services and background/foreground issues are something I've not messed with much, but I'd still like to know if there is a way to have kept using a service to update the widget.
in Android O, we have a new background limitations. When you're trying to startService(), you will get IlleagalStateException, so now you should use startForegroundService(), but if you start service by this new method, you will get RemoteServiceException. To avoid this exception you have 5 seconds to make startForeground() after startForegroundService(), to notify user, that you're working in background.
So, where is only one way in Android O:
context.startForegroundService(intent)
And in service onCreate() make something like that:
startForeground(1, new Notification());
UPDATE
So as i assume, some manufacturers have backport on Android N these new background limitations, that's why you can get same exception in Android N.
Without a Minimal, Complete, and Verifiable example, it is difficult to help you. Depending on what you had, you could have:
Switched to JobIntentService, particularly if your old service was an IntentService and you did not mind the possibility of a several-second delay in the work being started
Called startForegroundService() on Context (instead of startService()) to start your service, and in your service call startForeground(), to make your service be a foreground service
Called getForegroundService() on PendingIntent (instead of getService()), and in your service call startForeground(), to make your service be a foreground service
I have an Android app which uses the Awareness API to setup a fence when a headset is plugged in.
I have implemented the AwarenessFence using code much like in the examples at: https://developers.google.com/awareness/android-api/fence-register.
I have a PendingIntent defined as:
PendingIntent.getBroadcast(context, 0, new Intent("my.application.packageFENCE_RECEIVER_ACTION"), 0)
Then in my AndroidManifest.xml file I have
<receiver android:name=".fence.FenceDetector$MyFenceReceiver">
<intent-filter>
<action android:name="my.application.packageFENCE_RECEIVER_ACTION" />
</intent-filter>
This is declared in the Manifest due to the fact that I want to receive broadcasts even when my app is in the background.
This all worked fine on Android 7.0 and below, but when I run this on a Android 8.0 I get the error:
BroadcastQueue: Background execution not allowed: receiving Intent { act=my.application.packageFENCE_RECEIVER_ACTION
I assume this is due to the new restrictions for background execution on Android O.
Can anybody tell me how to register a broadcast receiver which can listen to awareness fence triggers when in the background on a Android device running API 26.
Let me know If there is something which is unclear or if I need to elaborate something.
Thanks in advance
I can't test it on device now, but from all I've read, the limitation is only on implicit broadcasts. That means, if you create a explicit broadcast instead, that's all you need to make it work.
That means instead of this:
// implicit intent matching action
PendingIntent.getBroadcast(context, 0,
new Intent("my.application.packageFENCE_RECEIVER_ACTION"), 0)
you do that:
// explicit intent directly targeting your class
PendingIntent.getBroadcast(context, 0,
new Intent(context, FenceDetector.MyFenceReceiver.class), 0)
I did a little digging around and stumbled upon this blog post by CommonsWare. It states the very problem that you're facing.
From the above post :-
One of the more controversial changes in Android O — for apps with a
sufficiently-high targetSdkVersion — is the effective ban on implicit
broadcasts.
So, according to this, I don't think your problem has anything to do with the Awareness API. Instead, it's because of the new behaviour introduced in Android 8.
Unfortunately, though, there doesn't seem to be viable solution to this as of now. Again, phrasing from the same post :-
If you are receiving system-sent implicit broadcasts (e.g.,
ACTION_PACKAGE_ADDED), keep your targetSdkVersion at 25 or lower,
until we figure out better workarounds that (hopefully) do not involve
polling.
So, hopefully there would be a better solution for 8 in the near future. Meanwhile, you could consider other options or could consider lowering your targetSDK.
Your understanding is very correct.
Apps that target Android 8.0 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest. An implicit broadcast is a broadcast that does not target that app specifically.
Apps can continue to register for explicit broadcasts in their manifests.
Source
If you are only interested in setup a fence when a headset is plugged in. You can use
Note: A number of implicit broadcasts are currently exempted from this limitation. Apps can continue to register receivers for these broadcasts in their manifests, no matter what API level the apps are targeting. For a list of the exempted broadcasts, see Implicit Broadcast Exceptions.
You can resister for ACTION_HEADSET_PLUG in Android Manifest. In onReceive you can either:
start a NotificationManager.startServiceInForeground() so that you can keep doing work in background.
Find a way to duplicate the service's functionality with a scheduled job. If the service is not doing something immediately noticeable to the user, you should generally be able to use a scheduled job instead. Refer Job Scheduler
Defer background work until the application is naturally in the foreground.
I would suggest to use the combination of Job Scheduler with ACTION_HEADSET_PLUG if long running work needs to be done.
Other wise if a short duration work needs to be done which in onReceive
of you can take help from the following:
A BroadcastReceiver that uses goAsync() to flag that it needs more time to finish after onReceive() is complete. This is especially useful if the work you want to complete in your onReceive() is long enough to cause the UI thread to miss a frame (>16ms), making it better suited for a background thread.
you should not start long running background threads from a broadcast receiver. After onReceive(), the system can kill the process at any time to reclaim memory, and in doing so, it terminates the spawned thread running in the process. To avoid this, you should either call goAsync() (if you want a little more time to process the broadcast in a background thread) or schedule a JobService from the receiver using the JobScheduler, so the system knows that the process continues to perform active work.
Source