Как запуститьForeground () без уведомления?

87

Я хочу создать службу и заставить ее работать на переднем плане.

На большинстве примеров кодов есть уведомления. Но я не хочу показывать никаких уведомлений. Это возможно?

Вы можете привести мне несколько примеров? Есть ли альтернативы?

Моя служба приложений делает медиаплеер. Как сделать так, чтобы система не убивала мою службу, за исключением того, что приложение убивает ее само (например, приостанавливает или останавливает музыку кнопкой).

Мухаммад Ресна Ризки Пратама
источник
11
У вас не может быть услуги «Передний план» без уведомления. Период.
Jug6ernaut 09
1
Как насчет того, чтобы дать "фальшивое" уведомление? это уловка?
Мухаммад Ресна Ризки Пратама,
После настройки уведомлений переднего плана вы можете удалить «фоновые уведомления» с помощью приложения Rubber .
Laurent
1
@MuhammadResnaRizkiPratama, не могли бы вы изменить принятый ответ? Этот вопрос чрезвычайно популярен, но принятый ответ полностью устарел, слишком деспотичен и делает предположения, зачем разработчику это нужно. Я предлагаю этот ответ, потому что он надежен, не использует ошибки ОС и работает на большинстве устройств Android.
Сэм

Ответы:

95

В качестве функции безопасности платформы Android вы ни при каких обстоятельствах не можете использовать передовую службу без уведомления. Это связано с тем, что передовая служба потребляет больше ресурсов и подчиняется другим ограничениям планирования (т. Е. Она не прекращается так быстро), чем фоновые службы, и пользователю необходимо знать, что, возможно, съедает их батарею. Так что не делай этого.

Тем не менее, это возможно , чтобы иметь «поддельное» уведомление, то есть вы можете сделать прозрачный значок уведомления (IIRC). Это крайне неискренне по отношению к вашим пользователям, и у вас нет причин для этого, кроме как убивать их батареи и тем самым создавать вредоносные программы.

Кристофер Мицински
источник
3
Спасибо. Это дает мне понять, почему setForeground нуждается в уведомлении.
Мухаммад Ресна Ризки Пратама
21
Что ж, похоже, у вас есть причина для этого, пользователи просили об этом. Как вы говорите, это может показаться неискренним, но для продукта, над которым я работаю, это было запрошено группами тестирования. Возможно, в Android должна быть возможность скрывать уведомления от некоторых служб, которые, как вы знаете, всегда работают. В противном случае ваша панель уведомлений будет выглядеть как новогодняя елка.
Radu
3
@Radu на самом деле у вас не должно быть так много приложений на переднем плане: это убьет вашу батарею, потому что они планируются по-другому (по сути, они не умирают). Это может быть приемлемо для мода, но я не думаю, что в ближайшее время этот вариант превратится в ванильный Android. «Пользователи» обычно не знают, что для них лучше ..
Кристофер Мичински
2
@Radu, который не поддерживает ваш аргумент. «Отмеченные наградами приложения» - это, как правило, те, которые на самом деле «исправляют» дыры Android через системные несоответствия (например, убийцы задач). Нет причин, по которым вам следует использовать службу переднего плана только потому, что это делают «отмеченные наградами приложения»: если вы это делаете, вы признаете, что убили батарею пользователя.
Кристофер Мицински
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()и в onDestroy call stopForeground(true)).

Вуаля! Уведомлений нет вообще, и ваша вторая служба продолжает работать.

Лиор Илуз
источник
23
Спасибо за это. Я думаю, что некоторые люди не понимают, что существует разработка Android, которая ведется для внутреннего использования в бизнесе (то есть, она не предназначена для того, чтобы когда-либо появиться на рынке или быть проданной Джо Блоу). Иногда люди просят о таких вещах, как «прекратить показывать служебное уведомление», и неплохо увидеть обходные пути, даже если они не являются кошерными для общего пользования.
JamieB
5
@JamieB Я не могу с этим согласиться ... Я предложил Дайанн Хакборн переосмыслить всю концепцию фоновых служб и, возможно, добавить разрешение на запуск службы в фоновом режиме без уведомлений. Для нас, разработчиков, не имеет значения, есть уведомление или нет - это запрос ПОЛЬЗОВАТЕЛЯ на его удаление! :) Кстати, вы можете убедиться, что он у вас тоже работает?
Лиор Илуз
1
@JamieB user875707 говорит, что параметр значка (идентификатор ресурса значка) должен быть равен 0, а не идентификатору уведомления (если это так, то, как говорится в документации, «startForeground» не будет работать).
wangqi060934
9
Вы должны, конечно, предположить, что это не будет работать в будущих версиях платформы.
hackbod
1
@JamieB Я тестировал это прямо сейчас на Android 5.0.1, и у меня он все еще работает.
Лиор Илуз 02
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;
    }

}

ForegroundEnhibitedService.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
  • Genymotion
    • 5.0
    • 6.0
  • CyanogenMod
    • 5.1.1

Больше не работает с Android 7.1.

Сэм
источник
2
Еще не тестировал, но было бы неплохо иметь хоть разрешение на это !!! +1 за раздел «В Google». Мои пользователи жалуются на уведомление, необходимое для того, чтобы приложение работало в фоновом режиме, я также жалуюсь на уведомление, необходимое для Skype / Llama и любого другого приложения
Рами Дабайн
извините, как этот пример можно использовать для прослушивания сигналов тревоги. Я разрабатываю приложение, которое устанавливает будильники с помощью alarmManager, и у меня проблема в том, что когда я закрываю приложение (Недавние приложения-> Очистить), все будильники также удаляются, и я пытаюсь найти способ предотвратить это
Ares91,
@ Ares91, попробуйте сделать для этого отдельный вопрос StackOverflow и обязательно включите как можно больше информации, включая соответствующий код. Я мало что знаю о сигналах тревоги, и этот вопрос касается только услуг.
Сэм
@Sam, какая служба должна вызываться первой: ForegroundEnumberingService или 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.

Snicolas
источник
2
Обратите внимание, что это, похоже, больше не принимается в SDK 17. Служба не перейдет на передний план, если в уведомлении передано значение 0.
Snicolas
Это была ошибка и, надеюсь, ее исправили. Идея служб 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 level = 0 visible = 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);
    ...

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

РЕДАКТИРОВАТЬ

Чтобы он работал с Pre-Honeycomb и Android 4.4 и выше, убедитесь, что вы используете то, NotificationCompat.Builderчто предоставлено библиотекой поддержки v7, а не Notification.Builder.

Ангграюди H
источник
1
У меня это отлично работает на 4.3 (18) @vlad sol. Интересно, почему еще нет других голосов за этот ответ?
Роб МакФили,
Я пробовал это в Android N Preview 4, и это не сработало. Я пробовал Notification.Builder, Support Library v4 NotificationCompat.Builderи Support Library v7 NotificationCompat.Builder. Уведомление не отображалось в панели уведомлений или строке состояния, но служба не работала в режиме переднего плана, когда я проверял ее с помощью getRunningServices()и dumpsys.
Сэм
@Sam, Значит, в более ранней версии этой ошибки нет, верно?
Anggrayudi H
@AnggrayudiH, под "этой ошибкой" вы имеете в виду, что уведомление не появляется? Или вы имеете в виду, что служба не переходит в режим переднего плана?
Сэм
1
У меня это не работает. Процесс запускается как передний план при отправке id! = 0, но НЕ запускается как передний план, когда id = 0. Используйте adb shell dumpsys activity servicesдля проверки.
beetree
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 или более ранней версии.

phnmnn
источник
Хороший обходной путь. Также я думаю, что для этого требуется значок на 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 и выше.


Я установил параметр значка в конструкторе для уведомления равным нулю, а затем передал полученное уведомление в 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);
Влад Соль
источник
Хотя этот ответ, вероятно, правильный и полезный, желательно, чтобы вы включили вместе с ним некоторые пояснения, чтобы объяснить, как он помогает решить проблему. Это станет особенно полезным в будущем, если произойдет изменение (возможно, не связанное с этим), из-за которого он перестанет работать, и пользователям необходимо понять, как оно когда-то работало.
Кевин Браун
Я думаю, что 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);

        }

    }

Постоянное уведомление не отображается, однако вы увидите уведомление «x-приложения Android работают в фоновом режиме».

Пит
источник
В Android 8.1: «Плохое уведомление для startForeground: java.lang.RuntimeException: недопустимый канал для служебного уведомления»
T-igra
Лично я считаю, что apps are running in the backgroundуведомление даже хуже, чем уведомление для конкретного приложения, поэтому я не уверен, что такой подход того стоит.
Sam
-2

Обновление: это больше не работает в Android 7.1 и выше.


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

Tshunglee
источник
Это единственное рабочее решение, о котором я знаю. Однако это уже покрыто другим ответом .
Сэм
-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 (я);

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

ссылка : прочтите параграф, в котором обсуждается, когда система останавливает службу

redM0nk
источник
Прочитал в некоторых статьях. Если просто выполнить startService (), тогда системе потребуется больше памяти. Итак, система убьет эту службу. это правда? У меня просто нет времени на эксперименты в сервисе.
Мухаммад Ресна Ризки Пратама
1
Мухаммад, это правда - цель startForeground () состоит в том, чтобы эффективно предоставить службе более высокий «приоритет памяти», чем фоновые службы, чтобы она могла работать дольше. StartForeground () должен использовать уведомление, чтобы пользователь был более осведомлен о работающей более постоянной / трудной для уничтожения службе.
DustinB
Это просто неправда; сделать сервис, который нельзя убить на Android, практически невозможно. Android с готовностью убивает неофициальные службы, чтобы освободить память.
Сэм,