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.