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

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

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

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

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

Ответ 1

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

Однако возможно иметь "поддельное" уведомление, т.е. вы можете сделать прозрачный значок уведомления (iirc). Это чрезвычайно неприлично для ваших пользователей, и у вас нет причин делать это, кроме как убить их аккумулятор и тем самым создавать вредоносное ПО.

Ответ 2

Обновление: Это было исправлено на 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);

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

  • Запустите фальшивый сервис с startForeground() с уведомлением и всем.
  • Запустите реальный сервис, который вы хотите запустить, также с startForeground() (тот же идентификатор уведомления)
  • Остановить первую (поддельную) услугу (вы можете вызвать stopSelf() и в onDestroy call stopForeground(true)).

Вуаля! Никаких уведомлений вообще, и ваш второй сервис не работает.

Ответ 3

Это больше не работает с Android 7.1 и может нарушать правила Google Play для разработчиков.

Вместо этого я предлагаю запретить пользователю блокировать сервисное уведомление.


Вот моя реализация техники в ответе Лиора Илуза.

Код

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 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.

Ответ 4

Вы можете использовать это (как предложено @Kristopher Micinski):

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

ОБНОВИТЬ:

Обратите внимание, что это больше не разрешено в выпусках Android KitKat+. И имейте в виду, что это более или менее нарушает принцип дизайна в Android, который делает фоновые операции видимыми для пользователей, как упомянуто @Kristopher Micinski

Ответ 5

Просто установите свой идентификатор уведомления на ноль:

// 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.

Ответ 6

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

Отредактировано: проверено с помощью dumpsys, и на самом деле сервис находится в моей системе 2.3. Еще не проверены с другими версиями ОС.

Ответ 7

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

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

Ответ 8

версия 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);

Ответ 9

Блокировать уведомление службы переднего плана

Android 7. 1+ нельзя использовать для сокрытия уведомления. Вместо этого попросите пользователя заблокировать его.

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

Не стоит блокировать уведомление на 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. Пользователь блокирует уведомления канала

Ответ 10

Я нашел на 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 работают в фоновом режиме.

Ответ 11

Вы можете скрыть уведомление на 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 или ниже

Ответ 12

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

Ответ 13

Вы также можете заявить свое приложение как постоянное.

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

Это существенно упрощает ваше приложение с более высоким приоритетом памяти, уменьшая вероятность его убийства.