StartForeground kończy się niepowodzeniem po aktualizacji do Androida 8.1

193

Po uaktualnieniu telefonu do wersji 8.1 Developer Preview moja usługa w tle nie uruchamia się poprawnie.

W mojej długo działającej usłudze zaimplementowałem metodę startForeground, aby rozpocząć bieżące powiadomienie, które jest wywoływane podczas tworzenia.

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

Komunikat o błędzie:

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)

niepoprawny kanał do powiadamiania usługi , najwyraźniej mój stary kanał DEFAULT_CHANNEL_ID nie jest już odpowiedni dla API 27. Jaki byłby właściwy kanał? Próbowałem przejrzeć dokumentację

Rawa
źródło
1
Ta odpowiedź była moim rozwiązaniem.
Alex Jolig

Odpowiedzi:

229

Po jakimś majsterkowaniu przez jakiś czas przy różnych rozwiązaniach odkryłem, że należy utworzyć kanał powiadomień w systemie Android 8.1 i nowszych.

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
}

Z mojego zrozumienia usługi w tle są teraz wyświetlane jako normalne powiadomienia, które użytkownik może następnie wybrać, aby nie wyświetlać, usuwając zaznaczenie kanału powiadomień.

Aktualizacja : Nie zapomnij również dodać uprawnienia do pierwszego planu jako wymaganego Androida P:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Rawa
źródło
Czy musimy wprowadzić te zmiany w przypadku JobIntentService? Czy obsługuje to wewnętrznie?
Amrut,
1
dlaczego nie IMPORTANCE_DEFAULTzamiast IMPORTANCE_NONE?
user924
1
@ user924 Kotlin jest w rzeczywistości nowszym językiem niż Swift. Kotlin nie zastępuje Javy, jest tylko alternatywą dla programowania Java na Androida. Jeśli spróbujesz, zobaczysz, że w rzeczywistości jest bardzo podobny w składni do Swift. Osobiście uważam, że jest lepszy niż Java, pomimo tego, co mówi indeks Tiobe (indeks podlega niewielkim stronniczościom braku odpowiedzi). Rozwiązuje wiele problemów związanych z Javą, w tym przerażający wyjątek NullPointerException, gadatliwość i kilka innych rzeczy. Według najnowszego We / Wy Google 95% programistów korzystających z Kotlin na Androida jest z niego zadowolonych.
Sub 6 Zasoby
3
Powinno to zostać wywołane z onCreate () twojej usługi
Evgenii Vorobei
1
@Rawa Cóż, nawet nie jestem pewien, co robisz ze swoją usługą Foreground w aplikacji, ponieważ Dokumentacja nie kłamie. Jasno stwierdza, że ​​otrzymasz SecurityException, jeśli spróbujesz utworzyć usługę pierwszego planu bez pozwolenia w manifeście.
CopsOnRoad
134

Rozwiązanie Java (Android 9.0, API 28)

Dodaj do swojej Serviceklasy:

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

AKTUALIZACJA: ANDROID 9.0 PIE (API 28)

Dodaj to uprawnienie do swojego AndroidManifest.xmlpliku:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
CopsOnRoad
źródło
Czy istnieje powód używania unikalnych identyfikatorów w dwóch wywołaniach startForeground ()? Czy nie mogą być tutaj tacy sami, ponieważ to samo powiadomienie?
Cody,
@CopsOnRoad, więc nie ma potrzeby kanału powiadomień dla O?
Shruti
2
@Shruti Musisz dodać uprawnienia wraz z kodem dla Androida 9.0. Oba są potrzebne.
CopsOnRoad
1
@CopsOnRoad Jest to wyjątek „Fatalny wyjątek: android.app.RemoteServiceException: Context.startForegroundService () nie wywołał następnie Service.startForeground ()”
Shruti
2
Czy można uniknąć wyświetlania powiadomienia podczas działania usługi?
matdev
29

Pierwsza odpowiedź jest świetna tylko dla tych, którzy znają kotlina, dla tych, którzy nadal używają java tutaj, tłumaczę pierwszą odpowiedź

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

W przypadku Androida P nie zapomnij dołączyć tego uprawnienia

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Hamza
źródło
Dziękujemy za przetłumaczenie kodu na Javę. To duża pomoc dla projektów Java!
Ray Li
17

Działa poprawnie w Andorid 8.1:

Zaktualizowana próbka (bez przestarzałego kodu):

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

Stary snipped (to inna aplikacja - nie związanych z powyższym kodzie):

@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
źródło
2
Część powyższego kodu jest już nieaktualna, co można pokonać, przechodząc Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...naNotification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
ban-geoengineering
1
@ ban-geoengineering masz absolutną rację ... Dodałem nowy przykładowy kod. Dzięki.
Martin Pfeffer
dlaczego PRIORITY_MAXlepiej używać?
user924
7

W moim przypadku jest tak, ponieważ próbowaliśmy opublikować powiadomienie bez określenia 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));
    }
}

Najlepszym miejscem do umieszczenia powyższego kodu jest onCreate()metoda w Applicationklasie, dlatego musimy zadeklarować go raz na zawsze:

public class App extends Application {

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

Po tym, jak to skonfigurujemy, możemy użyć powiadomienia z channelIdpodanym przez nas:

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

Następnie możemy użyć go do opublikowania powiadomienia:

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

Jeśli chcesz go używać jako powiadomienia usługi nowej wiedzy:

startForeground(notifId, builder.build());
Anggrayudi H.
źródło
1
Czy stała NOTIFICATION_CHANNEL_ID_TASK (2. linia) powinna być NOTIFICATION_CHANNEL_ID_INFO?
Timores,
@Timores, no. Możesz go zastąpić własną stałą.
Anggrayudi H
4

Dzięki @CopsOnRoad jego rozwiązanie było dużą pomocą, ale działa tylko dla SDK 26 i wyższych. Moja aplikacja jest kierowana na 24 i więcej.

Aby Android Studio nie narzekało, potrzebujesz warunku bezpośrednio wokół powiadomienia. Nie jest wystarczająco mądry, aby wiedzieć, że kod jest metodą zależną od 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
źródło
Czy możesz wyjaśnić, jakie zmiany wprowadziłeś w tym kodzie, nie otrzymałem tego.
CopsOnRoad
Wersje 8.0 i Android Pie działają doskonale. Ale dlaczego potrzebujemy kanału powiadomień tylko dla wersji 8.1?
Thamarai T
2

To zadziałało dla mnie. W mojej klasie usług utworzyłem kanał powiadomień dla Androida 8.1, jak poniżej:

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

Uwaga: Utwórz kanał, dla którego tworzysz Powiadomienie Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

Arjun
źródło
-1

Oto moje rozwiązanie

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

    }

Mam nadzieję, że to pomoże :)

Shahriar Nasim Nafi
źródło
1
Poświęć trochę czasu na wyjaśnienie uzasadnienia swojego rozwiązania.
straya