升级到 Android 8.1 后 startForeground 失败
Posted
技术标签:
【中文标题】升级到 Android 8.1 后 startForeground 失败【英文标题】:startForeground fail after upgrade to Android 8.1 【发布时间】:2018-05-11 22:04:52 【问题描述】:将手机升级到 8.1 Developer Preview 后,我的后台服务无法正常启动。
在我长期运行的服务中,我实现了一个 startForeground
方法来启动在创建时调用的持续通知。
@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)
错误信息:
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
,显然我的旧频道DEFAULT_CHANNEL_ID
不再适合我认为的 API 27。什么是合适的渠道?我试图查看文档
【问题讨论】:
This answer 是我的解决方案。 【参考方案1】:在对不同的解决方案进行了一段时间的修补后,我发现必须在 Android 8.1 及更高版本中创建通知通道。
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
据我了解,后台服务现在显示为普通通知,然后用户可以通过取消选择通知渠道来选择不显示。
更新: 另外别忘了根据需要添加Android P的前台权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
【讨论】:
在 JobIntentService 的情况下我们是否需要进行这些更改?还是内部处理? 为什么不用IMPORTANCE_DEFAULT
而不是IMPORTANCE_NONE
?
@user924 Kotlin 实际上是比 Swift 更新的语言。 Kotlin 并没有取代 Java,它只是用于 Android 开发的 Java 的替代品。如果你尝试一下,你会发现它的语法实际上与 Swift 非常相似。我个人认为它比 Java 好,尽管 Tiobe 指数是这么说的(该指数有一点不回应的偏见)。它修复了 Java 的许多问题,包括可怕的 NullPointerException、冗长和其他一些问题。根据最新的 Google I/O,使用 Kotlin for Android 的开发者中有 95% 对此感到满意。
这应该从你的服务的 onCreate() 调用
@Rawa 好吧,即使我不确定您在应用程序中使用前台服务做什么,因为文档不会说谎。它明确指出,如果您尝试在未经清单中的许可的情况下创建前台服务,您将收到 SecurityException。【参考方案2】:
Java 解决方案(Android 9.0,API 28)
在您的 Service
类中,添加以下内容:
@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);
更新:ANDROID 9.0 PIE (API 28)
将此权限添加到您的AndroidManifest.xml
文件中:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
【讨论】:
是否有理由在两个 startForeground( ) 调用中使用唯一 ID?由于相同的通知,它们在这里不能相同吗? @CopsOnRoad 所以O不需要通知渠道? @Shruti 您需要添加权限以及 Android 9.0 的代码。两者都需要。 @CopsOnRoad 这是异常'致命异常:android.app.RemoteServiceException:Context.startForegroundService() 没有调用 Service.startForeground()' 是否可以避免在服务运行时显示通知?【参考方案3】:第一个答案只适合懂kotlin的人,对于还在用java的人我这里翻译一下第一个答案
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";
对于android,P不要忘记包含这个权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
【讨论】:
感谢您将代码翻译成 Java。对 Java 项目有很大帮助! 非常感谢。此代码适用于 android 8.1。但不适用于 android 11。有任何更新吗?请.....【参考方案4】:在 Andorid 8.1 上正常工作:
更新示例(没有任何不推荐使用的代码):
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);
旧截图(这是一个不同的应用程序 - 不与上面的代码相关):
@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;
【讨论】:
以上部分代码现已弃用,您可以通过将Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...
更改为Notification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
来解决此问题
@ban-geoengineering 你是绝对正确的......我添加了新的示例代码。谢谢。
为什么PRIORITY_MAX
什么更好用?【参考方案5】:
在我的情况下,这是因为我们试图在未指定 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));
上述代码最好放在Application
类的onCreate()
方法中,这样我们只需声明一次即可:
public class App extends Application
@Override
public void onCreate()
super.onCreate();
initChannel();
设置完成后,我们可以使用刚刚指定的channelId
通知:
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);
然后,我们可以使用它来发布通知:
int notifId = 45;
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.notify(notifId, builder.build());
如果你想将其用作前台服务的通知:
startForeground(notifId, builder.build());
【讨论】:
常量 NOTIFICATION_CHANNEL_ID_TASK(第 2 行)应该是 NOTIFICATION_CHANNEL_ID_INFO 吗? @Timores,没有。你可以用你自己的常量替换它。【参考方案6】:感谢@CopsOnRoad,他的解决方案帮了大忙,但仅适用于 SDK 26岁及以上。我的应用面向 24 岁及以上。
为了防止 Android Studio 抱怨,您需要直接在通知周围添加一个条件。知道代码在以 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);
【讨论】:
能否请您澄清一下您在此代码中所做的更改,我没听懂。 版本 8.0 和 Android Pie 完美运行。但是为什么我们只需要 8.1 版本的通知通道呢?【参考方案7】:这对我有用。在我的服务类中,我为 android 8.1 创建了通知通道,如下所示:
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);
注意:创建要为Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
创建通知的频道
【讨论】:
【参考方案8】:替代答案:如果它是华为设备并且您已经实现了 Oreo 8 Android 所需的要求,并且仍然存在仅与华为设备有关的问题而不是唯一的设备问题,您可以阅读https://dontkillmyapp.com/huawei
【讨论】:
【参考方案9】:我遇到了同样的问题。当我对两个应用程序使用相同的频道 ID 和通知 ID 时,就会出现问题。因此,请尝试使用唯一的通知 ID 和频道 ID。
【讨论】:
【参考方案10】:这是我的解决方案
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);
希望它会有所帮助:)
【讨论】:
请花一些时间解释您的解决方案的基本原理。以上是关于升级到 Android 8.1 后 startForeground 失败的主要内容,如果未能解决你的问题,请参考以下文章
将解决方案升级到 Windows 8.1 后出现神秘的构建错误
xcodebuild:使用升级到 XCode 8.1 后出现“需要代码签名”错误
JAVA升级导致Android编译时Jack server出错
Alamofire 库 request.swift 文件中发生错误 - 更新到 xcode 8.1 后“任务使用不明确”