Jak zaktualizować tekst powiadomienia dla usługi pierwszego planu w systemie Android?

139

Mam konfigurację usługi pierwszego planu w systemie Android. Chciałbym zaktualizować tekst powiadomienia. Tworzę usługę, jak pokazano poniżej.

Jak mogę zaktualizować tekst powiadomienia skonfigurowany w ramach tej usługi pierwszego planu? Jaka jest najlepsza praktyka w zakresie aktualizowania powiadomienia? Każdy przykładowy kod będzie mile widziany.

public class NotificationService extends Service {

    private static final int ONGOING_NOTIFICATION = 1;

    private Notification notification;

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

        this.notification = new Notification(R.drawable.statusbar, getText(R.string.app_name), System.currentTimeMillis());
        Intent notificationIntent = new Intent(this, AbList.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        this.notification.setLatestEventInfo(this, getText(R.string.app_name), "Update This Text", pendingIntent);

        startForeground(ONGOING_NOTIFICATION, this.notification);

    }

Tworzę usługę w mojej głównej działalności, jak pokazano poniżej:

    // Start Notification Service
    Intent serviceIntent = new Intent(this, NotificationService.class);
    startService(serviceIntent);
Łukasz
źródło

Odpowiedzi:

63

Myślę, że startForeground()ponowne połączenie z tym samym unikalnym identyfikatorem iz Notificationnowymi informacjami zadziała, chociaż nie próbowałem tego scenariusza.

Aktualizacja: na podstawie komentarzy należy użyć NotifcationManager, aby zaktualizować powiadomienie, a usługa nadal pozostaje w trybie pierwszego planu. Spójrz na odpowiedź poniżej.

CommonsWare
źródło
1
Czy mógłbyś jednak podać przykład, jak nazwałbym to na podstawie mojej aktywności? Nie udało mi się znaleźć dobrej próbki sposobu wywoływania metod w mojej usłudze pierwszego planu.
Luke
1
@Luke: Istnieje wiele wzorców korzystania z usługi i nie mam pojęcia, jakie są Twoje cele. Jeśli dzwonisz, startService()aby przekazać polecenie do usługi, po prostu zadzwoń startService()ponownie, aby nakazać zaktualizowanie tekstu. Lub, jeśli dzwonisz bindService(), dodaj metodę do interfejsu API, aby usługa zaktualizowała swój tekst. Albo zastanów się, czy to sama usługa powinna podejmować decyzję, czy zaktualizować tekst, czy nie. A może tekst jest SharedPeferencetym, na którym usługa ma nasłuchiwanie. W skrócie nie da się udzielić dokładnej porady.
CommonsWare
9
aby wyjaśnić dalej: nie możesz cancel()ustawić powiadomienia przez startForeground(). Musisz usunąć status pierwszego planu samej usługi (używając, stopForeground()jeśli chcesz, aby tekst na
pasku zadań
4
Odrzuciłem tę odpowiedź, ponieważ jest ona wyraźnie błędna: developer.android.com/training/notify-user/managing.html @CommonsWare rozważ usunięcie tej odpowiedzi, ponieważ Twój wysoki wynik reputacji sprawia, że ​​jest to „święta prawda” dla zwykła przeglądarka. Dzięki.
HYS
2
Nie zadziałało dla mnie (chociaż pamiętam, że użyłem tej samej metody w poprzednim projekcie). Używanie NotificationManagerdziałało zgodnie z oczekiwaniami.
user149408
227

Jeśli chcesz zaktualizować zestaw powiadomień za pomocą funkcji startForeground (), po prostu utwórz nowe powiadomienie, a następnie użyj NotificationManager, aby je powiadomić.

Kluczową kwestią jest użycie tego samego identyfikatora powiadomienia.

Nie testowałem scenariusza wielokrotnego wywoływania metody startForeground () w celu zaktualizowania Notification, ale myślę, że użycie NotificationManager.notify byłoby lepsze.

Aktualizacja Powiadomienia NIE usunie Usługi z pierwszego planu (można to zrobić tylko poprzez wywołanie stopForground);

Przykład:

private static final int NOTIF_ID=1;

@Override
public void onCreate (){
    this.startForeground();
}

private void startForeground() {
    startForeground(NOTIF_ID, getMyActivityNotification(""));
}

private Notification getMyActivityNotification(String text){
    // The PendingIntent to launch our activity if the user selects
    // this notification
    CharSequence title = getText(R.string.title_activity);
    PendingIntent contentIntent = PendingIntent.getActivity(this,
            0, new Intent(this, MyActivity.class), 0);

    return new Notification.Builder(this)
            .setContentTitle(title)
            .setContentText(text)
            .setSmallIcon(R.drawable.ic_launcher_b3)
            .setContentIntent(contentIntent).getNotification();     
}

/**
 * This is the method that can be called to update the Notification
 */
private void updateNotification() {
    String text = "Some text that will update the notification";

    Notification notification = getMyActivityNotification(text);

    NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    mNotificationManager.notify(NOTIF_ID, notification);
}

W dokumentacji Zjednoczone

Aby skonfigurować powiadomienie i zaktualizować je, nadaj mu identyfikator powiadomienia dzwoniąc NotificationManager.notify(). Aby zaktualizować to powiadomienie po jego wydaniu, zaktualizuj lub utwórz NotificationCompat.Builderobiekt, utwórz z niego obiekt Notificationi wystaw go Notificationz tym samym identyfikatorem, którego użyłeś wcześniej. Jeśli poprzednie powiadomienie jest nadal widoczne, system aktualizuje je na podstawie zawartości Notificationobiektu. Jeśli poprzednie powiadomienie zostało odrzucone, zamiast niego zostanie utworzone nowe powiadomienie.

Luca Manzo
źródło
35
TO JEST PRAWIDŁOWA ODPOWIEDŹ! Powyższa odpowiedź jest bardzo błędna i myląca. Nie musisz ponownie uruchamiać usługi, aby zaktualizować głupie powiadomienie.
Radu,
7
@Radu Chociaż zgadzam się, że jest to optymalna odpowiedź (pozwala uniknąć nieco dłuższej ścieżki kodu, którą zajmuje odpowiedź Commonsware), mylisz się co do tego, co robi odpowiedź Commonsware - start / stopForegound nie uruchamia / zatrzymuje usługi, po prostu wpływają na jej pierwszoplanowość .
Stevie
@Stevie Dzięki za to Stevie, prawdopodobnie masz rację. Mimo to też bym z tym nie zadzierał!
Radu,
Wywołanie notify()lub startForeground()oba prowadzą do połączenia onStartCommand().
M. Reza Nasirloo 07.07.15
10
Problem z używaniem NotificationManagerdo aktualizacji powiadomienia wyświetlanego za pomocą startForegroundpolega na tym, że połączenie stopForegroundnie będzie już usuwać powiadomienia. Zaktualizowanie go innym wywołaniem startForegroundeliminuje ten problem.
Tad
23

Poprawa odpowiedzi Luca Manzo w systemie Android 8.0+. Podczas aktualizacji powiadomienia będzie wydawać dźwięk i wyświetlać się jako Uwaga.
aby temu zapobiec, musisz dodaćsetOnlyAlertOnce(true)

więc kod to:

private static final int NOTIF_ID=1;

@Override
public void onCreate(){
        this.startForeground();
}

private void startForeground(){
        startForeground(NOTIF_ID,getMyActivityNotification(""));
}

private Notification getMyActivityNotification(String text){
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
        ((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(
        NotificationChannel("timer_notification","Timer Notification",NotificationManager.IMPORTANCE_HIGH))
}

        // The PendingIntent to launch our activity if the user selects
        // this notification
        PendingIntent contentIntent=PendingIntent.getActivity(this,
        0,new Intent(this,MyActivity.class),0);

        return new NotificationCompat.Builder(this,"my_channel_01")
        .setContentTitle("some title")
        .setContentText(text)
        .setOnlyAlertOnce(true) // so when data is updated don't make sound and alert in android 8.0+
        .setOngoing(true)
        .setSmallIcon(R.drawable.ic_launcher_b3)
        .setContentIntent(contentIntent)
        .build();
}

/**
 * This is the method that can be called to update the Notification
 */
private void updateNotification(){
        String text="Some text that will update the notification";

        Notification notification=getMyActivityNotification(text);

        NotificationManager mNotificationManager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(NOTIF_ID,notification);
}
humazed
źródło
Uratowałeś mój dzień. Dziękuję
Rubén Viguera
2
Brakujące słowo kluczowe, powinno byćnew NotificationChannel
7hny
więc możemy przekazać do notification_id? dowolną liczbę inną niż 0?
Wini
5

oto kod, aby to zrobić w twojej usłudze . Utwórz nowe powiadomienie, ale poproś menedżera powiadomień o powiadomienie o tym samym identyfikatorze powiadomienia, którego użyłeś w startForeground.

Notification notify = createNotification();
final NotificationManager notificationManager = (NotificationManager) getApplicationContext()
    .getSystemService(getApplicationContext().NOTIFICATION_SERVICE);

notificationManager.notify(ONGOING_NOTIFICATION, notify);

pełne przykładowe kody można sprawdzić tutaj:

https://github.com/plateaukao/AutoScreenOnOff/blob/master/src/com/danielkao/autoscreenonoff/SensorMonitorService.java

Daniel Kao
źródło
Nie jestem pewien, czy to zachowa status pierwszego planu startService.
Martin Marconcini
@Daniel Kao Twoje rozwiązanie nie uruchamia usługi pierwszego planu
IgorGanapolsky
4
Popraw mnie, jeśli się mylę, ale czy ludzie głosujący na tę odpowiedź mogliby bardziej opisać, co jest w niej nie tak? Pytanie nie dotyczy sposobu uruchamiania usługi pierwszego planu, ale jak zaktualizować powiadomienie usługi pierwszego planu. Jest to w rzeczywistości ta sama odpowiedź, co Luca, co do której ludzie zgadzają się, działa i utrzymuje status pierwszego planu.
TheIT
@TheIT To nie działa. Status powiadomienia zmienia się not foregroundna foreground createdwiadomość.
Wiaczesław
1
Spowodowałoby to zduplikowane powiadomienie, ponieważ metoda startForeground () została już wywołana.
IgorGanapolsky
2

Wygląda na to, że żadna z istniejących odpowiedzi nie pokazuje, jak obsłużyć cały przypadek - aby rozpocząćForeground, jeśli jest to pierwsze połączenie, ale zaktualizować powiadomienie dla kolejnych połączeń.

Aby wykryć właściwy przypadek, możesz użyć następującego wzorca:

private void notify(@NonNull String action) {
    boolean isForegroundNotificationVisible = false;
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    StatusBarNotification[] notifications = notificationManager.getActiveNotifications();
    for (StatusBarNotification notification : notifications) {
        if (notification.getId() == FOREGROUND_NOTE_ID) {
            isForegroundNotificationVisible = true;
            break;
        }
    }
    Log.v(getClass().getSimpleName(), "Is foreground visible: " + isForegroundNotificationVisible);
    if (isForegroundNotificationVisible){
        notificationManager.notify(FOREGROUND_NOTE_ID, buildForegroundNotification(action));
    } else {
        startForeground(FOREGROUND_NOTE_ID, buildForegroundNotification(action));
    }
}

Dodatkowo musisz zbudować powiadomienie i kanał jak w innych odpowiedziach:

private Notification buildForegroundNotification(@NonNull String action) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        createNotificationChannel();
    }
    //Do any customization you want here
    String title;
    if (ACTION_STOP.equals(action)) {
        title = getString(R.string.fg_notitifcation_title_stopping);
    } else {
        title = getString(R.string.fg_notitifcation_title_starting);
    }
    //then build the notification
    return new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(title)
            .setOngoing(true)
            .build();
}

@RequiresApi(Build.VERSION_CODES.O)
private void createNotificationChannel(){
    NotificationChannel chan = new NotificationChannel(CHANNEL_ID, getString(R.string.fg_notification_channel), NotificationManager.IMPORTANCE_DEFAULT);
    chan.setLightColor(Color.RED);
    chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
    NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    assert manager != null;
    manager.createNotificationChannel(chan);
}
Nick Cardoso
źródło