Як запустити Forefore (), не показуючи сповіщення?


87

Я хочу створити послугу і зробити її запущеною на передньому плані.

У більшості прикладних кодів є повідомлення. Але я не хочу показувати жодного сповіщення. Це можливо?

Ви можете навести кілька прикладів? Чи є альтернативи?

Мій сервіс програми робить медіаплеєр. Як змусити систему не вбивати мою службу, за винятком того, що програма вбиває її саму (наприклад, призупиняти або зупиняти музику кнопкою).


11
Ви не можете мати послугу "Foreground" без повідомлення. Період.
Jug6ernaut

1
Як щодо надсилання "фальшивого" сповіщення? це фокус?
Мухаммед Ресна Різки Пратама

Після налаштування сповіщення на передньому плані ви можете видалити "запуск у фонових сповіщеннях" за допомогою програми Rubber .
Лоран,

1
@MuhammadResnaRizkiPratama, чи не могли б Ви змінити прийняту відповідь? Це питання надзвичайно популярне, але прийнята відповідь є абсолютно застарілою, занадто суворою і робить припущення, чому це потрібно розробнику. Я пропоную цю відповідь, оскільки вона надійна, не використовує помилки ОС та працює на більшості пристроїв Android.
Сем

Відповіді:


95

Як функцію безпеки платформи Android ви ні за яких обставин не можете мати передпланову службу, не маючи також повідомлення. Це пов’язано з тим, що попередньо розроблена служба споживає більшу кількість ресурсів і на неї поширюються різні обмеження щодо планування (тобто вона не вбивається так швидко), ніж фонові служби, і користувач повинен знати, що, можливо, їсть їх акумулятор. Отже, не роби цього.

Тим НЕ менше, це можливо , щоб мати «підроблене» повідомлення, тобто ви можете зробити прозорий значок повідомлення (IIRC). Це надзвичайно недостойно для ваших користувачів, і у вас немає підстав для цього, крім того, що ви вбиваєте їх акумулятор і тим самим створюєте шкідливе програмне забезпечення.


3
Дякую. Це дає мені зрозуміти, чому setForeground потребує повідомлення.
Muhammad Resna Rizki Pratama

21
Ну, здається, у вас є причина це зробити, користувачі просили про це. Незважаючи на те, що це може бути хитрим, як ви скажете, тестові групи вимагали його для продукту, над яким я працюю. Можливо, Android повинна мати можливість приховувати сповіщення від деяких служб, які, як ви знаєте, працюють завжди. Інакше панель сповіщень буде схожа на ялинку.
Раду

3
@Radu насправді у вас не повинно бути так багато додатків на передньому плані: це вб'є ваш час автономної роботи, оскільки вони заплановані по-іншому (по суті, вони не вмирають). Це може бути прийнятним для мода, але я не бачу, що цей варіант скоро перетвориться на ванільний Android. "Користувачі" зазвичай не знають, що для них найкраще ..
Крістофер Міцінський

2
@Radu, який не підтримує ваш аргумент. Зазвичай "додатки, що отримують нагороди", це ті, які насправді "виправляють" дірки Android через системні невідповідності (наприклад, вбивці завдань). Немає жодної причини, що вам доведеться користуватися послугою на передньому плані лише тому, що це роблять "програми, що отримують нагороди": якщо ви це зробите, ви зізнаєтесь у вбивстві акумулятора користувача.
Kristopher Micinski

2
Очевидно, цей метод більше не працюватиме, починаючи з 4.3. plus.google.com/u/0/105051985738280261832/posts/MTinJWdNL8t
erbi,

79

Оновлення: це було "виправлено" на Android 7.1. https://code.google.com/p/android/issues/detail?id=213309

З моменту оновлення 4.3 запустити службу, не показуючи сповіщення , в основному неможливоstartForeground() .

Однак ви можете приховати піктограму за допомогою офіційних API ... немає необхідності в прозорій піктограмі: (Використовуйте NotificationCompat для підтримки старих версій)

NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setPriority(Notification.PRIORITY_MIN);

Я помирився з тим фактом, що саме повідомлення все ще має бути там, але для тих, хто все ще хоче його приховати, я, можливо, знайшов обхідний шлях і для цього:

  1. Запустіть фейкову послугу з startForeground() повідомленням та іншим.
  2. Запустіть справжню службу, яку ви хочете запустити, також за допомогою startForeground()(того самого ідентифікатора сповіщення)
  3. Зупиніть першу (фальшиву) послугу (ви можете телефонувати stopSelf()і під час дзвінка onDestroystopForeground(true) ).

Вуаля! Повідомлень взагалі немає, і ваша друга служба продовжує працювати.


23
Дякую за це. Я думаю, що деякі люди не усвідомлюють, що існує розробка Android, яка продовжується для внутрішнього комерційного використання (тобто вона не має на меті ніколи з’являтися на ринку чи продаватися Джо Блов). Іноді люди запитують такі речі, як "перестати показувати службове сповіщення", і приємно спостерігати за робочими шляхами, навіть якщо це не кошерно для загального споживання.
JamieB

5
@JamieB Я не міг більше погодитись ... Я запропонував Діанн Хакборн їм переглянути всю концепцію фонових служб і, можливо, додати дозвіл на запуск служби у фоновому режимі без сповіщень. Для нас, розробників, це не має значення, чи є сповіщення чи ні - це запит КОРИСТУВАЧА про його видалення! :) До речі, чи можете ви переконатися, що це працює і для вас?
Lior Iluz

1
Користувач @JamieB875707 каже встановити параметр піктограми (ідентифікатор ресурсу піктограми) рівним 0, а не ідентифікатор сповіщення (Якщо так, оскільки в документації сказано, що "startForeground" не буде працювати).
wangqi060934

9
Звичайно, слід припустити, що це не буде працювати в майбутніх версіях платформи.
hackbod

1
@JamieB Я насправді протестував це зараз на Android 5.0.1, і це все ще працює для мене.
Ліор Ілуз,

20

Це більше не працює з Android 7.1, і це може порушувати політику розробника Google Play .

Натомість попросіть користувача заблокувати службове сповіщення .


Ось моя реалізація методики у відповіді від Ліор Iluz .

Код

ForegroundService.java

public class ForegroundService extends Service {

    static ForegroundService instance;

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

        instance = this;

        if (startService(new Intent(this, ForegroundEnablingService.class)) == null)
            throw new RuntimeException("Couldn't find " + ForegroundEnablingService.class.getSimpleName());
    }

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

        instance = null;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}

ForegroundEnablingService.java

public class ForegroundEnablingService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (ForegroundService.instance == null)
            throw new RuntimeException(ForegroundService.class.getSimpleName() + " not running");

        //Set both services to foreground using the same notification id, resulting in just one notification
        startForeground(ForegroundService.instance);
        startForeground(this);

        //Cancel this service's notification, resulting in zero notifications
        stopForeground(true);

        //Stop this service so we don't waste RAM.
        //Must only be called *after* doing the work or the notification won't be hidden.
        stopSelf();

        return START_NOT_STICKY;
    }

    private static final int NOTIFICATION_ID = 10;

    private static void startForeground(Service service) {
        Notification notification = new Notification.Builder(service).getNotification();
        service.startForeground(NOTIFICATION_ID, notification);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}

AndroidManifest.xml

<service android:name=".ForegroundEnablingService" />
<service android:name=".ForegroundService" />

Сумісність

Випробувано та працює над:

  • Офіційний емулятор
    • 4.0.2
    • 4.1.2
    • 4.2.2
    • 4.3.1
    • 4.4.2
    • 5.0.2
    • 5.1.1
    • 6.0
    • 7,0
  • Sony Xperia M
    • 4.1.2
    • 4.3
  • Samsung Galaxy?
    • 4.4.2
    • 5.X
  • Генімоція
    • 5.0
    • 6.0
  • CyanogenMod
    • 5.1.1

Більше не працює з Android 7.1.


2
Ще не тестував, але було б непогано мати хоча б дозвіл на це !!! +1 для частини "До Google", мої користувачі скаржаться на сповіщення, необхідне, щоб програма працювала у фоновому режимі, я також скаржуся на сповіщення, необхідне для скайпу / лами та будь-якого іншого додатка
Rami Dabain

вибачте, як цей приклад можна використовувати для прослуховування тривог. Я розробляю програму, яка встановлює тривоги за допомогою alarmManager, і у мене проблема полягає в тому, що коли я закриваю програму (Останні програми-> Очистити), усі сигнали також видаляються, і я шукаю спосіб запобігти цьому
Ares91,

@ Ares91, спробуйте створити для цього окреме запитання щодо StackOverflow та переконайтеся, що ви включили якомога більше інформації, включаючи відповідний код. Я мало що знаю про сигналізацію, і це питання стосується лише послуг.
Сем

@Sam, яку службу слід викликати першою, ForegroundEnablingService або Forground
Android Man

@AndroidManForegroundService
Сем

14

Ви можете використовувати це (як запропонував @Kristopher Micinski):

Notification note = new Notification( 0, null, System.currentTimeMillis() );
note.flags |= Notification.FLAG_NO_CLEAR;
startForeground( 42, note );

ОНОВЛЕННЯ:

Зверніть увагу, що це більше не дозволено з випусками Android KitKat +. І майте на увазі, що це більш-менш порушує принцип дизайну в Android, що робить фонові операції видимими для користувачів, як згадував @Kristopher Micinski


2
Зверніть увагу, що, схоже, це вже не приймається з SDK 17. Послуга не буде виходити на перший план, якщо витягувана передана до сповіщення
рівна

Це була помилка і, сподіваємось, її виправили. Ідея служб Foreground полягає в тому, що вони менш схильні до вбивства, і це спосіб переконати користувача в тому, що він існує.
Мартін Маркончіні,

4
Для Android 18 сповіщення більше не невидиме, але відображається повідомлення "Додаток запущений, торкніться, щоб отримати додаткову інформацію або зупинити додаток"
Емануель Моеклін,

1
Знімок екрана одного з моїх клієнтів: 1gravity.com/k10/Screenshot_2013-07-25-12-35-49.jpg . Я теж маю пристрій 4.3 і можу підтвердити цю знахідку.
Emanuel Moecklin

2
Попередження: Я отримую звіти про аварійне завершення роботи від Sony Ericsson LT26i (Xperia S) з Android 4.1.2 (SDK 16): android.app.RemoteServiceException: Погане повідомлення, опубліковане з пакета ...: Не вдалося створити піктограму: StatusBarIcon (pkg = ... id = 0x7f020052 рівень = 0 видимий = true num = 0) Я встановив іконку ідентифікатора на 0 до SDK 17, теж. З SDK 18 я встановив для нього дійсний ресурс, який можна малювати. Можливо, вам потрібен дійсний ідентифікатор іконки з SDK 16!
almisoft

13

Попередження : хоча ця відповідь працює, вона насправді мовчки заважає вашій послузі стати послугою на перший план .

Оригінальна відповідь:


Просто встановіть ідентифікатор сповіщення на нуль:

// field for notification ID
private static final int NOTIF_ID = 0;

    ...
    startForeground(NOTIF_ID, mBuilder.build());
    NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    mNotificationManager.cancel(NOTIF_ID);
    ...

Перевага, яку ви можете отримати, полягає в тому, що a Serviceзможе працювати з високим пріоритетом, не руйнуючись системою Android, якщо тільки це не буде великим тиском пам'яті.

РЕДАГУВАТИ

Щоб це працювало з Pre-Honeycomb та Android 4.4 та NotificationCompat.Builderновішими версіями, переконайтесь, що ви використовуєте те, що надано бібліотекою підтримки v7, замість Notification.Builder.


1
Для мене це чудово працює на 4.3 (18) @vlad sol. Чому ще немає жодних прихильників цієї відповіді?
Роб Макфілі

Я спробував це в Android N Preview 4, і це не спрацювало. Я спробував Notification.Builder, бібліотека підтримки v4 NotificationCompat.Builderта бібліотека підтримки v7 NotificationCompat.Builder. Повідомлення не відображалося в панелі сповіщень або рядку стану, але служба не працювала в режимі переднього плану, коли я перевірив його за допомогою getRunningServices()і dumpsys.
Сем

@Sam, отже, ця помилка не з'являється у попередній версії, правильно?
Anggrayudi H

@AnggrayudiH, під "цією помилкою" ви маєте на увазі, що сповіщення не відображається? Або ви маєте на увазі, що служба не переходить у режим переднього плану?
Сем

1
Для мене це не працює. Процес починається як передній план, коли відправляється id! = 0, але НЕ починається як передній план, коли id = 0. Використовуйте adb shell dumpsys activity servicesдля перевірки.
буряк

8

Ви можете приховати сповіщення на Android 9+ за допомогою користувацького макета з layout_height = "0dp"

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.custom_notif);
builder.setContent(remoteViews);
builder.setPriority(NotificationCompat.PRIORITY_LOW);
builder.setVisibility(Notification.VISIBILITY_SECRET);

custom_notif.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0dp">
</LinearLayout>

Перевірено на Pixel 1, android 9. Це рішення не працює на Android 8 або менше


Хороший обхідний шлях. Також я думаю, що для цього потрібна піктограма на android 10. Використання прозорого зображення заповнювача для піктограми вирішує проблему.
shyos

7

Оновлення: це більше не працює в Android 4.3 і новіших версіях


Є одне обхідне рішення. Спробуйте створити сповіщення без значка налаштування, і воно не відображатиметься. Не знаю, як це працює, але це так :)

    Notification notification = new NotificationCompat.Builder(this)
            .setContentTitle("Title")
            .setTicker("Title")
            .setContentText("App running")
            //.setSmallIcon(R.drawable.picture)
            .build();
    startForeground(101,  notification);

3
В Android N Preview 4 це не працює. Android не відображає (YourServiceName) is runningсповіщення, коли ви не вказуєте піктограму. За даними CommonsWare, це було з Android 4.3: commonsware.com/blog/2013/07/30/…
Сем

1
Сьогодні я провів тестування та підтвердив, що це не працює в Android 4.3 та новіших версіях.
Сем

5

Оновлення: це більше не працює в Android 4.3 і новіших версіях


Я встановив параметр піктограми конструктора для Notification на нуль, а потім передав отримане сповіщення startForeground (). У журналі немає помилок, а повідомлення не відображається. Однак я не знаю, чи послугу вдало висунули на перший план - чи є спосіб перевірити?

Відредаговано: перевірено за допомогою dumpsys, і справді служба передувала моїй системі 2.3. Ще не перевіряв інші версії ОС.


9
Скористайтеся командою "adb shell dumpsys activity services", щоб перевірити, чи встановлено для "isForeground" значення true.
чорний

1
Або просто викликати порожній Notification()конструктор.
johsin18

Це спрацювало для мене. Перший раз, коли я запустив свою службу за одну ніч, тому вона, безумовно, на передньому плані (плюс dumpsys каже, що це так) і жодного повідомлення.
JamieB

Зверніть увагу, що це більше не працює в Android 4.3 (API 18). ОС поміщає повідомлення-заповнювач у шухляду сповіщень.
Сем

4

Заблокувати сповіщення служби переднього плану

Більшість відповідей тут або не працюють, не працюють на передньому плані або не порушують політику Google Play .

Єдиний спосіб надійно та безпечно приховати сповіщення - це заблокувати його користувачем.

Android 4.1 - 7.1

Єдиний спосіб - заблокувати всі сповіщення з вашого додатка:

  1. Надіслати користувача на екран відомостей про програму:

    Uri uri = Uri.fromParts("package", getPackageName(), null);
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
    startActivity(intent);
    
  2. Нехай користувач блокує сповіщення програми

Зауважте, що це також блокує тости вашого додатка.

Android 8.0 - 8.1

Не варто блокувати сповіщення на Android O, оскільки ОС просто замінить його на сповіщення " працює у фоновому режимі " або "з використанням акумулятора ".

Android 9+

Використовуйте канал сповіщень, щоб заблокувати службове сповіщення, не впливаючи на інші сповіщення.

  1. Призначити службове сповіщення каналу сповіщень
  2. Надіслати користувача до налаштувань каналу сповіщень

    Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
        .putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName())
        .putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId());
    startActivity(intent);
    
  3. Повідомлення про блокування користувачем


2

версія 4.3 (18) і вище приховування сповіщення про службу неможлива, але ви можете вимкнути піктограму, версія 4.3 (18) і нижче можна приховати сповіщення

Notification noti = new Notification();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
    noti.priority = Notification.PRIORITY_MIN;
}
startForeground(R.string.app_name, noti);

Хоча ця відповідь, ймовірно, правильна і корисна, бажано, якщо ви додасте до неї якесь пояснення, щоб пояснити, як це допомагає вирішити проблему. Це стає особливо корисним у майбутньому, якщо відбудеться зміна (можливо, не пов’язана), яка призведе до того, що вона перестане працювати, і користувачі повинні зрозуміти, як це колись працювало.
Kevin Brown

Я думаю, що Android 4.3 насправді є API 18. Крім того, цей метод приховує лише піктограму з рядка стану; піктограма все ще існує у сповіщенні.
Сем

для приховування піктограми з повідомлення можна додати порожнє зображення mBuilder.setSmallIcon (R.drawable.blank);
vlad sol

1
Я дивлюсь на джерело kitkat, і метод нульового ідентифікатора, схоже, не може працювати. якщо ідентифікатор дорівнює нулю, службовий запис оновлюється до НЕ переднього плану.
Джеффрі Блаттман,

2

Я виявив, що на Android 8.0 це все ще можливо, якщо не використовувати канал сповіщень.

public class BootCompletedIntentReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if ("android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

                Intent notificationIntent = new Intent(context, BluetoothService.class);    
                context.startForegroundService(notificationIntent);

            } else {
                //...
            }

        }
    }
}

А в BluetoothService.class:

 @Override
    public void onCreate(){    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            Intent notificationIntent = new Intent(this, BluetoothService.class);

            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

            Notification notification = new Notification.Builder(this)
                    .setContentTitle("Title")
                    .setContentText("App is running")
                    .setSmallIcon(R.drawable.notif)
                    .setContentIntent(pendingIntent)
                    .setTicker("Title")
                    .setPriority(Notification.PRIORITY_DEFAULT)
                    .build();

            startForeground(15, notification);

        }

    }

Постійне сповіщення не відображається, однак ви побачите сповіщення Android x працює у фоновому режимі.


В Android 8.1: "Помилкове сповіщення про startForeground: java.lang.RuntimeException: недійсний канал для службового сповіщення"
T-igra

Особисто я вважаю, що apps are running in the backgroundсповіщення навіть гірше, ніж сповіщення про конкретну програму, тому я не впевнений, що такий підхід того вартий.
Сем

-2

Оновлення: це більше не працює в Android 7.1 та новіших версіях


Ось спосіб зробити oom_adj вашого додатка 1 (протестовано в емуляторі ANDROID 6.0 SDK).Додайте тимчасову послугу у головному дзвінку служби startForgroundService(NOTIFICATION_ID, notificion). А потім знову запустіть тимчасовий виклик служби startForgroundService(NOTIFICATION_ID, notificion)з тим самим ідентифікатором сповіщення, через деякий час у тимчасовому виклику служби stopForgroundService (true), щоб відхилити початкове повідомлення.


Це єдине робоче рішення, про яке мені відомо. Однак це вже охоплено іншою відповіддю .
Сем

-3

Ви також можете оголосити свою заявку постійною.

<application
    android:icon="@drawable/icon"
    android:label="@string/app_name"
    android:theme="@style/Theme"
    *android:persistent="true"* >
</application>

Це, по суті, встановлює для вашого додатку вищий пріоритет пам’яті, зменшуючи ймовірність його вбивства.


5
Це неправильно, постійними можуть бути лише системні програми: groups.google.com/forum/?fromgroups=#!topic/android-developers/…
Snicolas

-6

Я розробив простий медіаплеєр кілька місяців тому. Отже, я вважаю, якщо ви робите щось на зразок:

Intent i = new Intent(this, someServiceclass.class);

startService (i);

Тоді система не зможе вбити вашу службу.

посилання : прочитайте параграф, в якому обговорюється, коли система зупиняє службу


Я читав у деяких статтях. Якщо просто запустити startService (), то системі потрібно більше пам'яті. Тож система вб’є цю службу. це правда? У мене просто немає часу на експерименти на службі.
Мухаммед Ресна Різки Пратама

1
Мухаммед, це правда - мета startForeground () полягає в тому, щоб ефективно надати службі вищий "пріоритет пам'яті", ніж фонові служби, щоб вона могла довше вижити. Функція startForeground () повинна використовувати сповіщення, щоб користувач був більш обізнаним про запущену службу, яка постійніше / важче вбивається.
DustinB,

Це просто неправда; практично неможливо зробити сервіс, який не можна вбити на Android. Android легко вбиває послуги, що не належать до переднього плану, щоб звільнити пам’ять.
Сем
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.