229

After upgrading my phone to 8.1 Developer Preview my background service no longer starts up properly.

In my long-running service I've implemented a startForeground method to start the ongoing notification which is called in on create.

@TargetApi(Build.VERSION_CODES.O)
private fun startForeground() {
    // Safe call, handled by compat lib.
    val notificationBuilder = NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)

    val notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .build()
    startForeground(101, notification)
}

Error message:

11-28 11:47:53.349 24704-24704/$PACKAGE_NAMEE/AndroidRuntime: FATAL EXCEPTION: main
    Process: $PACKAGE_NAME, PID: 24704
    android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification: Notification(channel=My channel pri=0 contentView=null vibrate=null sound=null defaults=0x0 flags=0x42 color=0x00000000 vis=PRIVATE)
        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)

invalid channel for service notification, apparently my old channel the DEFAULT_CHANNEL_ID is no longer appropriate for API 27 I assume. What would be the proper channel? I've tried to look through the documentation

iknow
  • 4,796
  • 9
  • 19
  • 37
Rawa
  • 10,673
  • 6
  • 33
  • 52

10 Answers10

281

After some tinkering for a while with different solutions i found out that one must create a notification channel in Android 8.1 and above.

private fun startForeground() {
    val channelId =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                createNotificationChannel("my_service", "My Background Service")
            } else {
                // If earlier version channel ID is not used
                // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
                ""
            }

    val notificationBuilder = NotificationCompat.Builder(this, channelId )
    val notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setPriority(PRIORITY_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()
    startForeground(101, notification)
}

@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
    val chan = NotificationChannel(channelId,
            channelName, NotificationManager.IMPORTANCE_NONE)
    chan.lightColor = Color.BLUE
    chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
    val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    service.createNotificationChannel(chan)
    return channelId
}

From my understanding background services are now displayed as normal notifications that the user then can select to not show by deselecting the notification channel.

Update: Also don't forget to add the foreground permission as required Android P:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Rawa
  • 10,673
  • 6
  • 33
  • 52
  • Do we need to make these changes in case of JobIntentService? Or it is handling it internally? – Amrut Apr 05 '18 at 08:56
  • 2
    why not `IMPORTANCE_DEFAULT` instead of `IMPORTANCE_NONE`? – user924 May 08 '18 at 07:53
  • @Sub6Resources well it's not that popular as swift (which is really replaces objective-c fast), kotlin is much older than swift (and so? did it replace java as swift did with objective-c?), but still isn't that popular and its rating on Tiobe index site is very low (swift rating is much higher) – user924 May 08 '18 at 07:59
  • 1
    @user924 Kotlin is actually a newer language than Swift. Kotlin doesn't replace Java, it is just an alternative to Java for Android development. If you try it out, you'll see it is actually quite similar in syntax to Swift. I personally believe it is better than Java, despite what the Tiobe index says (the index is subject to a little bit of non-response bias). It fixes many of the issues that Java has, including the dreaded NullPointerException, verbosity, and several other things. According to the latest Google I/O, 95% of developers who use Kotlin for Android are happy with it. – Sub 6 Resources May 12 '18 at 04:03
  • @Sub6Resources one thing disappoints me, Kotlin can replace Java in Android at some point (Google does great job), but it's bad that we still have to deal with Oracle Java for JVM developing in other areas (not mobile, but desktop/server/embedded apps). But now we have two languages... Yeah I know that Kotlin/Swift are very similar, I don't know who plagiarises the most but Kotlin was released (First appeared 2011) much sooner than Swift (First appeared 2014) – user924 May 12 '18 at 18:41
  • For me, the line `NotificationCompat.Builder(this, channelId )` produces error, since my min sdk is 14 while this constructor overload does not exist yet. – FindOutIslamNow May 27 '18 at 03:39
  • 3
    This should be called from onCreate() of your service – Evgenii Vorobei Jun 24 '18 at 10:14
  • This is generating a notification in my status bar " App name is running Tap for more info" which is not cancelable. When I am giving channel in my notification. Any fix for that? – Fahad Aug 01 '18 at 12:49
  • Unfortunately, this code fails in Android 9.0 (API 28). – CopsOnRoad Sep 25 '18 at 10:43
  • @CopsOnRoad In what way? I'm currently using it my project targeting API 28. – Rawa Sep 25 '18 at 11:24
  • I am probably your well wisher because this will cause crashes (caused in my app too by many Pixel users who updated it to Android 9.0 and gave 1 star ratings). I found the solution of why. Please see my updated answer. – CopsOnRoad Sep 25 '18 at 11:52
  • @CopsOnRoad I'm not sure what you are doing in you app, but I've a Google Pixel XL with 9.0 (And Google Pixel 2 XL) and it is working fine. Also tried with a Oneplus 6 with 9.0. Do you still have a requiresApi annotation? – Rawa Sep 25 '18 at 12:02
  • 2
    @Rawa Well even I am not sure what you are doing with your Foreground service in the app because the Documentation doesn't lie. It clearly states, you will get SecurityException if you try to create foreground service without the permission in the manifest. – CopsOnRoad Sep 25 '18 at 12:18
  • That is correct, this issue was a answer for Android 8.1 though. At that point the API for 9.0 wasn't set and the permission not required. Furthermore, you shouldn't get the same "java.lang.RuntimeException: invalid channel for service notification" runtime error for a permission error. I'll gladly update the response to help others though. – Rawa Sep 25 '18 at 12:26
  • @Rawa How to do not show notification like App is running in the background? – patel135 Nov 12 '18 at 11:41
  • @135 With a foreground service you can't, it needs to have a notification consistently not to be killed. These are new a restrictions from Oreo and newer, to ensure apps are not draining your battery. – Rawa Nov 12 '18 at 13:22
  • @Rawa: sir i got above error in ionic3 can you tell me how to implement above code in ionic3? – Kapil soni Feb 22 '20 at 04:14
  • it won't help for many Huawei devices anyway – user924 Jan 28 '21 at 18:56
159

Java Solution (Android 9.0, API 28)

In your Service class, add this:

@Override
public void onCreate(){
    super.onCreate();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        startMyOwnForeground();
    else
        startForeground(1, new Notification());
}

private void startMyOwnForeground(){
    String NOTIFICATION_CHANNEL_ID = "com.example.simpleapp";
    String channelName = "My Background Service";
    NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
    chan.setLightColor(Color.BLUE);
    chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
    NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    assert manager != null;
    manager.createNotificationChannel(chan);

    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
    Notification notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.drawable.icon_1)
            .setContentTitle("App is running in background")
            .setPriority(NotificationManager.IMPORTANCE_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build();
    startForeground(2, notification);
}

UPDATE: ANDROID 9.0 PIE (API 28)

Add this permission to your AndroidManifest.xml file:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
CopsOnRoad
  • 109,635
  • 30
  • 367
  • 257
  • Is there a reason for using a unique IDs in the two startForeground( ) calls? Can't they be the same here since its the same notification? – Cody Oct 04 '18 at 20:16
  • @CopsOnRoad so there's no need of notification channel for O? – Shruti Oct 16 '18 at 06:09
  • 2
    @Shruti You need to add permission along with the code for Android 9.0. Both are needed. – CopsOnRoad Oct 16 '18 at 06:12
  • @CopsOnRoad Thanks, added that! But `if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)` should be `if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)` cause acc to doc: Starting in Android 8.0 (API level 26), all notifications must be assigned to a channel. – Shruti Oct 16 '18 at 06:37
  • @CopsOnRoad I am getting this exception for API level 26 and above(Oreo and Pie) – Shruti Oct 16 '18 at 06:38
  • 2
    @CopsOnRoad This is the exception 'Fatal Exception: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()' – Shruti Oct 16 '18 at 06:51
  • Could you please post the question separately, I will definitely help you. – CopsOnRoad Oct 16 '18 at 06:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181925/discussion-between-shruti-and-copsonroad). – Shruti Oct 16 '18 at 06:58
  • @SujithManjavana it just saying that `manager` should not be `null` – CopsOnRoad May 14 '19 at 14:27
  • 2
    Is it possible to avoid displaying the notification while the service is running ? – matdev Jun 25 '19 at 09:26
  • sometimes it works without – Amit Vaghela Sep 12 '19 at 12:06
  • @AmitVaghela It can only work if you have `targetSdkVersion` less than `28`. – CopsOnRoad Sep 12 '19 at 15:13
  • It's works, but i have a notification when the app start. How remove it ? – Benoit Canonne Oct 14 '19 at 10:26
  • I have a simple question, let's say I want to do a specific code when my service is running. Where should I put this code? Like getting the location or something like that. After creating the `Notification` ? – Mohamad Mousheimish Apr 17 '20 at 23:07
32

The first answer is great only for those people who know kotlin, for those who still using java here I translate the first answer

 public Notification getNotification() {
        String channel;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            channel = createChannel();
        else {
            channel = "";
        }
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, channel).setSmallIcon(android.R.drawable.ic_menu_mylocation).setContentTitle("snap map fake location");
        Notification notification = mBuilder
                .setPriority(PRIORITY_LOW)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();


        return notification;
    }

    @NonNull
    @TargetApi(26)
    private synchronized String createChannel() {
        NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);

        String name = "snap map fake location ";
        int importance = NotificationManager.IMPORTANCE_LOW;

        NotificationChannel mChannel = new NotificationChannel("snap map channel", name, importance);

        mChannel.enableLights(true);
        mChannel.setLightColor(Color.BLUE);
        if (mNotificationManager != null) {
            mNotificationManager.createNotificationChannel(mChannel);
        } else {
            stopSelf();
        }
        return "snap map channel";
    } 

For android, P don't forget to include this permission

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Hamza
  • 1,887
  • 1
  • 16
  • 35
17

Works properly on Andorid 8.1:

Updated sample (without any deprecated code):

public NotificationBattery(Context context) {
    this.mCtx = context;

    mBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
            .setContentTitle(context.getString(R.string.notification_title_battery))
            .setSmallIcon(R.drawable.ic_launcher)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setChannelId(CHANNEL_ID)
            .setOnlyAlertOnce(true)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setWhen(System.currentTimeMillis() + 500)
            .setGroup(GROUP)
            .setOngoing(true);

    mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.notification_view_battery);

    initBatteryNotificationIntent();

    mBuilder.setContent(mRemoteViews);

    mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

    if (AesPrefs.getBooleanRes(R.string.SHOW_BATTERY_NOTIFICATION, true)) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.notification_title_battery),
                    NotificationManager.IMPORTANCE_DEFAULT);
            channel.setShowBadge(false);
            channel.setSound(null, null);
            mNotificationManager.createNotificationChannel(channel);
        }
    } else {
        mNotificationManager.cancel(Const.NOTIFICATION_CLIPBOARD);
    }
}

Old snipped (it's a different app - not related to the code above):

@Override
public int onStartCommand(Intent intent, int flags, final int startId) {
    Log.d(TAG, "onStartCommand");

    String CHANNEL_ONE_ID = "com.kjtech.app.N1";
    String CHANNEL_ONE_NAME = "Channel One";
    NotificationChannel notificationChannel = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,
                CHANNEL_ONE_NAME, IMPORTANCE_HIGH);
        notificationChannel.enableLights(true);
        notificationChannel.setLightColor(Color.RED);
        notificationChannel.setShowBadge(true);
        notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.createNotificationChannel(notificationChannel);
    }

    Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    Notification notification = new Notification.Builder(getApplicationContext())
            .setChannelId(CHANNEL_ONE_ID)
            .setContentTitle(getString(R.string.obd_service_notification_title))
            .setContentText(getString(R.string.service_notification_content))
            .setSmallIcon(R.mipmap.ic_launcher)
            .setLargeIcon(icon)
            .build();

    Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    notification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);

    startForeground(START_FOREGROUND_ID, notification);

    return START_STICKY;
}
Mete
  • 4,855
  • 4
  • 26
  • 34
Martin Pfeffer
  • 11,627
  • 8
  • 54
  • 64
8

In my case, it's because we tried to post a notification without specifying the NotificationChannel:

public static final String NOTIFICATION_CHANNEL_ID_SERVICE = "com.mypackage.service";
public static final String NOTIFICATION_CHANNEL_ID_TASK = "com.mypackage.download_info";

public void initChannel(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_SERVICE, "App Service", NotificationManager.IMPORTANCE_DEFAULT));
        nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_INFO, "Download Info", NotificationManager.IMPORTANCE_DEFAULT));
    }
}

The best place to put above code is in onCreate() method in the Application class, so that we just need to declare it once for all:

public class App extends Application {

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

After we set this up, we can use notification with the channelId we just specified:

Intent i = new Intent(this, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID_INFO);
            .setContentIntent(pi)
            .setWhen(System.currentTimeMillis())
            .setContentTitle("VirtualBox.exe")
            .setContentText("Download completed")
            .setSmallIcon(R.mipmap.ic_launcher);

Then, we can use it to post a notification:

int notifId = 45;
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.notify(notifId, builder.build());

If you want to use it as foreground service's notification:

startForeground(notifId, builder.build());
Anggrayudi H
  • 13,618
  • 11
  • 44
  • 76
6

Thanks to @CopsOnRoad, his solution was a big help but only works for SDK 26 and higher. My app targets 24 and higher.

To keep Android Studio from complaining you need a conditional directly around the notification. It is not smart enough to know the code is in a method conditional to VERSION_CODE.O.

@Override
public void onCreate(){
    super.onCreate();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        startMyOwnForeground();
    else
        startForeground(1, new Notification());
}

private void startMyOwnForeground(){

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

        String NOTIFICATION_CHANNEL_ID = "com.example.simpleapp";
        String channelName = "My Background Service";
        NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        assert manager != null;
        manager.createNotificationChannel(chan);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
        Notification notification = notificationBuilder.setOngoing(true)
                .setSmallIcon(AppSpecific.SMALL_ICON)
                .setContentTitle("App is running in background")
                .setPriority(NotificationManager.IMPORTANCE_MIN)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();
        startForeground(2, notification);
    }
}
Jimale Abdi
  • 1,855
  • 1
  • 16
  • 27
MobileMateo
  • 354
  • 4
  • 4
3

This worked for me. In my service class, I created the notification channel for android 8.1 as below:

public class Service extends Service {

    public static final String NOTIFICATION_CHANNEL_ID_SERVICE = "com.package.MyService";
    public static final String NOTIFICATION_CHANNEL_ID_INFO = "com.package.download_info";

    @Override
    public void onCreate() {

        super.onCreate();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_SERVICE, "App Service", NotificationManager.IMPORTANCE_DEFAULT));
            nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_INFO, "Download Info", NotificationManager.IMPORTANCE_DEFAULT));
        } else {
            Notification notification = new Notification();
            startForeground(1, notification);
        }
    }
}

Note: Create the channel where you are creating the Notification for Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

Mihai Chelaru
  • 5,844
  • 14
  • 34
  • 43
Arjun
  • 893
  • 8
  • 19
2

Alternative answer: if it's a Huawei device and you have implemented requirements needed for Oreo 8 Android and there are still issues only with Huawei devices than it's only device issue, you can read https://dontkillmyapp.com/huawei

user924
  • 3,322
  • 2
  • 26
  • 60
0

I had the same issue. The problem occurred when I used same channel id and notification id for two apps. So try with a unique notification id and channel id.

Vinil Chandran
  • 1,543
  • 1
  • 17
  • 31
-1

Here is my solution

private static final int NOTIFICATION_ID = 200;
private static final String CHANNEL_ID = "myChannel";
private static final String CHANNEL_NAME = "myChannelName";

private void startForeground() {

    final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
            getApplicationContext(), CHANNEL_ID);

    Notification notification;



        notification = mBuilder.setTicker(getString(R.string.app_name)).setWhen(0)
                .setOngoing(true)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("Send SMS gateway is running background")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setShowWhen(true)
                .build();

        NotificationManager notificationManager = (NotificationManager) getApplication().getSystemService(Context.NOTIFICATION_SERVICE);

        //All notifications should go through NotificationChannel on Android 26 & above
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    CHANNEL_NAME,
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);

        }
        notificationManager.notify(NOTIFICATION_ID, notification);

    }

Hope it will help :)