Context.startForegroundService() did not then call

2020-06-03 06:49发布

问题:

I have hundred of crashes reported by my users and I still can't find a fix for it. These crashes are coming from Android 8 (Samsung, Huawei, Google).

I am getting these two crashes:

Fatal Exception: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1881)
       at android.os.Handler.dispatchMessage(Handler.java:105)
       at android.os.Looper.loop(Looper.java:164)
       at android.app.ActivityThread.main(ActivityThread.java:6938)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

and the other one:

Fatal Exception: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2104)
       at android.os.Handler.dispatchMessage(Handler.java:108)
       at android.os.Looper.loop(Looper.java:166)
       at android.app.ActivityThread.main(ActivityThread.java:7428)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

I assume these crashes are the same, but as you can see the stack trace shows different line of code.

The problem is that I can't reproduce it, everything works fine on my devices and my emulator. However, I (somehow) reproduced by creating a service without calling the startForeground() within the Service class.

I'm unable to "catch" the exception, because it comes from system-level right after 5 seconds when the service was created.

What have I done is that I have created a method which creates a sticky notification and calling the startForeground method (my Service class):

private void startWithNotification() {
     Resources res = getResources();
     String title = res.getString(R.string.application_name);

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
         createChannels();
     }

     NotificationCompat.Builder builder =  new NotificationCompat.Builder(context, ANDROID_CHANNEL_ID)
             .setContentTitle(title)
             .setChannelId(ANDROID_CHANNEL_ID)
             .setCategory(NotificationCompat.CATEGORY_SERVICE)
             .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
             .setPriority(NotificationCompat.PRIORITY_DEFAULT)
             .setOngoing(true)
             .setAutoCancel(false)
             .setSmallIcon(R.drawable.ic_siluette)
             .setColor(ContextCompat.getColor(this, R.color.colorPrimary))
             .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.application_icon));

     startForeground(NOTIFICATION_APP, builder.build());
 }

 private void createChannels() {
     // create android channel
     NotificationChannel androidChannel = new NotificationChannel(ANDROID_CHANNEL_ID, ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
     // Sets whether notifications posted to this channel should display notification lights
     androidChannel.enableLights(true);
     // Sets whether notification posted to this channel should vibrate.
     androidChannel.enableVibration(true);
     // Sets the notification light color for notifications posted to this channel
     androidChannel.setLightColor(Color.GREEN);
     // Sets whether notifications posted to this channel appear on the lockscreen or not
        androidChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);

     NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

     nm.createNotificationChannel(androidChannel);
 }

This method is getting called from different Service life-cycle-events:

onCreate()
onStartCommand()
stopService()
onDestroy()

I am calling the method within these events, because people said that the Service might not being created and it's automatically destroyed.

The service gets started when an incoming or an outgoing call is made via BroadcastReceiver:

public class IncomingOutgoingCallReceiver extends BroadcastReceiver {
   private void callAppService(Context context, int callType) {
        Intent intent = new Intent(context, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putInt(CALL_TYPE, callType);
        intent.putExtras(bundle);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent);
        }
        else {
            context.startService(intent);
        }
    }

    private void onCallEnd(Context context) {
       context.stopService(new Intent(context, MyService.class));
    }
}

The Service class:

public class MyService extends Service {

    private void handleIntent(Intent intent) {
        // Use intent data and do work

        if (canStartService(intent)) {
            return;
        }
    }

    private boolean canStartService(Intent intent) {
        // multiple checks
        // if (intent bundle contains ... ) return false;
        // if (phone number contains .... ) return false;

        return true;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        startWithNotification();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        handleIntent(intent);
        startWithNotification();

        return START_NOT_STICKY;
    }

    private void startWithNotification() {
        // Contains the code from above (didn't put here because of space)
    }

    @Override
    public boolean stopService(Intent name) {
        startWithNotification();

        return super.stopService(name);
    }

    // Can be called from different Views which are attached to the WindowManager (user interacting with the UI)
    public void stopService() {
        startWithNotification();

        stopForeground(true);
        stopSelf();
    }

    @Override
    public void onDestroy() {
        startWithNotification();

        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}