Ошибка startForeground после обновления до Android 8.1

193

После обновления телефона до 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)

неверный канал для уведомления о сервисе , очевидно мой старый канал DEFAULT_CHANNEL_ID больше не подходит для API 27, я полагаю. Какой будет правильный канал? Я пытался просмотреть документацию

Рава
источник
1
Этот ответ был моим решением.
Алекс Джолиг

Ответы:

231

Немного поработав с различными решениями, я обнаружил, что необходимо создать канал уведомлений в 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? Или это внутренне?
Amrut
1
почему не IMPORTANCE_DEFAULTвместо IMPORTANCE_NONE?
user924
1
@ user924 Kotlin на самом деле более новый язык, чем Swift. Kotlin не заменяет Java, это просто альтернатива Java для разработки под Android. Если вы попробуете это, вы увидите, что он на самом деле очень похож по синтаксису на Swift. Я лично считаю, что это лучше, чем Java, несмотря на то, что говорит индекс Тиобе (индекс подвержен небольшому отклонению от отсутствия ответа). Он устраняет многие проблемы, которые есть у Java, в том числе страшное исключение NullPointerException, многословность и ряд других вещей. Согласно последним данным ввода-вывода Google, 95% разработчиков, использующих Kotlin для Android, довольны им.
Sub 6 Resources
3
Это должно вызываться из onCreate () вашего сервиса
Евгений Воробей
1
@Rawa Ну, даже я не уверен, что вы делаете со своим сервисом Foreground в приложении, потому что документация не лжет. В нем четко указано, что вы получите SecurityException, если попытаетесь создать службу переднего плана без разрешения в манифесте.
CopsOnRoad
134

Решение 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" />
CopsOnRoad
источник
Есть ли причина для использования уникальных идентификаторов в двух вызовах startForeground ()? Разве они не могут быть одинаковыми здесь, так как это одно и то же уведомление?
Коди
@ CopsOnRoad, поэтому нет необходимости в канале уведомлений для O?
Шрути
2
@ Шрути Вам нужно добавить разрешение вместе с кодом для Android 9.0. Оба необходимы.
CopsOnRoad
1
@CopsOnRoad Это исключение «Неустранимое исключение: android.app.RemoteServiceException: Context.startForegroundService () не вызывало Service.startForeground ()»
Шрути
2
Можно ли избежать отображения уведомления во время работы службы?
Matdev
29

Первый ответ хорош только для тех, кто знает 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!
Рэй Ли
17

Работает правильно на Андорид 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);
    }
}

Old snipped (это другое приложение - не относится к коду выше):

@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 Вы абсолютно правы ... Я добавил новый пример кода. Спасибо.
Мартин Пфеффер
почему PRIORITY_MAXчто лучше использовать?
user924
7

В моем случае это потому, что мы пытались опубликовать уведомление без указания 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));
    }
}

Лучшее место для кода выше - onCreate()метод в Applicationклассе, так что нам просто нужно объявить его раз и навсегда:

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());
Ангграюди Н
источник
1
Должна ли константа NOTIFICATION_CHANNEL_ID_TASK (2-я строка) быть NOTIFICATION_CHANNEL_ID_INFO?
Тимор
@ Тиморес, нет. Вы можете заменить его своей собственной константой.
Anggrayudi H
4

Благодаря @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);
    }
}
MobileMateo
источник
Не могли бы вы уточнить, какие изменения вы внесли в этот код, я этого не получил.
CopsOnRoad
Версии 8.0 и Android Pie работают отлично. Но зачем нужен канал уведомлений только для версии 8.1?
Thamarai T
2

Это сработало для меня. В своем классе обслуживания я создал канал уведомлений для 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

Арджун
источник
-1

Вот мое решение

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);

    }

Надеюсь, это поможет :)

Шахриар Насим Нафи
источник
1
Пожалуйста, найдите время, чтобы объяснить обоснование вашего решения.
страя