升级到Android 8.1后,startForeground失败

268

将我的手机升级到8.1开发者预览版后,我的后台服务不再正常启动。

在我的长时间运行的服务中,我已经实现了一个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)

无效的服务通知频道,显然我的旧频道DEFAULT_CHANNEL_ID在API 27上不再适用。哪一个是正确的频道?我尝试查阅文档。


1
这个答案是我的解决方案。 - Ghasem
13个回答

337

经过一段时间的尝试不同的解决方案后,我发现在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" />

2
为什么不使用IMPORTANCE_DEFAULT而是使用IMPORTANCE_NONE - user924
2
Kotlin实际上比Swift更新。 Kotlin不会取代Java,它只是Android开发中的一种替代选择。如果你尝试一下,你会发现它在语法上与Swift非常相似。我个人认为它比Java更好,尽管Tiobe指数显示相反(该指数存在一定的非响应偏差)。它解决了Java存在的许多问题,包括可怕的NullPointerException、冗长和其他几个问题。根据最新的Google I/O,95%使用Kotlin进行Android开发的开发人员对其感到满意。 - Sub 6 Resources
4
这应该从您的服务的onCreate()中调用。 - Evgenii Vorobei
不幸的是,该代码在Android 9.0(API 28)中失败了。 - CopsOnRoad
2
@Rawa,我也不确定你在应用程序中使用前台服务的目的是什么,因为文档是不会说谎的。它明确指出,如果您尝试在清单中没有权限的情况下创建前台服务,您将会收到SecurityException异常。 - CopsOnRoad
显示剩余14条评论

186

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" />

2
@Shruti 你需要在代码中添加Android 9.0的权限。两者都是必需的。 - CopsOnRoad
2
@CopsOnRoad 这是一个异常 'Fatal Exception: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()'。 - Shruti
2
在服务运行时是否有可能避免显示通知? - matdev
有时候它可以在没有<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />的情况下工作。 - Amit Vaghela
它能工作,但是应用程序启动时会有一个通知。如何移除它? - Benoit Canonne
显示剩余9条评论

39

第一个答案对于那些懂 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" />

1
感谢将代码翻译成Java。这对于Java项目来说是一个很大的帮助! - Ray Li
非常感谢。这段代码在Android 8.1上可以运行,但在Android 11上无法运行。是否有任何更新?拜托了..... - John Smith

19

在Android 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;
}

2
以上代码的一部分现已过时,您可以通过将 Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)... 更改为 Notification.Builder(getApplicationContext(), CHANNEL_ONE_ID)... 来克服这个问题。 - ban-geoengineering
1
@ban-geoengineering 你说得完全正确... 我添加了新的示例代码。谢谢。 - Martin Pfeffer
为什么要使用PRIORITY_MAX,有更好的选择吗? - user924

9
在我的情况下,这是因为我们尝试发布一个通知,但没有指定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());

2
常量 NOTIFICATION_CHANNEL_ID_TASK(第二行)应该改为 NOTIFICATION_CHANNEL_ID_INFO 吗? - Timores
@Timores,不行。你可以用自己的常量替换它。 - Anggrayudi H

8

感谢@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);
    }
}

1
请问您能否澄清一下在这段代码中您所做的更改,我没有理解到。 - CopsOnRoad
1
版本8.0和Android Pie完美运行。但是为什么我们只需要在8.1版本中使用通知渠道呢? - Thamarai T
解释为什么我们必须创建通知。 - Jeff Bootsholz

3

这对我很有帮助。在我的服务类中,我按照以下方式为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 通知时,请创建该通道。

2

另一种答案:如果它是华为设备,并且您已经实现了 Oreo 8 Android 所需的要求,但仍然存在与华为设备相关的问题,则只是设备问题。您可以阅读https://dontkillmyapp.com/huawei


0

这可能有点过时了,但如果有人遇到了和我一样的情况。 由于某种原因,在Android 11 OnePlus Nord上

Notification.Builder().Build()

崩溃

NotificationCompat.Builder().Build()

运行良好。 考虑迁移到androidx.core.app.NotificationCompat。


0
如果您正在使用VPN库,这些代码将会有所帮助,我将其放置在onCreate(savedInstanceState: Bundle?)中。
 NotificationChannelManager.createNotificationChannelIfNeeded(
            activity,
            channelName = "Chanel Name",
            channelDescription = "Channel description"
        )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接