Je sais que trop de réponses ont déjà été publiées, mais la vérité est que startForegroundService ne peut pas être corrigé au niveau d'une application et vous devez cesser de l'utiliser. Cette recommandation de Google d'utiliser l'API Service # startForeground () dans les 5 secondes après l'appel de Context # startForegroundService () n'est pas quelque chose qu'une application peut toujours faire.
Android exécute de nombreux processus simultanément et il n'y a aucune garantie que Looper appellera votre service cible qui est censé appeler startForeground () dans les 5 secondes. Si votre service cible n'a pas reçu l'appel dans les 5 secondes, vous n'avez pas de chance et vos utilisateurs connaîtront une situation ANR. Dans votre trace de pile, vous verrez quelque chose comme ceci:
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)
Si je comprends bien, Looper a analysé la file d'attente ici, a trouvé un "agresseur" et l'a simplement tué. Le système est heureux et sain maintenant, alors que les développeurs et les utilisateurs ne le sont pas, mais puisque Google limite leurs responsabilités au système, pourquoi devraient-ils se soucier des deux derniers? Apparemment non. Pourraient-ils faire mieux? Bien sûr, par exemple, ils auraient pu ouvrir la boîte de dialogue "L'application est occupée", demandant à un utilisateur de décider d'attendre ou de tuer l'application, mais pourquoi, ce n'est pas de sa responsabilité. L'essentiel est que le système soit sain maintenant.
D'après mes observations, cela se produit relativement rarement, dans mon cas, environ 1 crash en un mois pour les utilisateurs 1K. La reproduire est impossible, et même si elle est reproduite, vous ne pouvez rien faire pour la réparer définitivement.
Il y avait une bonne suggestion dans ce fil pour utiliser "bind" au lieu de "start" et puis quand le service est prêt, traiter onServiceConnected, mais encore une fois, cela signifie ne pas utiliser du tout les appels startForegroundService.
Je pense que la bonne et honnête action de la part de Google serait de dire à tout le monde que startForegourndServcie a une déficience et ne doit pas être utilisé.
La question demeure: quoi utiliser à la place? Heureusement pour nous, il existe maintenant JobScheduler et JobService, qui sont une meilleure alternative pour les services de premier plan. C'est une meilleure option, à cause de cela:
Pendant l'exécution d'un travail, le système détient un verrou de réveil au nom de votre application. Pour cette raison, aucune action n'est nécessaire pour garantir que l'appareil reste éveillé pendant la durée du travail.
Cela signifie que vous n'avez plus besoin de vous soucier de la manipulation des verrous de réveil et c'est pourquoi ce n'est pas différent des services de premier plan. Du point de vue de l'implémentation, JobScheduler n'est pas votre service, c'est celui d'un système, il gèrera probablement la file d'attente correctement, et Google ne terminera jamais son propre enfant :)
Samsung est passé de startForegroundService à JobScheduler et JobService dans leur Samsung Accessory Protocol (SAP). C'est très utile lorsque des appareils comme les montres connectées doivent parler à des hôtes comme les téléphones, où le travail doit interagir avec un utilisateur via le thread principal d'une application. Étant donné que les tâches sont publiées par le planificateur dans le thread principal, cela devient possible. Vous devez cependant vous rappeler que le travail s'exécute sur le thread principal et décharger toutes les tâches lourdes sur d'autres threads et tâches asynchrones.
Ce service exécute chaque tâche entrante sur un gestionnaire exécuté sur le thread principal de votre application. Cela signifie que vous devez décharger votre logique d'exécution vers un autre thread / gestionnaire / AsyncTask de votre choix
Le seul écueil du passage à JobScheduler / JobService est que vous devrez refactoriser l'ancien code, et ce n'est pas amusant. J'ai passé les deux derniers jours à faire exactement cela pour utiliser la nouvelle implémentation SAP de Samsung. Je regarderai mes rapports de plantage et je vous ferai savoir si je reverrai les plantage. Théoriquement, cela ne devrait pas se produire, mais il y a toujours des détails dont nous ne sommes peut-être pas conscients.
MISE À JOUR
Plus de plantages signalés par le Play Store. Cela signifie que JobScheduler / JobService n'ont pas un tel problème et le passage à ce modèle est la bonne approche pour se débarrasser du problème startForegroundService une fois pour toujours. J'espère que Google / Android le lit et finira par commenter / conseiller / fournir un guide officiel pour tout le monde.
MISE À JOUR 2
Pour ceux qui utilisent SAP et qui demandent comment SAP V2 utilise JobService, l'explication est ci-dessous.
Dans votre code personnalisé, vous devrez initialiser SAP (c'est Kotlin):
SAAgentV2.requestAgent(App.app?.applicationContext,
MessageJobs::class.java!!.getName(), mAgentCallback)
Vous devez maintenant décompiler le code de Samsung pour voir ce qui se passe à l'intérieur. Dans SAAgentV2, jetez un œil à l'implémentation de requestAgent et à la ligne suivante:
SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);
where d defined as below
private SAAdapter d;
Accédez maintenant à la classe SAAdapter et recherchez la fonction onServiceConnectionRequested qui planifie un travail à l'aide de l'appel suivant:
SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12);
SAJobService n'est qu'une implémentation d'Android'd JobService et c'est celle qui fait la planification des travaux:
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());
}
Comme vous le voyez, la dernière ligne ici utilise Android'd JobScheduler pour obtenir ce service système et planifier un travail.
Dans l'appel requestAgent, nous avons passé mAgentCallback, qui est une fonction de rappel qui recevra le contrôle lorsqu'un événement important se produit. Voici comment le rappel est défini dans mon application:
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 ici est une classe que j'ai implémentée pour traiter toutes les demandes provenant d'une smartwatch Samsung. Ce n'est pas le code complet, seulement un squelette:
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) {
}
}
Comme vous le voyez, MessageJobs nécessite également la classe MessageSocket que vous devez implémenter et qui traite tous les messages provenant de votre appareil.
En bout de ligne, ce n'est pas si simple et cela nécessite quelques recherches sur les internes et le codage, mais cela fonctionne, et surtout - il ne plante pas.