Como startForeground () sem mostrar notificação?

87

Quero criar um serviço e executá-lo em primeiro plano.

A maioria dos códigos de exemplo contém notificações. Mas não quero mostrar nenhuma notificação. Isso é possível?

Você pode me dar alguns exemplos? Existem alternativas?

Meu serviço de aplicativo está fazendo mediaplayer. Como fazer com que o sistema não mate meu serviço, exceto que o próprio aplicativo o mate (como pausar ou parar a música por botão).

Muhammad Resna Rizki Pratama
fonte
11
Você não pode ter um serviço "Primeiro plano" sem uma notificação. Período.
Jug6ernaut
1
Que tal dar uma notificação "falsa"? isso é um truque?
Muhammad Resna Rizki Pratama
Depois que a notificação de primeiro plano for configurada, você pode remover "notificações em execução em segundo plano" usando o aplicativo Rubber .
Laurent,
1
@MuhammadResnaRizkiPratama, você consideraria alterar a resposta aceita? Esta pergunta é extremamente popular, mas a resposta aceita é completamente desatualizada, muito pesada e faz suposições sobre o motivo pelo qual o desenvolvedor precisa disso. Sugiro esta resposta porque é confiável, não explora bugs do sistema operacional e funciona na maioria dos dispositivos Android.
Sam

Respostas:

95

Como recurso de segurança da plataforma Android, você não pode , sob nenhuma circunstância, ter um serviço em primeiro plano sem também ter uma notificação. Isso ocorre porque um serviço em primeiro plano consome uma quantidade maior de recursos e está sujeito a diferentes restrições de agendamento (ou seja, não é eliminado tão rapidamente) do que os serviços em segundo plano, e o usuário precisa saber o que está possivelmente consumindo sua bateria. Então, não faça isso.

Porém, é possível ter uma notificação "falsa", ou seja, pode-se fazer um ícone de notificação transparente (iirc). Isso é extremamente falso para seus usuários e você não tem motivo para fazer isso, a não ser acabar com a bateria e, assim, criar malware.

Kristopher Micinski
fonte
3
Obrigado. Isso me deixa claro por que setForeground precisa da notificação.
Muhammad Resna Rizki Pratama
21
Bem, parece que você tem uma razão para fazer isso, os usuários pediram por isso. Embora possa ser falso, como você diz, foi solicitado pelos grupos de teste para um produto no qual estou trabalhando. Talvez o Android deva ter a opção de ocultar notificações de alguns serviços que você sabe que estão sempre em execução. Caso contrário, sua barra de notificação parecerá uma árvore de Natal.
Radu
3
@Radu na verdade, você não deveria ter tantos aplicativos em primeiro plano: isso vai acabar com a vida útil da bateria, porque eles são programados de forma diferente (essencialmente, eles não morrem). Isso pode ser aceitável para um mod, mas não vejo essa opção tornando-o no Android vanilla tão cedo. "Usuários" geralmente não sabem o que é melhor para eles ..
Kristopher Micinski
2
@Radu que não apóia seu argumento. "Aplicativos premiados" são geralmente aqueles que realmente "consertam" as falhas do Android por meio de inconsistências do sistema (como eliminadores de tarefas). Não há razão para que você precise usar um serviço de primeiro plano só porque "apps premiados" fazem: se você o fizer, estará admitindo que matou a bateria do usuário.
Kristopher Micinski
2
Aparentemente, este método não funcionará mais a partir de 4.3. plus.google.com/u/0/105051985738280261832/posts/MTinJWdNL8t
erbi
79

Atualização: Isso foi "consertado" no Android 7.1. https://code.google.com/p/android/issues/detail?id=213309

Desde a atualização 4.3, é basicamente impossível iniciar um serviço startForeground()sem mostrar uma notificação.

Você pode, no entanto, ocultar o ícone usando APIs oficiais ... sem necessidade de um ícone transparente: (Use NotificationCompatpara oferecer suporte a versões mais antigas)

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

Aceitei o fato de que a notificação em si ainda precisa estar lá, mas para quem ainda quiser escondê-la, posso ter encontrado uma solução alternativa para isso também:

  1. Inicie um serviço falso startForeground()com a notificação e tudo mais.
  2. Inicie o serviço real que deseja executar, também com startForeground()(mesmo ID de notificação)
  3. Pare o primeiro serviço (falso) (você pode ligar stopSelf()e ligar para onDestroy stopForeground(true)).

Voilà! Nenhuma notificação e seu segundo serviço continua em execução.

Lior Iluz
fonte
23
Obrigado por isso. Acho que algumas pessoas não percebem que existe um desenvolvimento para Android que é feito para uso comercial interno (ou seja, não tem a intenção de aparecer no mercado ou ser vendido para Joe Blow). Às vezes, as pessoas pedem coisas como "pare de mostrar a notificação de serviço" e é bom ver soluções alternativas, mesmo que não seja kosher para consumo geral.
JamieB de
5
@JamieB Não poderia estar mais de acordo ... Sugeri a Dianne Hackborn que eles precisassem repensar todo o conceito de serviços em segundo plano e talvez adicionar uma permissão para executar um serviço em segundo plano sem as notificações. Não é como se isso importasse para nós, desenvolvedores, se há uma notificação ou não - é a solicitação do USUÁRIO para removê-la! :) A propósito, você pode verificar se funciona para você também?
Lior Iluz de
1
@JamieB user875707 diz para definir o parâmetro do ícone (o id do recurso do ícone) para 0, não o id da notificação (em caso afirmativo, como a documentação diz "startForeground" não funcionará).
wangqi060934
9
Você deve, é claro, assumir que isso não funcionará em versões futuras da plataforma.
hackbod
1
@JamieB Na verdade, testei isso agora no Android 5.0.1 e ainda funciona para mim.
Lior Iluz
20

Isso não funciona mais a partir do Android 7.1 e pode violar as políticas de desenvolvedor do Google Play .

Em vez disso, faça com que o usuário bloqueie a notificação de serviço .


Aqui está minha implementação da técnica na resposta de Lior Iluz .

Código

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

Compatibilidade

Testado e trabalhando em:

  • Emulador Oficial
    • 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

Não funciona mais a partir do Android 7.1.

Sam
fonte
2
Ainda não testei, mas seria bom ter pelo menos uma permissão para isso !!! +1 para a parte "Para o Google", Meus usuários estão reclamando sobre a notificação necessária para manter o aplicativo em execução em segundo plano, eu também reclamo sobre a notificação necessária para skype / llama e qualquer outro aplicativo por aí
Rami Dabain
desculpe como este exemplo pode ser usado para ouvir alarmes. Estou desenvolvendo um aplicativo que define alarmes com alarmManager e o problema que tenho é que quando fecho o aplicativo (Aplicativos recentes-> Limpar) todos os alarmes são removidos também e estou tentando encontrar uma maneira de evitar isso
Ares91
@ Ares91, tente fazer uma pergunta StackOverflow separada para isso e certifique-se de incluir o máximo de informações possível, incluindo o código relevante. Não sei muito sobre alarmes e esta pergunta é apenas sobre serviços.
Sam
@Sam qual serviço deve ser chamado primeiro, ForegroundEnablingService ou Forground
Android Man
@AndroidManForegroundService
Sam
14

Você pode usar isso (conforme sugerido por @Kristopher Micinski):

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

ATUALIZAR:

Observe que isso não é mais permitido com versões do Android KitKat +. E tenha em mente que isso viola mais ou menos o princípio de design do Android que torna as operações em segundo plano visíveis aos usuários, conforme mencionado por @Kristopher Micinski

Snicolas
fonte
2
Observe que isso não parece ser mais aceito com o SDK 17. O serviço não irá para o primeiro plano se o drawable passado para a notificação for 0.
Snicolas
Era um bug e esperançosamente foi corrigido. A ideia dos serviços de primeiro plano é que eles são menos propensos a serem mortos, e essa é uma maneira de garantir que o usuário saiba de sua existência.
Martin Marconcini
4
Com o Android 18, a notificação não é mais invisível, mas mostra a mensagem "O aplicativo está em execução, toque para obter mais informações ou para parar o aplicativo"
Emanuel Moecklin
1
Captura de tela de um de meus clientes: 1gravity.com/k10/Screenshot_2013-07-25-12-35-49.jpg . Eu também tenho um dispositivo 4.3 e posso confirmar essa descoberta.
Emanuel Moecklin
2
Aviso: estou recebendo relatórios de falha do Sony Ericsson LT26i (Xperia S) com Android 4.1.2 (SDK 16): android.app.RemoteServiceException: Notificação inválida postada do pacote ...: Não foi possível criar ícone: StatusBarIcon (pkg = ... id = 0x7f020052 level = 0 visible = true num = 0) Também defini a id do ícone como 0 até o SDK 17. No SDK 18, defini-o como um recurso drawable válido. Talvez você precise de um id de ícone válido do SDK 16!
almisoft de
13

Aviso : embora esta resposta pareça funcionar, ela na verdade impede silenciosamente que seu serviço se torne um serviço em primeiro plano .

Resposta original:


Basta definir o ID da sua notificação para zero:

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

Um benefício que você pode obter é Serviceque será capaz de rodar em alta prioridade sem ser destruído pelo sistema Android, a menos que em alta pressão de memória.

EDITAR

Para fazê-lo funcionar com Pre-Honeycomb e Android 4.4 e superior, certifique-se de usar o NotificationCompat.Builderfornecido pela Biblioteca de Suporte v7, em vez de Notification.Builder.

Anggrayudi H
fonte
1
Isso funciona perfeitamente para mim em 4.3 (18) @vlad sol. Por que nenhum outro voto positivo ainda para esta resposta, eu me pergunto?
Rob McFeely
Tentei fazer isso no Android N Preview 4 e não funcionou. Eu tentei Notification.Builder, Support Library v4 NotificationCompat.Buildere Support Library v7 NotificationCompat.Builder. A notificação não apareceu na gaveta de notificação ou na barra de status, mas o serviço não estava sendo executado no modo de primeiro plano quando o verifiquei usando getRunningServices()e dumpsys.
Sam
@ Sam, então esse bug não aparece na versão anterior, correto?
Anggrayudi H
@AnggrayudiH, por "este bug", você quer dizer que a notificação não apareceu? Ou você quer dizer que o serviço não vai para o modo de primeiro plano?
Sam
1
Isso não funciona para mim. O processo é iniciado como primeiro plano quando id! = 0 é enviado, mas NÃO é iniciado como primeiro plano quando id = 0. Use adb shell dumpsys activity servicespara verificar.
beterraba
8

Você pode ocultar a notificação no Android 9+ usando o layout personalizado com 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>

Testado no Pixel 1, android 9. Esta solução não funciona no Android 8 ou menos

phnmnn
fonte
Boa solução alternativa. Também acho que requer ícone no android 10. Usar uma imagem de espaço reservado transparente para o ícone resolve o problema.
shyos
7

Atualização: isso não funciona mais no Android 4.3 e superior


Existe uma solução alternativa. Tente criar uma notificação sem definir o ícone, e a notificação não será exibida. Não sei como funciona, mas funciona :)

    Notification notification = new NotificationCompat.Builder(this)
            .setContentTitle("Title")
            .setTicker("Title")
            .setContentText("App running")
            //.setSmallIcon(R.drawable.picture)
            .build();
    startForeground(101,  notification);
Vladimir Petrovski
fonte
3
No Android N Preview 4, isso não funciona. O Android mostra uma (YourServiceName) is runningnotificação em vez da sua própria quando você não especifica um ícone. De acordo com CommonsWare, esse tem sido o caso desde o Android 4.3: commonsware.com/blog/2013/07/30/…
Sam
1
Fiz alguns testes hoje e confirmei que isso não funciona no Android 4.3 e superior.
Sam
5

Atualização: isso não funciona mais no Android 4.3 e superior


Eu defini o parâmetro icon para o construtor de Notificação como zero e, em seguida, passei a notificação resultante para startForeground (). Nenhum erro no log e nenhuma notificação é exibida. Não sei, porém, se o serviço foi colocado em primeiro plano com êxito - há alguma maneira de verificar?

Editado: verificado com dumpsys e, de fato, o serviço está em primeiro plano no meu sistema 2.3. Ainda não verifiquei com outras versões do sistema operacional.

Alexander Pruss
fonte
9
Use o comando "adb shell dumpsys activity services" para verificar se "isForeground" está definido como verdadeiro.
preto de
1
Ou apenas chame o Notification()construtor vazio .
johsin18
Isso funcionou para mim. A primeira vez que fiz meu serviço rodar durante a noite, ele está definitivamente em primeiro plano (além do dumpsys dizer que está) e sem notificação.
JamieB de
Observe que isso não funciona mais no Android 4.3 (API 18). O sistema operacional coloca uma notificação de espaço reservado na gaveta de notificação.
Sam
4

Bloquear a notificação de serviço em primeiro plano

A maioria das respostas aqui não funciona, quebra o serviço de primeiro plano ou viola as políticas do Google Play .

A única maneira de ocultar a notificação de maneira confiável e segura é fazer com que o usuário a bloqueie.

Android 4.1 - 7.1

A única maneira é bloquear todas as notificações do seu aplicativo:

  1. Envie o usuário para a tela de detalhes do aplicativo:

    Uri uri = Uri.fromParts("package", getPackageName(), null);
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
    startActivity(intent);
    
  2. Faça com que o usuário bloqueie as notificações do aplicativo

Observe que isso também bloqueia os toasts do seu aplicativo.

Android 8.0 - 8.1

Não vale a pena bloquear a notificação no Android O porque o SO irá apenas substituí-la por uma notificação " executando em segundo plano " ou " usando bateria ".

Android 9+

Use um canal de notificação para bloquear a notificação de serviço sem afetar suas outras notificações.

  1. Atribuir notificação de serviço ao canal de notificação
  2. Enviar usuário para as configurações do canal de notificação

    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. Faça com que o usuário bloqueie as notificações do canal

Sam
fonte
2

versão 4.3 (18) e superior ocultar notificação de serviço não é possível, mas você pode desativar o ícone, versão 4.3 (18) e abaixo é possível ocultar a notificação

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);
vlad sol
fonte
Embora essa resposta provavelmente seja correta e útil, é preferível incluir alguma explicação com ela para explicar como ela ajuda a resolver o problema. Isso se torna especialmente útil no futuro, se houver uma mudança (possivelmente não relacionada) que faça com que ele pare de funcionar e os usuários precisem entender como funcionava antes.
Kevin Brown de
Acho que o Android 4.3 é, na verdade, a API 18. Além disso, esse método apenas oculta o ícone da barra de status; o ícone ainda existe na notificação.
Sam
para ocultar o ícone da notificação, você pode adicionar uma imagem vazia mBuilder.setSmallIcon (R.drawable.blank);
vlad sol
1
estou olhando para a fonte do kitkat, e o método de ID zero não parece funcionar. se o ID for zero, o registro do serviço NÃO será atualizado para o primeiro plano.
Jeffrey Blattman,
2

Descobri que no Android 8.0 ainda é possível não usar um canal de notificação.

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

        }
    }
}

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

        }

    }

Uma notificação persistente não é mostrada, no entanto, você verá que a notificação do Android 'x apps estão sendo executados em segundo plano'.

Pete
fonte
No Android 8.1: "Notificação inválida para startForeground: java.lang.RuntimeException: canal inválido para notificação de serviço"
T-igra
Pessoalmente, acho que a apps are running in the backgroundnotificação é ainda pior do que uma notificação específica do aplicativo, então não tenho certeza se essa abordagem vale a pena.
Sam
-2

Atualização: isso não funciona mais no Android 7.1 e superior


Aqui está uma maneira de tornar o oom_adj do seu aplicativo em 1 (testado no emulador ANDROID 6.0 SDK).Adicionar um serviço temporário, em sua chamada de serviço principal startForgroundService(NOTIFICATION_ID, notificion). E, em seguida, inicie a chamada de serviço temporária startForgroundService(NOTIFICATION_ID, notificion)com o mesmo id de notificação novamente, após um tempo na chamada de serviço temporária stopForgroundService (true) para descartar a ontificação inicial.

Tshunglee
fonte
Esta é a única solução de trabalho que conheço. No entanto, já foi abordado por outra resposta .
Sam
-3

Você também pode declarar seu aplicativo como persistente.

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

Isso basicamente define seu aplicativo com uma prioridade de memória mais alta, diminuindo a probabilidade de ele ser morto.

Habitante
fonte
5
Isso está errado, apenas os aplicativos do sistema podem ser persistentes: groups.google.com/forum/?fromgroups=#!topic/android-developers/…
Snicolas
-6

Desenvolvi um reprodutor de mídia simples alguns meses atrás. Então, o que eu acredito é se você estiver fazendo algo como:

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

startService (i);

Então, o sistema não deve ser capaz de encerrar seu serviço.

referência : leia o parágrafo que discute quando o sistema interrompe o serviço

redM0nk
fonte
Eu li em alguns artigos. Se apenas fizer startService (), o sistema precisará de mais memória. Portanto, o sistema eliminará esse serviço. isso é verdade? Simplesmente não tenho tempo para fazer experiências em serviço.
Muhammad Resna Rizki Pratama
1
Muhammad, isso é verdade - o propósito de startForeground () é efetivamente dar ao serviço uma "prioridade de memória" mais alta do que os serviços de segundo plano para que ele possa sobreviver por mais tempo. O startForeground () deve usar uma notificação para que o usuário esteja mais ciente de um serviço mais persistente / difícil de eliminar que está em execução.
DustinB
Isto simplesmente não é verdade; é praticamente impossível fazer um serviço que não pode ser morto no Android. O Android elimina prontamente os serviços que não estão em primeiro plano para liberar memória.
Sam