FirebaseMessagingService Crashes on Android O due to background execution limits

11,536

Solution 1

@KaMyLL is right. I had the same issue with our app and could solve it by replacing the IntentService (which we have started within onTokenRefresh()) with an JobIntentService.

Because I found the JobScheduler and JobIntentService docs a bit confusing, I would like to some everything up with some code snippets. I hope this makes everything clear to everyone having this issue.

What is causing this issue?

Due to the new Background Execution Limits of Android 8, you should not start background services anymore when the app could be in background:

While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.

And also:

In many cases, your app can replace background services with JobScheduler jobs.

So for Android 7.x and below, using startService() when the app is in background is (as far as I know) no problem. But in Android 8, this results in a crash. In consequence, you should use a JobScheduler now. The behavioral difference between JobScheduler and an IntentService is that an IntentService is executed immediately. On the other hand, a job enqueued to a JobScheduler is not guaranteed to be executed immediately. The Android OS will determine when there is a good point of time to do so in order to be more energy efficient. So there might be a delay. And I have no idea so far how long this could take. So one solution could be to check the OS version and branch your code using if-else. Fortunately, the support library helps us to solve this in a more elegant way without duplicating any code: JobIntentService, which basically does this for you under the hood.

How to reproduce the issue?

The first quote above states that the app still "has a window of several minutes in which it is still allowed to create and use services.", so in order to reproduce and debug the issue (with the example of onTokenRefresh() in Firebase), you could set a breakpoint before your start your service with startService(). Close the app and wait there for 5-10 minutes. Continue the execution and you will see the IllegalStateException from this question. Being able to reproduce the issue as fundamental to make sure that our fixes really solve the problem.

How to migrate my IntenService to JobIntentService?

I use FirebaseInstanceIdService.onTokenRefresh() as an example:

a) Add the BIND_JOB_SERVICE permission to your service:

<service android:name=".fcm.FcmRegistrationJobIntentService"
   android:exported="false"
   android:permission="android.permission.BIND_JOB_SERVICE"/>

b) Instead of extending from IntentService, simply extend from android.support.v4.app.JobIntentService, rename the onHandleIntent(Intent) method to onHandleWork(Intent), and add a enqueueWork(Context, Intent) convenient function:

public class FcmRegistrationJobIntentService extends JobIntentService 
{
    // Unique job ID for this service.
    static final int JOB_ID = 42;

    // Convenience method for enqueuing work in to this service.
    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, FcmRegistrationJobIntentService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // the code from IntentService.onHandleIntent() ...
    }
}

c) Start the job using the enqueueWork() convenient function:

public class ComfyFirebaseInstanceIdService extends FirebaseInstanceIdService {
    @Override
    public void onTokenRefresh() {
        Intent intent = new Intent(this, FcmRegistrationJobIntentService.class);
        // startService(intent);
        FcmRegistrationJobIntentService.enqueueWork(this, intent);
    }
}

I hope this example is helpful. At least after following these steps, I was not able to reproduce the issue on my Android 8 device anymore, and it continues to work an my Android 7 device.

Update

as FirebaseInstanceIdService deprecated we should remove this from the code, and use onNewToken from FirebaseMessagingService instead.

Solution 2

I've done some research about it and the best option is to transform IntentService into JobIntentService available in app compat library. It would behave like IntentService on all pre-Oreo devices. On Android 8 and above it will enqueue job to android system JobScheduler. This job by default have set deadline parameter to 0, so theoretically it should fire as fast as possible.

Share:
11,536
cottonBallPaws
Author by

cottonBallPaws

Updated on June 06, 2022

Comments

  • cottonBallPaws
    cottonBallPaws almost 2 years

    Our app is crashing on Android O due to the new background execution limits. We are on Firebase version 10.2.1, which is the one that added Android O support.

    Seems like an issue with Firebase? Or is there some change needed to support this on our end?

    java.lang.IllegalStateException: Not allowed to start service Intent { act=com.google.firebase.INSTANCE_ID_EVENT pkg=my.package.name cmp=my.package.name/my.package.name.MyFcmIdService (has extras) }: app is in background uid UidRecord{30558fa u0a327 RCVR idle procs:1 seq(0,0,0)}
    at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1505)
    at android.app.ContextImpl.startService(ContextImpl.java:1461)
    at android.content.ContextWrapper.startService(ContextWrapper.java:644)
    at android.support.v4.content.WakefulBroadcastReceiver.startWakefulService(WakefulBroadcastReceiver.java:99)
    at com.google.firebase.iid.zzg.b(zzg.java:9)
    at com.google.firebase.iid.zzg.a(zzg.java:72)
    at com.google.firebase.iid.zzg.a(zzg.java:2)
    at com.google.firebase.iid.FirebaseInstanceIdService.a(FirebaseInstanceIdService.java:23)
    at com.google.firebase.iid.FirebaseInstanceIdService.a(FirebaseInstanceIdService.java:34)
    at com.google.firebase.iid.FirebaseInstanceId.<init>(FirebaseInstanceId.java:31)
    at com.google.firebase.iid.FirebaseInstanceId.getInstance(FirebaseInstanceId.java:47)
    at com.google.firebase.iid.FirebaseInstanceId.a(FirebaseInstanceId.java:4)
    at com.google.firebase.iid.FirebaseInstanceIdService.a(FirebaseInstanceIdService.java:19)
    at com.google.firebase.iid.FirebaseInstanceIdService.b(FirebaseInstanceIdService.java:35)
    at com.google.firebase.iid.zzb$zza$1.run(zzb.java:24)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
    at java.lang.Thread.run(Thread.java:764)
    

    Update Upgrading to 11.4.2 resolves this issue.