Lo so, sono già state pubblicate troppe risposte, tuttavia la verità è che startForegroundService non può essere risolto a livello di app e dovresti smettere di usarlo. La raccomandazione di Google di utilizzare l'API Service # startForeground () entro 5 secondi dalla chiamata a Context # startForegroundService () non è qualcosa che un'app può sempre fare.
Android esegue molti processi contemporaneamente e non vi è alcuna garanzia che Looper chiamerà il servizio di destinazione che dovrebbe chiamare startForeground () entro 5 secondi. Se il tuo servizio di destinazione non ha ricevuto la chiamata entro 5 secondi, sei sfortunato e i tuoi utenti riscontreranno una situazione ANR. Nella traccia dello stack vedrai qualcosa del genere:
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)
A quanto ho capito, Looper ha analizzato la coda qui, ha trovato un "maltrattatore" e l'ha semplicemente ucciso. Il sistema è felice e sano ora, mentre gli sviluppatori e gli utenti non lo sono, ma poiché Google limita le loro responsabilità al sistema, perché dovrebbero preoccuparsi degli ultimi due? Apparentemente non lo fanno. Potrebbero renderlo migliore? Naturalmente, ad esempio, avrebbero potuto pubblicare la finestra di dialogo "Applicazione occupata", chiedendo a un utente di prendere una decisione sull'attesa o sull'uccisione dell'app, ma perché preoccuparsi, non è una sua responsabilità. La cosa principale è che il sistema è integro adesso.
Dalle mie osservazioni, ciò accade relativamente raramente, nel mio caso circa 1 arresto in un mese per gli utenti 1K. Riproducerlo è impossibile e anche se è riprodotto, non c'è nulla che tu possa fare per risolverlo in modo permanente.
In questo thread c'era un buon suggerimento per usare "bind" invece di "start" e poi quando il servizio è pronto, elabora onServiceConnected, ma di nuovo significa che non usa affatto le chiamate startForegroundService.
Penso che l'azione giusta e onesta da parte di Google sarebbe quella di dire a tutti che startForegourndServcie ha un difetto e non dovrebbe essere usato.
La domanda rimane ancora: cosa usare invece? Fortunatamente per noi, ci sono JobScheduler e JobService ora, che sono un'alternativa migliore per i servizi in primo piano. È un'opzione migliore, per questo:
Mentre un lavoro è in esecuzione, il sistema mantiene un wakelock per conto della tua app. Per questo motivo, non è necessario intraprendere alcuna azione per garantire che il dispositivo rimanga sveglio per la durata del lavoro.
Significa che non devi più preoccuparti di gestire i wakelock ed è per questo che non è diverso dai servizi di primo piano. Dal punto di vista dell'implementazione JobScheduler non è il tuo servizio, è un sistema, presumibilmente gestirà la coda giusta e Google non terminerà mai il proprio figlio :)
Samsung è passato da startForegroundService a JobScheduler e JobService nel loro Samsung Accessory Protocol (SAP). È molto utile quando dispositivi come gli smartwatch devono parlare con host come telefoni, in cui il lavoro deve interagire con un utente attraverso il thread principale di un'app. Poiché i lavori vengono registrati dallo scheduler nel thread principale, diventa possibile. Tuttavia, è necessario ricordare che il lavoro è in esecuzione sul thread principale e scaricare tutti gli elementi pesanti su altri thread e attività asincrone.
Questo servizio esegue ogni processo in entrata su un gestore in esecuzione sul thread principale dell'applicazione. Ciò significa che è necessario scaricare la logica di esecuzione su un altro thread / gestore / AsyncTask di propria scelta
L'unico inconveniente del passaggio a JobScheduler / JobService è che dovrai refactificare il vecchio codice e non è divertente. Ho passato gli ultimi due giorni a fare proprio questo per usare la nuova implementazione SAP di Samsung. Guarderò i miei rapporti sugli arresti anomali e ti informerò se visualizzeranno nuovamente gli arresti anomali. Teoricamente non dovrebbe accadere, ma ci sono sempre dettagli di cui potremmo non essere a conoscenza.
AGGIORNARE
Non ci sono più arresti anomali segnalati dal Play Store. Significa che JobScheduler / JobService non hanno questo problema e il passaggio a questo modello è l'approccio giusto per sbarazzarsi del problema startForegroundService una volta per sempre. Spero che Google / Android lo legga e alla fine commenterà / consigli / fornirà una guida ufficiale per tutti.
AGGIORNAMENTO 2
Per coloro che usano SAP e chiedono come SAP V2 utilizza JobService, la spiegazione è di seguito.
Nel tuo codice personalizzato dovrai inizializzare SAP (è Kotlin):
SAAgentV2.requestAgent(App.app?.applicationContext,
MessageJobs::class.java!!.getName(), mAgentCallback)
Ora devi decompilare il codice Samsung per vedere cosa succede dentro. In SAAgentV2 dai un'occhiata all'implementazione di requestAgent e alla seguente riga:
SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);
where d defined as below
private SAAdapter d;
Vai subito alla classe SAAdapter e trova la funzione onServiceConnectionRequested che pianifica un lavoro utilizzando la seguente chiamata:
SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12);
SAJobService è solo un'implementazione di JobService Android e questo è quello che fa una pianificazione dei lavori:
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());
}
Come vedi, l'ultima riga qui utilizza JobScheduler per Android per ottenere questo servizio di sistema e pianificare un lavoro.
Nella richiesta requestAgent abbiamo superato mAgentCallback, che è una funzione di callback che riceverà il controllo quando si verifica un evento importante. Ecco come viene definito il callback nella mia app:
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 qui è una classe che ho implementato per elaborare tutte le richieste provenienti da uno smartwatch Samsung. Non è il codice completo, solo uno scheletro:
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) {
}
}
Come vedi, MessageJobs richiede anche la classe MessageSocket che dovrai implementare e che elabora tutti i messaggi provenienti dal tuo dispositivo.
In conclusione, non è così semplice e richiede un po 'di scavo per gli interni e la codifica, ma funziona e, soprattutto, non si blocca.