startForeground fallisce dopo l'aggiornamento ad Android 8.1


193

Dopo aver aggiornato il mio telefono a 8.1 Developer Preview il mio servizio in background non si avvia più correttamente.

Nel mio servizio di lunga durata ho implementato un metodo startForeground per avviare la notifica in corso che viene chiamata al momento della creazione.

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

Messaggio di errore:

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)

canale non valido per la notifica del servizio , apparentemente il mio vecchio canale DEFAULT_CHANNEL_ID non è più appropriato per l'API 27 presumo. Quale sarebbe il canale giusto? Ho provato a consultare la documentazione


1
Questa risposta è stata la mia soluzione.
Alex Jolig,

Risposte:


231

Dopo aver armeggiato per un po 'con soluzioni diverse, ho scoperto che bisogna creare un canale di notifica in Android 8.1 e versioni successive.

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
}

Dalla mia comprensione, i servizi in background vengono ora visualizzati come normali notifiche che l'utente può quindi selezionare per non mostrare deselezionando il canale di notifica.

Aggiornamento : Inoltre, non dimenticare di aggiungere l'autorizzazione in primo piano come richiesto Android P:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Dobbiamo apportare queste modifiche in caso di JobIntentService? O lo sta gestendo internamente?
Amrut,

1
perché non IMPORTANCE_DEFAULTinvece di IMPORTANCE_NONE?
user924

1
@ user924 Kotlin è in realtà una lingua più recente di Swift. Kotlin non sostituisce Java, è solo un'alternativa a Java per lo sviluppo Android. Se lo provi, vedrai che in realtà è abbastanza simile nella sintassi di Swift. Personalmente credo che sia meglio di Java, nonostante ciò che dice l'indice Tiobe (l'indice è soggetto a un po 'di bias di mancata risposta). Risolve molti dei problemi che Java ha, tra cui la temuta NullPointerException, la verbosità e molte altre cose. Secondo l'ultimo I / O di Google, il 95% degli sviluppatori che usano Kotlin per Android ne è soddisfatto.
Risorse secondarie 6,

3
Questo dovrebbe essere chiamato da onCreate () del tuo servizio
Evgenii Vorobei,

1
@Rawa Beh, anche io non sono sicuro di cosa stai facendo con il tuo servizio Foreground nell'app perché la Documentazione non mente. Indica chiaramente che otterrai SecurityException se provi a creare un servizio in primo piano senza l'autorizzazione nel manifest.
CopsOnRoad

134

Soluzione Java (Android 9.0, API 28)

Nella tua Serviceclasse, aggiungi questo:

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

AGGIORNAMENTO: ANDROID 9.0 PIE (API 28)

Aggiungi questa autorizzazione al tuo AndroidManifest.xmlfile:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

C'è un motivo per usare un ID univoco nelle due chiamate startForeground ()? Non possono essere gli stessi qui dato che è la stessa notifica?
Cody,

@CopsOnRoad quindi non c'è bisogno del canale di notifica per O?
Shruti,

2
@Shruti Devi aggiungere l'autorizzazione insieme al codice per Android 9.0. Entrambi sono necessari.
CopsOnRoad,

1
@CopsOnRoad Questa è l'eccezione 'Eccezione irreversibile: android.app.RemoteServiceException: Context.startForegroundService () non ha quindi chiamato Service.startForeground ()'
Shruti il

2
È possibile evitare di visualizzare la notifica mentre il servizio è in esecuzione?
matdev,

29

La prima risposta è ottima solo per quelle persone che conoscono kotlin, per coloro che usano ancora java qui traduco la prima risposta

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

Per Android, P non dimenticare di includere questa autorizzazione

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Grazie per aver tradotto il codice in Java. È di grande aiuto per i progetti Java!
Ray Li

17

Funziona correttamente su Andorid 8.1:

Esempio aggiornato (senza codice deprecato):

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

Vecchio frammento (è un'app diversa, non correlata al codice sopra):

@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
Parte del codice sopra è ora deprecato, che puoi superare cambiando Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...inNotification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
ban-geoengineering

1
@ ban-geoengineering hai perfettamente ragione ... Ho aggiunto un nuovo codice di esempio. Grazie.
Martin Pfeffer,

perché PRIORITY_MAXcosa è meglio usare?
user924

7

Nel mio caso, è perché abbiamo provato a pubblicare una notifica senza specificare 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));
    }
}

Il posto migliore per inserire il codice sopra è nel onCreate()metodo nella Applicationclasse, quindi dobbiamo solo dichiararlo una volta per tutte:

public class App extends Application {

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

Dopo aver impostato questo, possiamo usare la notifica con il channelIdappena specificato:

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

Quindi, possiamo usarlo per pubblicare una notifica:

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

Se si desidera utilizzarlo come notifica del servizio di primo piano:

startForeground(notifId, builder.build());

1
La costante NOTIFICATION_CHANNEL_ID_TASK (2a riga) dovrebbe essere NOTIFICATION_CHANNEL_ID_INFO?
Timores,

@Timores, no. Puoi sostituirlo con la tua costante.
Anggrayudi H,

4

Grazie a @CopsOnRoad, la sua soluzione è stata di grande aiuto, ma funziona solo con SDK 26 e versioni successive. La mia app ha come target 24 e versioni successive.

Per evitare che Android Studio si lamenti, è necessario disporre di un condizionale direttamente intorno alla notifica. Non è abbastanza intelligente sapere che il codice è in un metodo subordinato a 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);
    }
}

Potete per favore chiarire quali modifiche sono state apportate in questo codice, non ho capito.
CopsOnRoad,

Le versioni 8.0 e Android Pie funzionano perfettamente. Ma perché abbiamo bisogno del canale di notifica solo per la versione 8.1?
Thamarai T

2

Questo ha funzionato per me. Nella mia classe di servizio, ho creato il canale di notifica per Android 8.1 come di seguito:

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

Nota: creare il canale per cui si sta creando la notifica Build.VERSION.SDK_INT >= Build.VERSION_CODES.O


-1

Ecco la mia soluzione

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

    }

Spero che possa aiutare :)


1
Ti preghiamo di dedicare un po 'di tempo a spiegare le motivazioni della tua soluzione.
straya,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.