startForeground échoue après la mise à niveau vers Android 8.1

193

Après la mise à niveau de mon téléphone vers la version 8.1 Developer Preview, mon service d'arrière-plan ne démarre plus correctement.

Dans mon service de longue date, j'ai implémenté une méthode startForeground pour démarrer la notification en cours qui est appelée lors de la création.

    @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)
}

Message d'erreur:

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)

canal invalide pour la notification de service , apparemment mon ancien canal le DEFAULT_CHANNEL_ID n'est plus approprié pour l'API 27 je suppose. Quel serait le canal approprié? J'ai essayé de parcourir la documentation

Rawa
la source
1
Cette réponse était ma solution.
Alex Jolig

Réponses:

231

Après avoir bricolé pendant un certain temps avec différentes solutions, j'ai découvert qu'il fallait créer un canal de notification sous Android 8.1 et supérieur.

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
}

D'après ce que j'ai compris, les services d'arrière-plan sont maintenant affichés comme des notifications normales que l'utilisateur peut alors choisir de ne pas afficher en désélectionnant le canal de notification.

Mise à jour : n'oubliez pas non plus d'ajouter l'autorisation de premier plan comme requis Android P:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Rawa
la source
Avons-nous besoin de faire ces changements dans le cas de JobIntentService? Ou il le gère en interne?
Amrut le
1
pourquoi pas IMPORTANCE_DEFAULTau lieu de IMPORTANCE_NONE?
user924
1
@ user924 Kotlin est en fait une langue plus récente que Swift. Kotlin ne remplace pas Java, c'est juste une alternative à Java pour le développement Android. Si vous l'essayez, vous verrez que sa syntaxe est en fait assez similaire à celle de Swift. Je pense personnellement que c'est mieux que Java, malgré ce que dit l'indice Tiobe (l'indice est sujet à un petit biais de non-réponse). Il corrige de nombreux problèmes rencontrés par Java, y compris la redoutable NullPointerException, la verbosité et plusieurs autres choses. Selon le dernier Google I / O, 95% des développeurs qui utilisent Kotlin pour Android en sont satisfaits.
Sub 6 Resources
3
Cela devrait être appelé depuis onCreate () de votre service
Evgenii Vorobei
1
@Rawa Eh bien même je ne suis pas sûr de ce que vous faites avec votre service Foreground dans l'application car la documentation ne ment pas. Il indique clairement que vous obtiendrez SecurityException si vous essayez de créer un service de premier plan sans l'autorisation du manifeste.
CopsOnRoad
134

Solution Java (Android 9.0, API 28)

Dans votre Serviceclasse, ajoutez ceci:

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

MISE À JOUR: ANDROID 9.0 PIE (API 28)

Ajoutez cette autorisation à votre AndroidManifest.xmlfichier:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
CopsOnRoad
la source
Y a-t-il une raison d'utiliser un identifiant unique dans les deux appels startForeground ()? Ne peuvent-ils pas être les mêmes ici puisque c'est la même notification?
Cody
@CopsOnRoad donc il n'y a pas besoin de canal de notification pour O?
Shruti
2
@Shruti Vous devez ajouter une autorisation avec le code pour Android 9.0. Les deux sont nécessaires.
CopsOnRoad
1
@CopsOnRoad Il s'agit de l'exception 'Fatal Exception: android.app.RemoteServiceException: Context.startForegroundService () n'a pas alors appelé Service.startForeground ()'
Shruti
2
Est-il possible d'éviter d'afficher la notification pendant l'exécution du service?
matdev
29

La première réponse n'est géniale que pour les personnes qui connaissent kotlin, pour ceux qui utilisent encore java ici, je traduis la première réponse

 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";
    } 

Pour Android, n'oubliez pas d'inclure cette autorisation

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Hamza
la source
Merci d'avoir traduit le code en Java. C'est une grande aide pour les projets Java!
Ray Li
17

Fonctionne correctement sur Andorid 8.1:

Exemple mis à jour (sans code obsolète):

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

Ancien extrait (c'est une application différente - non liée au code ci-dessus):

@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;
}
Martin Pfeffer
la source
2
Une partie du code ci-dessus est désormais obsolète, ce que vous pouvez surmonter en passant Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...àNotification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
ban-
1
@ ban-geoengineering vous avez absolument raison ... J'ai ajouté un nouvel exemple de code. Merci.
Martin Pfeffer
pourquoi PRIORITY_MAXquoi de mieux à utiliser?
user924
7

Dans mon cas, c'est parce que nous avons essayé de publier une notification sans spécifier le 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));
    }
}

Le meilleur endroit pour mettre le code ci-dessus est dans onCreate()method dans la Applicationclasse, de sorte que nous ayons juste besoin de le déclarer une fois pour toutes:

public class App extends Application {

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

Après avoir configuré cela, nous pouvons utiliser la notification avec le que channelIdnous venons de spécifier:

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

Ensuite, nous pouvons l'utiliser pour publier une notification:

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

Si vous souhaitez l'utiliser comme notification du service de premier plan:

startForeground(notifId, builder.build());
Anggrayudi H
la source
1
La constante NOTIFICATION_CHANNEL_ID_TASK (2e ligne) doit-elle être NOTIFICATION_CHANNEL_ID_INFO?
Timores
@Timores, non. Vous pouvez le remplacer par votre propre constante.
Anggrayudi H
4

Grâce à @CopsOnRoad, sa solution a été d'une grande aide mais ne fonctionne que pour le SDK 26 et supérieur. Mon application cible 24 et plus.

Pour empêcher Android Studio de se plaindre, vous avez besoin d'un conditionnel directement autour de la notification. Il n'est pas assez intelligent de savoir que le code est dans une méthode conditionnelle à 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
la source
Pouvez-vous s'il vous plaît clarifier les modifications que vous avez apportées à ce code, je n'ai pas compris.
CopsOnRoad
Les versions 8.0 et Android Pie fonctionnent parfaitement. Mais pourquoi avons-nous besoin du canal de notification pour la version 8.1 uniquement?
Thamarai T
2

Cela a fonctionné pour moi. Dans ma classe de service, j'ai créé le canal de notification pour Android 8.1 comme ci-dessous:

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

Remarque: créez le canal sur lequel vous créez la notification pour Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

Arjun
la source
-1

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

    }

J'espère que cela aidera :)

Shahriar Nasim Nafi
la source
1
Veuillez prendre le temps d'expliquer la justification de votre solution.
straya