29

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?

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
mars3142
  • 2,501
  • 4
  • 28
  • 58
  • 1
    Aw, crap. I'll need to try to reproduce this. Assuming that your analysis is correct, your choices are to either make this a foreground service (use Android 8.0's `startForegroundService()` instead of `startService()`) or switch to scheduling a job with `JobScheduler`. – CommonsWare Jun 12 '17 at 15:28
  • Aha! So I'm not the only one suddenly getting crashes on O because of this. – Nathan Osman Sep 26 '17 at 19:38
  • Yes, my solution was to start a JobScheduler (or JobIntentService). It works very well. – mars3142 Sep 26 '17 at 20:05

2 Answers2

55

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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 2
    Use the open source Firebase JobDispatcher might be an option for backward compatibility: https://github.com/firebase/firebase-jobdispatcher-android – M66B Jun 12 '17 at 19:30
  • 1
    @M66B: Yes, as might `JobIntentService` in the v26 support library -- see answer update. – CommonsWare Jun 12 '17 at 20:18
  • Although JobIntentService is certainly interesting, it doesn't support job (work) conditions (like unmetered network available or device is charging). However, JobDispatcher can replace JobScheduler with minor code changes in most cases. At least I had success with it with minor effort. – M66B Jun 13 '17 at 06:21
  • I add that it's not needed if the app is ignoring battery optimizations, in that case it can call startService – greywolf82 Jun 13 '17 at 06:33
  • The "goAsync" option work for about 10 seconds till it gets ANR, right ? It even says so in the docs : "As a general rule, broadcast receivers are allowed to run for up to 10 seconds" (here: https://developer.android.com/reference/android/content/BroadcastReceiver.html#goAsync() ) . I think it should cover most cases. – android developer Jun 18 '17 at 19:47
  • Does using JobIntentService mean the job will try to work right away? – android developer Jun 18 '17 at 19:47
  • 1
    @androiddeveloper: "I think it should cover most cases" -- that would depend on what work the `IntentService` did. "Does using JobIntentService mean the job will try to work right away?" -- it will be scheduled to run right away. However, at boot time, there is a fair bit of memory thrashing, because dozens of apps request this broadcast. The `JobIntentService` documentation indicates that the work may be delayed somewhat in these sorts of conditions. – CommonsWare Jun 18 '17 at 19:55
  • @CommonsWare About JobIntentService , will it try to work right away in normal conditions (meaning not on boot) ? – android developer Jun 19 '17 at 08:14
  • FYI, the RemoteServiceException causes Android to display the app has stopped dialog on the pixel. – black Sep 12 '17 at 10:08
  • In my case, I start service as below: startForegroundService(new Intent(...)). from onStartCommand() of service class I am putting a notification and make this foreground service via startForeground(). Once task is done, I call stopService(). But in many cases I get a force code like below: Context.startForegroundService() did not then call Service.startForeground() Based on some AOSP code analysis, i realised that force close is shown as showing Notification is not completed by framework. – vicky Oct 05 '17 at 23:03
  • // Check to see if the service had been started as foreground, but being // brought down before actually showing a notification. That is not allowed. Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); Not sure how I can I make sure that before stop service call that notification is already posted. Any clue on this ? Note: this happens only when some tasks get finished very quickly. In my case, most of the tasks works fine but only for super quick tasks this is happening – vicky Oct 05 '17 at 23:04
  • 1
    @vicky: I recommend that you ask a separate Stack Overflow question, as it is very difficult to follow what you have in your two comments. – CommonsWare Oct 05 '17 at 23:14
  • Would schedule a WorkManager be another alternative? Using WorkManager seem much easy and straightforward. – Cheok Yan Cheng Jun 02 '18 at 21:15
  • 1
    @CheokYanCheng: Probably, though I have not tried one from an `ACTION_BOOT_COMPLETED` receiver. Also, in general, I'm not sure how well this works from a receiver -- I don't know how they are handling ensuring that your work gets scheduled with `JobScheduler` when you are not in the foreground. – CommonsWare Jun 02 '18 at 21:19
  • 2
    According to https://developer.android.com/about/versions/oreo/background, when you receive `BOOT_COMPLETED` shouldn't your app be put on a whitelist, since it is handling a task like "Receiving a broadcast, such as an SMS/MMS message"? – jpmcosta Jul 28 '18 at 13:41
  • 1
    @jpmcosta: I guess so, though I have not tried it personally. – CommonsWare Jul 28 '18 at 13:54
  • 1
    Thanks for answer. As far as I can tell, that may vary with the device, as I'm having issues with some devices. I will have to test it further. – jpmcosta Jul 29 '18 at 03:59
1

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.

Ryan
  • 1,863
  • 13
  • 20