startForeground schlägt nach dem Upgrade auf Android 8.1 fehl

191

Nach dem Upgrade meines Telefons auf 8.1 Developer Preview wird mein Hintergrunddienst nicht mehr ordnungsgemäß gestartet.

In meinem langjährigen Dienst habe ich eine startForeground-Methode implementiert, um die laufende Benachrichtigung zu starten, die beim Erstellen aufgerufen wird.

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

Fehlermeldung:

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)

Ungültiger Kanal für Dienstbenachrichtigung , anscheinend ist mein alter Kanal, die DEFAULT_CHANNEL_ID, nicht mehr für API 27 geeignet, nehme ich an. Was wäre der richtige Kanal? Ich habe versucht, die Dokumentation durchzusehen

Rawa
quelle
1
Diese Antwort war meine Lösung.
Alex Jolig

Antworten:

226

Nach einigem Basteln mit verschiedenen Lösungen stellte ich fest, dass man in Android 8.1 und höher einen Benachrichtigungskanal erstellen muss.

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
}

Nach meinem Verständnis werden Hintergrunddienste jetzt als normale Benachrichtigungen angezeigt, die der Benutzer auswählen kann, um sie nicht anzuzeigen, indem er den Benachrichtigungskanal abwählt.

Update : Vergessen Sie auch nicht, die Vordergrundberechtigung nach Bedarf hinzuzufügen. Android P:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Rawa
quelle
Müssen wir diese Änderungen im Fall von JobIntentService vornehmen? Oder geht es intern darum?
Amrut
1
warum nicht IMPORTANCE_DEFAULTstatt IMPORTANCE_NONE?
user924
1
@ user924 Kotlin ist eigentlich eine neuere Sprache als Swift. Kotlin ersetzt Java nicht, es ist nur eine Alternative zu Java für die Android-Entwicklung. Wenn Sie es ausprobieren, werden Sie feststellen, dass es in der Syntax Swift ziemlich ähnlich ist. Ich persönlich glaube, es ist besser als Java, trotz der Aussagen des Tiobe-Index (der Index unterliegt ein wenig Non-Response-Bias). Es behebt viele der Probleme, die Java hat, einschließlich der gefürchteten NullPointerException, der Ausführlichkeit und einiger anderer Dinge. Laut den neuesten Google I / O sind 95% der Entwickler, die Kotlin für Android verwenden, damit zufrieden.
Sub 6 Ressourcen
3
Dies sollte von onCreate () Ihres Dienstes
aufgerufen
1
@Rawa Nun, auch ich bin mir nicht sicher, was Sie mit Ihrem Vordergrunddienst in der App machen, weil die Dokumentation nicht lügt. Es wird eindeutig angegeben, dass Sie SecurityException erhalten, wenn Sie versuchen, einen Vordergrunddienst ohne die Berechtigung im Manifest zu erstellen.
CopsOnRoad
135

Java-Lösung (Android 9.0, API 28)

Fügen Sie in Ihrer ServiceKlasse Folgendes hinzu:

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

UPDATE: ANDROID 9.0 PIE (API 28)

Fügen Sie diese Berechtigung zu Ihrer AndroidManifest.xmlDatei hinzu:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
CopsOnRoad
quelle
Gibt es einen Grund für die Verwendung eindeutiger IDs in den beiden startForeground () -Aufrufen? Können sie hier nicht gleich sein, da es die gleiche Benachrichtigung ist?
Cody
@CopsOnRoad, sodass für O kein Benachrichtigungskanal erforderlich ist?
Shruti
2
@Shruti Sie müssen die Berechtigung zusammen mit dem Code für Android 9.0 hinzufügen. Beides wird benötigt.
CopsOnRoad
1
@CopsOnRoad Dies ist die Ausnahme 'Schwerwiegende Ausnahme: android.app.RemoteServiceException: Context.startForegroundService () hat dann Service.startForeground () nicht aufgerufen'
Shruti
2
Ist es möglich zu vermeiden, dass die Benachrichtigung angezeigt wird, während der Dienst ausgeführt wird?
Matdev
29

Die erste Antwort ist nur für diejenigen großartig, die Kotlin kennen. Für diejenigen, die hier noch Java verwenden, übersetze ich die erste Antwort

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

Für Android, P vergessen Sie nicht, diese Erlaubnis einzuschließen

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Hamza
quelle
Vielen Dank für die Übersetzung des Codes nach Java. Es ist eine große Hilfe für Java-Projekte!
Ray Li
17

Funktioniert ordnungsgemäß mit Andorid 8.1:

Aktualisiertes Beispiel (ohne veralteten Code):

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 (es ist eine andere App - nicht mit dem obigen Code verwandt):

@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
quelle
2
Ein Teil des obigen Codes ist jetzt veraltet, was Sie überwinden können, indem Sie Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...zuNotification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
Ban-Geoengineering
1
@ Ban-Geoengineering Sie haben absolut Recht ... Ich habe neuen Beispielcode hinzugefügt. Vielen Dank.
Martin Pfeffer
warum PRIORITY_MAXwas ist besser zu benutzen?
user924
7

In meinem Fall haben wir versucht, eine Benachrichtigung zu veröffentlichen, ohne Folgendes anzugeben 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));
    }
}

Der beste Ort, um den obigen Code einzufügen, ist die onCreate()Methode in der ApplicationKlasse, sodass wir sie nur ein für alle Mal deklarieren müssen:

public class App extends Application {

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

Nachdem wir dies eingerichtet haben, können wir die Benachrichtigung mit dem channelIdsoeben angegebenen verwenden:

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

Dann können wir damit eine Benachrichtigung veröffentlichen:

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

Wenn Sie es als Benachrichtigung für den Vordergrunddienst verwenden möchten:

startForeground(notifId, builder.build());
Anggrayudi H.
quelle
1
Sollte die Konstante NOTIFICATION_CHANNEL_ID_TASK (2. Zeile) NOTIFICATION_CHANNEL_ID_INFO sein?
Timores
@ Timores, nein. Sie können es durch Ihre eigene Konstante ersetzen.
Anggrayudi H
4

Dank @CopsOnRoad war seine Lösung eine große Hilfe, funktioniert aber nur für SDK 26 und höher. Meine App zielt auf 24 und höher ab.

Damit sich Android Studio nicht beschwert, benötigen Sie eine Bedingung direkt um die Benachrichtigung. Es ist nicht klug genug zu wissen, dass sich der Code in einer von VERSION_CODE.O bedingten Methode befindet.

@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
quelle
Können Sie bitte klarstellen, welche Änderungen Sie an diesem Code vorgenommen haben? Ich habe dies nicht verstanden.
CopsOnRoad
Die Versionen 8.0 und Android Pie funktionieren einwandfrei. Aber warum brauchen wir den Benachrichtigungskanal nur für Version 8.1?
Thamarai T
2

Das hat bei mir funktioniert. In meiner Serviceklasse habe ich den Benachrichtigungskanal für Android 8.1 wie folgt erstellt:

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

Hinweis: Erstellen Sie den Kanal, für den Sie die Benachrichtigung erstellen Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

Arjun
quelle
-1

Hier ist meine Lösung

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

    }

Hoffe es wird helfen :)

Shahriar Nasim Nafi
quelle
Bitte nehmen Sie sich etwas Zeit, um die Gründe für Ihre Lösung zu erläutern.
Straya