startForeground fail after upgrade to Android 8.1
Andrew Mclaughlin
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
11 Answers
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 // (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" /> 19 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" /> 14 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" /> 2 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;
} 3 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()); 2 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); }
} 2 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
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
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.
this maybe old but, incase someone had the same situation as I did. for some reason, on Android 11 OnePlus Nord
Notification.Builder().Build()crashes,
NotificationCompat.Builder().Build()works fine. Consider migrating to androidx.core.app.NotificationCompat.
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 :)
1