Context.startForegroundService () não chamou Service.startForeground ()

258

Estou usando a Serviceclasse no sistema operacional Android.

Eu pretendo usar o Serviceem segundo plano.

A documentação do Android afirma que

Se seu aplicativo atingir o nível 26 ou superior da API, o sistema impõe restrições ao uso ou criação de serviços em segundo plano, a menos que o aplicativo em si esteja em primeiro plano. Se um aplicativo precisar criar um serviço em primeiro plano, ele deverá chamar startForegroundService().

Se você usar startForegroundService(), Servicelança o seguinte erro.

Context.startForegroundService() did not then call
Service.startForeground() 

O que há de errado nisso?

Cara legal
fonte
IOW, forneça um exemplo mínimo reproduzível . Isso incluiria todo o rastreamento da pilha Java e o código que está acionando a falha.
CommonsWare
3
O bug ainda está aqui na API 26 e 27 (27.0.3). As versões afetadas do Android são 8.0 e 8.1 Você pode reduzir o número de falhas adicionando o startForeground () ao onCreate () e ao onStartCommand (), mas as falhas ainda acontecem para alguns usuários. A única maneira de corrigi-lo no atm é targetSdkVersion 25 no seu build.gradle.
Alex
1
Estou usando essa solução alternativa agora. funciona como charme theandroiddeveloper.com/blog/...
Prasad Pawar
1
Eu tenho o mesmo problema. Corrijo esse problema. Compartilhei minha implementação neste tópico stackoverflow.com/questions/55894636/…
Beyazid

Respostas:

99

Nos documentos do Google no Android 8.0, o comportamento muda :

O sistema permite que os aplicativos chamem Context.startForegroundService () mesmo enquanto o aplicativo estiver em segundo plano. No entanto, o aplicativo deve chamar o método startForeground () desse serviço dentro de cinco segundos após a criação do serviço.

Solução: Chamada startForeground()em onCreate()para o Serviceque você usaContext.startForegroundService()

Veja também: Limites de execução em segundo plano para Android 8.0 (Oreo)

zhuzhumouse
fonte
19
Fiz isso no onStartCommandmétodo, mas ainda estou recebendo esse erro. Eu liguei startForegroundService(intent)no meu MainActivity. Talvez o serviço seja iniciado muito devagar. Acho que o limite de cinco segundos não deve existir antes que eles possam prometer que o serviço é iniciado imediatamente.
Kimi Chiu
29
Definitivamente, o período de 5 segundos não é suficiente, essa exceção ocorre com frequência nas sessões de depuração. Eu suspeito que isso também aconteceria no modo de lançamento de tempos em tempos. E sendo EXCEÇÃO FATAL, apenas trava o aplicativo! Eu tentei pegá-lo com Thread.setDefaultUncaughtExceptionHandler (), mas mesmo ao chegar lá e ignorar o Android congela o aplicativo. Isso porque essa exceção é acionada a partir de handleMessage () e o loop principal termina efetivamente ... procurando uma solução alternativa.
Southerton
Liguei para o método Context.startForegroundService () no receptor de transmissão. Então, como lidar com essa situação? porque onCreate () não está disponível no receptor de radiodifusão
Anand Savjani
6
@southerton Acho que você deveria chamá-lo em onStartCommand()vez de onCreate, porque se você fechar o serviço e iniciá-lo novamente, ele pode ir para onStartCommand()sem chamar onCreate...
desenvolvedor android
1
Podemos verificar a resposta da equipe do Google aqui issuetracker.google.com/issues/76112072#comment56
Prags
82

Liguei ContextCompat.startForegroundService(this, intent)para iniciar o serviço então

Em serviço onCreate

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

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}
humazed
fonte
47
Eu também. Mas ainda estou recebendo esse erro ocasionalmente. Talvez o Android não possa garantir que ele ligará onCreateem 5 segundos. Portanto, eles devem redesenhar antes de nos forçar a seguir as regras.
Kimi Chiu
5
Eu estava chamando startForeground em onStartCommand () e, ocasionalmente, estava recebendo esse erro. Mudei para onCreate e não o vejo desde (dedos cruzados).
Tyler
4
Isso cria uma notificação local no Oreo dizendo "APP_NAME está em execução. Toque para fechar ou ver informações". Como parar de mostrar essa notificação?
Artist404
6
Não, a equipe do Android afirmou que esse é um comportamento pretendido. Acabei de redesenhar meu aplicativo. Isto é ridículo. Sempre é necessário redesenhar os aplicativos devido a esse "comportamento pretendido". É a terceira vez.
Kimi Chiu
12
A principal causa desse problema é que o serviço foi parado antes de ser promovido para o primeiro plano. Mas a afirmação não parou depois que o serviço foi destruído. Você pode tentar reproduzir isso adicionando StopServiceapós ligar startForegroundService.
Kimi Chiu
55

Por que esse problema está acontecendo é porque a estrutura do Android não pode garantir que seu serviço seja iniciado em 5 segundos, mas, por outro lado, a estrutura possui um limite estrito de notificação em primeiro plano, que deve ser acionada em 5 segundos, sem verificar se a estrutura tentou iniciar o serviço .

Esse é definitivamente um problema de estrutura, mas nem todos os desenvolvedores que enfrentam esse problema estão fazendo o melhor possível:

  1. startForegrounduma notificação deve estar em ambos onCreatee onStartCommand, porque se o seu serviço já estiver criado e, de alguma forma, sua atividade estiver tentando iniciá-lo novamente, onCreatenão será chamada.

  2. o ID da notificação não deve ser 0, caso contrário, a mesma falha ocorrerá, mesmo que não seja o mesmo motivo.

  3. stopSelfnão deve ser chamado antes startForeground.

Com todos os itens acima de 3, esse problema pode ser reduzido um pouco, mas ainda não é uma correção, a correção real ou, digamos, a solução alternativa é fazer o downgrade da versão sdk de destino para 25.

E observe que provavelmente o Android P ainda continuará com esse problema porque o Google se recusa a entender o que está acontecendo e não acredita que isso seja culpa deles, leia # 36 e # 56 para obter mais informações

ZhouX
fonte
9
Por que onCreate e onStartCommand? Você pode colocá-lo no onStartCommand?
Rcell
stopSelf não deve ser chamado antes de startForeground => ou diretamente depois, porque parece cancelar o "startForeground" quando você o faz.
Frank
1
Eu executei alguns testes e, mesmo que onCreate não seja chamado se o serviço já estiver criado, você não precisará chamar startForeground novamente. Portanto, você pode chamá-lo apenas no método onCreate e não no onStartCommand. Provavelmente, eles consideram a instância única de serviço como estando em primeiro plano após a primeira chamada até o final.
Lxu
Estou usando 0 como o ID de notificação da chamada inicial startForeground. Mudá-lo para 1 corrige o problema para mim (espero)
mr5 09/01
Então qual é a solução?
IgorGanapolsky 10/04
35

Eu sei que muitas respostas já foram publicadas, mas a verdade é: startForegroundService não pode ser corrigido no nível do aplicativo e você deve parar de usá-lo. A recomendação do Google de usar a API Service # startForeground () dentro de 5 segundos após a chamada do Context # startForegroundService () não é algo que um aplicativo sempre possa fazer.

O Android executa muitos processos simultaneamente e não há garantia de que o Looper ligue para o serviço de destino que deveria chamar startForeground () dentro de 5 segundos. Se o serviço de destino não recebeu a ligação dentro de 5 segundos, você estará sem sorte e seus usuários enfrentarão a situação de ANR. No rastreamento da pilha, você verá algo assim:

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
  at android.os.MessageQueue.next (MessageQueue.java:326)
  at android.os.Looper.loop (Looper.java:181)
  at android.app.ActivityThread.main (ActivityThread.java:6981)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

Pelo que entendi, Looper analisou a fila aqui, encontrou um "agressor" e simplesmente a matou. O sistema está feliz e saudável agora, enquanto desenvolvedores e usuários não, mas como o Google limita suas responsabilidades ao sistema, por que eles deveriam se preocupar com os dois últimos? Aparentemente eles não. Eles poderiam melhorar? Obviamente, por exemplo, eles poderiam ter exibido a caixa de diálogo "O aplicativo está ocupado", pedindo ao usuário que tome uma decisão sobre aguardar ou matar o aplicativo, mas por que se preocupar, não é responsabilidade deles. O principal é que o sistema esteja saudável agora.

Pelas minhas observações, isso acontece relativamente raramente, no meu caso, aproximadamente 1 falha em um mês para usuários de 1K. É impossível reproduzi-lo e, mesmo que seja reproduzido, não há nada que você possa fazer para corrigi-lo permanentemente.

Houve uma boa sugestão nesse encadeamento para usar "bind" em vez de "start" e, quando o serviço estiver pronto, processe onServiceConnected, mas, novamente, isso significa não usar as chamadas startForegroundService.

Penso que a ação correta e honesta do lado do Google seria dizer a todos que o startForegourndServcie tem uma deficiência e não deve ser usado.

A questão ainda permanece: o que usar? Felizmente para nós, agora existem JobScheduler e JobService, que são uma alternativa melhor para os serviços em primeiro plano. É uma opção melhor, por causa disso:

Enquanto um trabalho está em execução, o sistema mantém um wakelock em nome do seu aplicativo. Por esse motivo, você não precisa executar nenhuma ação para garantir que o dispositivo permaneça acordado durante a duração do trabalho.

Isso significa que você não precisa mais se preocupar em lidar com wakelocks e é por isso que não é diferente dos serviços em primeiro plano. Do ponto de vista da implementação, o JobScheduler não é o seu serviço, é um sistema, presumivelmente ele manipulará a fila corretamente e o Google nunca encerrará seu próprio filho :)

A Samsung mudou de startForegroundService para JobScheduler e JobService em seu Samsung Accessory Protocol (SAP). É muito útil quando dispositivos como smartwatches precisam conversar com hosts como telefones, onde o trabalho precisa interagir com um usuário por meio do thread principal de um aplicativo. Como os trabalhos são publicados pelo planejador no encadeamento principal, torna-se possível. Lembre-se, porém, de que o trabalho está sendo executado no encadeamento principal e descarrega todo o material pesado para outros encadeamentos e tarefas assíncronas.

Este serviço executa cada trabalho recebido em um manipulador em execução no encadeamento principal do seu aplicativo. Isso significa que você deve descarregar sua lógica de execução para outro thread / manipulador / AsyncTask de sua escolha

A única armadilha da mudança para o JobScheduler / JobService é que você precisará refatorar o código antigo, e isso não é divertido. Passei os últimos dois dias fazendo exatamente isso para usar a nova implementação SAP da Samsung. Vou assistir meus relatórios de falhas e informar se você vê as falhas novamente. Teoricamente, isso não deveria acontecer, mas sempre há detalhes dos quais podemos não estar cientes.

ATUALIZAÇÃO Não há mais falhas relatadas pela Play Store. Isso significa que o JobScheduler / JobService não tem esse problema e a mudança para esse modelo é a abordagem correta para se livrar do problema startForegroundService de uma vez por todas. Espero que o Google / Android leia e acabe comentando / aconselhando / fornecendo uma orientação oficial para todos.

ATUALIZAÇÃO 2

Para aqueles que usam o SAP e perguntam como o SAP V2 utiliza a explicação do JobService, veja abaixo.

No seu código personalizado, você precisará inicializar o SAP (é Kotlin):

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

Agora você precisa descompilar o código da Samsung para ver o que está acontecendo lá dentro. No SAAgentV2, consulte a implementação requestAgent e a seguinte linha:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

Vá para a classe SAAdapter agora e encontre a função onServiceConnectionRequested que agenda um trabalho usando a seguinte chamada:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

O SAJobService é apenas uma implementação do Android'd JobService e é esse que realiza um agendamento de tarefas:

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

Como você vê, a última linha aqui usa o JobScheduler do Android para obter esse serviço do sistema e agendar um trabalho.

Na chamada requestAgent, passamos pelo mAgentCallback, que é uma função de retorno de chamada que receberá controle quando um evento importante acontecer. É assim que o retorno de chamada é definido no meu aplicativo:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

MessageJobs aqui é uma classe que eu implementei para processar todas as solicitações provenientes de um smartwatch Samsung. Não é o código completo, apenas um esqueleto:

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

Como você vê, o MessageJobs também requer a classe MessageSocket que você precisaria implementar e processa todas as mensagens vindas do seu dispositivo.

Resumindo, não é tão simples e requer alguma pesquisa interna e codificação, mas funciona e o mais importante - não trava.

Oleg Gryb
fonte
Boa resposta, mas há um grande problema, JobIntentServiceé executado imediatamente como um IntentServiceOreo abaixo, mas programa um trabalho acima e acima do Oreo, para JobIntentServiceque não seja iniciado imediatamente. Mais informações
CopsOnRoad 5/10
1
@CopsOnRoad É muito útil e começa imediatamente no meu caso, como escrevi, é usado para interações em tempo real entre o telefone e um smartwatch. De jeito nenhum, um usuário esperaria 15 minutos para enviar dados entre esses dois. Funciona muito bem e nunca falha.
Oleg Gryb 5/10/19
1
Parece legal, faria sua resposta valer a pena se você puder compartilhar algum código de amostra, eu sou novo no Android e descobri que Job...leva tempo para começar e é uma versão avançada do AlarmManager.
CopsOnRoad 5/10/19
2
Provavelmente eu vou quando o tempo permite: eu vou ter de dissociar-lo de código específico de costume e de compilação algumas libs Propriedade SAMSUNG
Oleg Gryb
1
@batmaci - O JobService é usado internamente pela SAP. Consulte a Atualização 2 no OP para obter detalhes.
Oleg Gryb 18/01
32

Seu aplicativo falhará se você ligar Context.startForegroundService(...)e depois ligar Context.stopService(...)antes da Service.startForeground(...)chamada.

Tenho uma reprodução clara aqui ForegroundServiceAPI26

Abri um bug no seguinte: Rastreador de problemas do Google

Vários erros foram abertos e fechados. Não será corrigido.

Espero que o meu, com etapas claras de reprodução, faça o corte.

Informações fornecidas pela equipe do Google

Rastreador de problemas do Google Comentário 36

Este não é um bug do framework; é intencional. Se o aplicativo iniciar uma instância de serviço startForegroundService(), ele deve fazer a transição para o estado de primeiro plano e mostrar a notificação. Se a instância do serviço for interrompida antes de startForeground()ser chamada, essa promessa não será cumprida: isso é um bug no aplicativo.

Re # 31 , publicar um serviço que outros aplicativos podem iniciar diretamente é fundamentalmente inseguro. Você pode atenuar um pouco isso tratando todas as ações de início desse serviço como exigentes startForeground(), embora obviamente isso possa não ser o que você tinha em mente.

Rastreador de problemas do Google Comentário 56

Existem alguns cenários diferentes que levam ao mesmo resultado aqui.

A questão semântica definitiva, de que é simplesmente um erro iniciar algo, startForegroundService()mas negligenciar a transição para o primeiro plano startForeground(), é apenas isso: uma questão semântica. Isso é tratado como um bug de aplicativo, intencionalmente. Parar o serviço antes de fazer a transição para o primeiro plano é um erro de aplicativo. Esse foi o cerne do OP e é por isso que esse problema foi marcado como "funcionando como pretendido".

No entanto, também há perguntas sobre a detecção espúria desse problema. Isso está sendo tratado como um problema genuíno, embora esteja sendo rastreado separadamente desse problema específico do rastreador de erros. Não somos surdos à queixa.

swooby
fonte
2
A melhor atualização que eu vi é issuetracker.google.com/issues/76112072#comment56 . Reescrevi meu código para usar Context.bindService, o que evita completamente a chamada problemática para Context.startForegroundService. Você pode ver o meu código de exemplo no github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/...
swooby
sim, como escrevi, o bind deve funcionar, mas eu já mudei para o JobServive e não mudarei nada, a menos que este também seja quebrado ':)
Oleg Gryb
20

Eu pesquisei sobre isso por alguns dias e consegui a solução. Agora, no Android O, você pode definir a limitação do fundo como abaixo

O serviço que está chamando uma classe de serviço

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    SettingActivity.this.startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

e a classe de serviço deve ser como

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}
Ahmad Arslan
fonte
3
Você tentou em algum aplicativo que possui pelo menos vários milhares de usuários? Eu tentei, alguns usuários ainda têm problemas de falha.
Alex
Não, não, estou usando o código exato e não vejo os clientes travando.
Ahmad Arslan
Quantos usuários você tem? Vi ~ 10-30 + falhas diariamente (com o erro) no aplicativo que tinha 10 mil usuários por dia. Obviamente, isso acontece apenas para usuários com o Android 8.0 e o Android 8.1, caso o targetSdkVersion seja 27. Todos os problemas desaparecem para 0 falhas logo após eu definir o targetSdkVersion para 25.
Alex
única 2200 usuário: - / mas não obteve qualquer acidente
Ahmad Arslan
1
@ Jeeva, na verdade não. Atm, eu uso o targetSdkVersion 25 com o compileSdkVersion 27. Parece o melhor caminho após muitas experiências ... Espero que eles terminem developer.android.com/topic/libraries/architecture/… antes de agosto de 2018 porque android-developers.googleblog .com / 2017/12 /…
Alex
16

Eu tenho um widget que faz atualizações relativamente frequentes quando o dispositivo está ativado e eu estava vendo milhares de falhas em apenas alguns dias.

O gatilho do problema

Até notei o problema no meu Pixel 3 XL, quando não achei que o dispositivo tivesse muita carga. E todo e qualquer caminho de código foi coberto startForeground(). Mas então percebi que, em muitos casos, meu serviço realiza o trabalho muito rapidamente. Acredito que o gatilho do meu aplicativo foi que o serviço estava terminando antes que o sistema realmente mostrasse uma notificação.

A solução alternativa / solução

Consegui me livrar de todas as falhas. O que fiz foi remover a chamada para stopSelf(). (Eu estava pensando em adiar a parada até ter certeza de que a notificação foi exibida, mas não quero que o usuário a veja se não for necessário.) Quando o serviço estiver inativo por um minuto ou o sistema destrói-o normalmente sem lançar nenhuma exceção.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}
Roy Solberg
fonte
11

Apenas um alerta, porque eu perdi muitas horas com isso. Eu continuava recebendo essa exceção, embora estivesse ligando startForeground(..)como a primeira coisa a entrar onCreate(..). No final, descobri que o problema foi causado pelo uso NOTIFICATION_ID = 0. Usar qualquer outro valor parece corrigir isso.

tobalr
fonte
No meu caso eu estava usando ID 1, graças, assuntos foi resolvido
Amin Pinjari
4
Estou usando o ID 101, ainda recebo esse erro algumas vezes.
CopsOnRoad
9

Eu tenho uma solução alternativa para esse problema. Eu verifiquei essa correção no meu próprio aplicativo (300K + DAU), que pode reduzir pelo menos 95% desse tipo de falha, mas ainda não pode 100% evitar esse problema.

Esse problema ocorre mesmo quando você liga para startForeground () logo após o início do serviço, conforme documentado pelo Google. Pode ser porque o processo de criação e inicialização do serviço já custa mais de 5 segundos em muitos cenários; não importa quando e onde você chama o método startForeground (), essa falha é inevitável.

Minha solução é garantir que startForeground () seja executado dentro de 5 segundos após o método startForegroundService (), não importa quanto tempo seu serviço precise ser criado e inicializado. Aqui está a solução detalhada.

  1. Não use startForegroundService em primeiro lugar, use bindService () com o sinalizador auto_create. Ele aguardará a inicialização do serviço. Aqui está o código, meu serviço de amostra é MusicService:

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
  2. Então aqui está a implementação do MusicBinder:

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
  3. A parte mais importante, implementação de MusicService, método forceForeground () garantirá que o método startForeground () seja chamado logo após startForegroundService ():

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
  4. Se você deseja executar o trecho de código da etapa 1 com uma intenção pendente, como se deseja iniciar um serviço em primeiro plano em um widget (um clique no botão do widget) sem abrir o aplicativo, é possível agrupar o trecho de código em um receptor de transmissão e dispare um evento de transmissão em vez de iniciar o comando service.

Isso é tudo. Espero que ajude. Boa sorte.

Hexise
fonte
7

Como todo mundo que visita aqui está sofrendo a mesma coisa, quero compartilhar minha solução que ninguém mais tentou antes (nesta pergunta de qualquer maneira). Posso garantir que ele está funcionando, mesmo em um ponto de interrupção parado, que confirma esse método.

A questão é ligar Service.startForeground(id, notification)do próprio serviço, certo? Infelizmente, o Android Framework não garante a chamada Service.startForeground(id, notification)dentro Service.onCreate()de 5 segundos, mas lança a exceção de qualquer maneira, então eu vim dessa maneira.

  1. Vincule o serviço a um contexto com um fichário do serviço antes de chamar Context.startForegroundService()
  2. Se a ligação for bem-sucedida, chame a Context.startForegroundService() partir da conexão de serviço e chame imediatamente Service.startForeground() dentro da conexão de serviço.
  3. NOTA IMPORTANTE: Chame o Context.bindService()método dentro de um try-catch porque, em algumas ocasiões, a chamada pode gerar uma exceção; nesse caso, você precisa confiar Context.startForegroundService()diretamente na chamada e espero que não falhe. Um exemplo pode ser um contexto de receptor de transmissão, no entanto, a obtenção do contexto do aplicativo não gera uma exceção nesse caso, mas a utilização direta do contexto.

Isso funciona mesmo quando estou aguardando um ponto de interrupção após ligar o serviço e antes de acionar a chamada "startForeground". Esperar entre 3-4 segundos não aciona a exceção, e após 5 segundos lança a exceção. (Se o dispositivo não puder executar duas linhas de código em 5 segundos, é hora de jogar isso no lixo.)

Portanto, comece criando uma conexão de serviço.

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

Dentro do seu serviço, crie um fichário que retorne a instância do seu serviço.

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

Em seguida, tente vincular o serviço a partir desse contexto. Se for bem-sucedido, ele chamará o ServiceConnection.onServiceConnected()método da conexão de serviço que você está usando. Em seguida, lide com a lógica no código mostrado acima. Um código de exemplo ficaria assim:

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

Parece estar funcionando nos aplicativos que eu desenvolvo, portanto deve funcionar quando você tentar também.

Furkan Yurdakul
fonte
5

Problema com o Android O API 26

Se você interromper o serviço imediatamente (para que seu serviço realmente não seja executado (redação / compreensão) e você esteja dentro do intervalo ANR, ainda precisará chamar startForeground antes de stopSelf

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

Tentei esta abordagem, mas ainda cria um erro: -

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

Estou usando isso até que o erro seja resolvido

mContext.startService(playIntent);
Andy Cass
fonte
3
Deve ser Util.SDK_INT> = 26, não apenas maior
Andris
4

Estou enfrentando o mesmo problema e depois de passar algum tempo encontrei uma solução, você pode tentar o código abaixo. Se você estiver usando Service, coloque esse código em onCreate; caso contrário Intent Service, use esse código em onHandleIntent.

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }
Sambhaji Karad
fonte
4

Estive pesquisando esse problema e foi isso que descobri até agora. Essa falha pode ocorrer se tivermos um código semelhante a este:

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

A exceção é lançada no seguinte bloco do código:

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

Este método é executado antes onCreate()de MyForegroundServiceporque horários Android a criação do serviço no manipulador de thread principal, mas bringDownServiceLockedé chamado em um BinderThread, wich é uma condição de corrida. Isso significa que MyForegroundServicenão teve a chance de ligar, o startForegroundque causará o acidente.

Para consertar isso, precisamos garantir que isso bringDownServiceLockednão seja chamado antes onCreate()de MyForegroundService.

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

Usando transmissões indesejadas temos certeza de que a transmissão não se perde e stopReceiverrecebe a parada intenção assim que foi registrado em onCreate()de MyForegroundService. A essa altura, já ligamos startForeground(...). Também precisamos remover essa transmissão pegajosa para impedir que o stopReceiver seja notificado na próxima vez.

Observe que o método sendStickyBroadcastestá obsoleto e eu o uso apenas como uma solução temporária para corrigir esse problema.

makovkastar
fonte
Você deve chamá-lo em vez de context.stopService (serviceIntent) quando quiser parar o serviço.
makovkastar
4

Tantas respondem, mas nenhuma funcionou no meu caso.

Eu comecei o serviço assim.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

E no meu serviço no onStartCommand

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

E não se esqueça de definir NOTIFICATION_ID diferente de zero

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

Então, tudo estava perfeito, mas ainda estava travando no 8.1, então a causa era a seguinte.

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

Chamei stop foreground com notificação de remoção, mas uma vez que o serviço de notificação removida se torne em segundo plano e o serviço em segundo plano não possa ser executado no Android O a partir de segundo plano. iniciado após o envio recebido.

Palavra tão mágica é

   stopSelf();

Até agora, qualquer motivo pelo qual seu serviço esteja falhando, siga todas as etapas acima e aproveite.

arenoso
fonte
if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (true); } o que é isso se mais para? nos dois casos, você estava fazendo o mesmo, stopForeground (true);
user3135923
Eu acredito que você pretendia colocar stopSelf (); dentro do bloco else em vez de stopForeground (true);
Justin Stanley
1
Sim, @JustinStanley use stopSelf ();
sandy
Não deve startForegroundser chamado onCreate?
Aba
Estou usando o NotificationManager e não a classe Notification. Como posso implementar isso?
Prajwal W
3

https://developer.android.com/reference/android/content/Context.html#startForegroundService(android.content.Intent)

Semelhante ao startService (Intent), mas com uma promessa implícita de que o Serviço chamará startForeground (int, android.app.Notification) assim que iniciar a execução. O serviço recebe um período de tempo comparável ao intervalo ANR para fazer isso; caso contrário, o sistema interromperá automaticamente o serviço e declarará o aplicativo ANR.

Ao contrário do startService comum (Intent), esse método pode ser usado a qualquer momento, independentemente de o aplicativo que hospeda o serviço estar em um estado de primeiro plano.

certifique-se de ligar o Service.startForeground(int, android.app.Notification)onCreate () para garantir que ele seja Context.startService(Intent)chamado Service.startForeground(int, android.app.Notification).. se você tiver alguma condição que possa impedir você de fazer isso, é melhor usar o normal e ligar para você mesmo.

Parece que ele Context.startForegroundService()adiciona um cão de guarda para garantir que você ligou Service.startForeground(int, android.app.Notification)antes de ser destruído ...

Alécio Carvalho
fonte
3

Por favor, não chame nenhum StartForgroundServices dentro do método onCreate () , você deve chamar os serviços StartForground em onStartCommand () depois de criar o thread de trabalho, caso contrário você receberá ANR sempre, portanto, não escreva login complexo no thread principal de onStartCommand () ;

public class Services extends Service {

    private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

EDIT: Cuidado! A função startForeground não pode aceitar 0 como primeiro argumento; ela gera uma exceção! neste exemplo contém chamada de função incorreta, altere 0 para sua própria const, que não poderia ser 0 ou ser maior que Max (Int32)

Shalu Gupta
fonte
2

Mesmo depois de chamar o startForegroundem Service, Ele trava em alguns dispositivos, se nós chamamos stopServiceimediatamente antes onCreateé chamado. Portanto, corrigi esse problema iniciando o serviço com um sinalizador adicional:

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

e adicionou um check-in noStartCommand para ver se foi iniciado:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //call startForeground first
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

PS Se o serviço não estivesse em execução, ele seria iniciado primeiro, o que é uma sobrecarga.

Gaurav Singla
fonte
2

Você deve adicionar uma permissão abaixo para o dispositivo Android 9 quando usar o target sdk 28 ou posterior ou a exceção sempre ocorrerá:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Shrdi
fonte
1

Acabei de verificar o PendingIntentnulo ou não antes de chamar a context.startForegroundService(service_intent)função.

isso funciona para mim

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}
SaimumIslam27
fonte
Na verdade, isso apenas chuta o serviço para startService em vez de startForegroundService, se nulo. A instrução if precisaria ser aninhada; caso contrário, o aplicativo falharia ao tentar usar serviços em uma versão posterior em algum momento.
a54studio
1

basta chamar o método startForeground imediatamente após a criação do Service ou IntentService. como isso:

import android.app.Notification;
public class AuthenticationService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}
Hossein Karami
fonte
1
EXCEÇÃO FATAL: android.app.RemoteServiceException principal: notificação inválida para startForeground: java.lang.RuntimeException: canal inválido para notificação de serviço: Notificação (canal = nula pri = 0 contentView = vibração nula = som nulo = padrões nulos = 0x0 sinalizadores = 0x40 color = 0x00000000 vis = PRIVATE) em android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) em android.os.Handler.dispatchMessage (Handler.java:106) em android.os.Looper.loop (Looper. java: 164) em android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry
1

Ok, algo que notei sobre isso que pode ajudar alguns outros também. Isso é estritamente de teste para ver se eu consigo descobrir como corrigir as ocorrências que estou vendo. Por uma questão de simplicidade, digamos que eu tenho um método que chama isso do apresentador.

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

Isso falhará com o mesmo erro. O Serviço NÃO será iniciado até que o método esteja completo, portanto, não onCreate()no serviço.

Portanto, mesmo se você atualizar a interface do usuário do thread principal, se tiver algo que possa sustentar esse método depois dele, ele não iniciará a tempo e fornecerá o temido erro de primeiro plano. No meu caso, estávamos carregando algumas coisas em uma fila e cada uma foi chamada startForegroundService, mas alguma lógica estava envolvida com cada uma delas em segundo plano. Portanto, se a lógica demorar muito para concluir esse método, uma vez que foram chamados de volta, o tempo de falha. O velho startServicesimplesmente ignorou e seguiu seu caminho, e desde que o chamamos cada vez, a próxima rodada terminava.

Isso me deixou pensando, se eu chamasse o serviço de um thread em segundo plano, ele não poderia ser totalmente vinculado no início e executado imediatamente, então comecei a experimentar. Mesmo que isso NÃO o inicie imediatamente, ele não trava.

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

Não vou fingir saber por que ele não falha, embora eu suspeite que isso o force a esperar até que o thread principal possa lidar com isso em tempo hábil. Sei que não é ideal vinculá-lo ao segmento principal, mas como meu uso chama isso em segundo plano, não estou realmente preocupado se ele esperar até que seja concluído em vez de travar.

a54studio
fonte
você está iniciando seu serviço em segundo plano ou em atividade?
Mateen Chaudhry
Fundo. O problema é que o Android não pode garantir que o serviço será iniciado no tempo previsto. Alguém poderia pensar que eles apenas lançariam um erro para ignorá-lo, se você quiser, mas, infelizmente, não o Android. Tem que ser fatal. Isso ajudou em nosso aplicativo, mas não o corrigiu totalmente.
a54studio
1

Estou adicionando algum código na resposta @humazed. Portanto, não há notificação inicial. Pode ser uma solução alternativa, mas funciona para mim.

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

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

Estou adicionando transparentColor em ícone pequeno e cor na notificação. Vai funcionar.

KHEM RAJ MEENA
fonte
0

Corrigi o problema ao iniciar o serviço em startService(intent)vez de Context.startForeground()ligar startForegound()imediatamente depois super.OnCreate(). Além disso, se você iniciar o serviço na inicialização, poderá iniciar a Atividade que inicia o serviço na transmissão de inicialização. Embora não seja uma solução permanente, funciona.

melianor
fonte
em alguns dispositivos Xiaomi, as atividades não podem ser iniciadas em segundo plano
Mateen Chaudhry
0

Atualizando dados no onStartCommand(...)

onBind (...)

onBind(...)é um evento de ciclo de vida melhor para iniciar startForegroundvs. onCreate(...)porque onBind(...)passa em um Intentque pode conter dados importantes no Bundlenecessário para inicializar o Service. No entanto, não é necessário, como onStartCommand(...)é chamado quando o Serviceé criado pela primeira vez ou chamado depois.

onStartCommand (...)

startForegroundem onStartCommand(...)é importante, a fim de atualizar o Serviceuma vez que já foi criado.

Quando ContextCompat.startForegroundService(...)é chamado depois que um Servicefoi criado onBind(...)e onCreate(...)não é chamado. Portanto, os dados atualizados podem ser transmitidos onStartCommand(...)através do Intent Bundlepara atualizar dados no Service.

Amostra

Eu estou usando esse padrão para implementar o PlayerNotificationManagerno Coinverse aplicativo de notícias criptomoeda.

Activity / Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}
Adam Hurwitz
fonte
-1

Serviço

class TestService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")

        val nBuilder = NotificationCompat.Builder(this, "all")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("TestService")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        startForeground(1337, nBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val rtn = super.onStartCommand(intent, flags, startId)

        if (intent?.action == STOP_ACTION) {
            Log.d(TAG, "onStartCommand -> STOP")
            stopForeground(true)
            stopSelf()
        } else {
            Log.d(TAG, "onStartCommand -> START")
        }

        return rtn
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {

        private val TAG = "TestService"
        private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
        }

        fun stop(context: Context) {
            val intent = Intent(context, TestService::class.java)
            intent.action = STOP_ACTION
            ContextCompat.startForegroundService(context, intent)
        }

    }

}

Testador

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)

start_test_service.setOnClickListener {
    TestService.start(this@MainActivity)
    TestService.stop(this@MainActivity)
}

Resultado

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
JeanK
fonte