Pourquoi mon service Android est-il redémarré lorsque le processus est arrêté, même si j’ai utilisé START_NOT_STICKY?

Mon application utilise un modèle dans lequel je lance un service avec Context # startService () , ainsi que le lie avec Context # bindService () . C’est pour que je puisse contrôler la durée de vie du service indépendamment du fait que des clients y sont actuellement liés. Cependant, j’ai remarqué récemment que chaque fois que mon application est tuée par le système, elle redémarre rapidement tous les services en cours d’exécution. À ce stade, il ne sera jamais demandé au service de s’arrêter, ce qui entraîne un épuisement de la batterie chaque fois que cela se produit. Voici un exemple minimal:

J’ai trouvé quelqu’un avec un problème similaire ici , mais il n’a jamais été diagnostiqué ni résolu.

Un service:

@Override public void onCreate() { Toast.makeText(this, "onCreate", Toast.LENGTH_LONG).show(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return new Binder(); } 

Activité:

 @Override protected void onStart() { super.onStart(); Intent service = new Intent(this, BoundService.class); startService(service); bindService(service, mServiceConnection, 0); } @Override protected void onStop() { unbindService(mServiceConnection); Toast.makeText(this, "unbindService", Toast.LENGTH_SHORT).show(); super.onStop(); } 

Pour le tester, j’ai lancé l’application, qui a démarré le service et qui lui est lié. Puis je me suis retiré de l’application, ce qui dissocie (mais laisse le service en cours d’exécution). Alors j’ai fait

 $ adb shell am kill com.tavianator.servicerestart 

et bien sûr, 5 secondes plus tard, le toast “onCreate” apparaît, indiquant que le service a redémarré. Logcat montre ceci:

 $ adb logcat | grep BoundService W/ActivityManager( 306): Scheduling restart of crashed service com.tavianator.servicerestart/.BoundService in 5000ms I/ActivityManager( 306): Start proc com.tavianator.servicerestart for service com.tavianator.servicerestart/.BoundService: pid=20900 uid=10096 gids={1028} 

Si je remplace le modèle startService () par BIND_AUTO_CREATE, le problème ne se produit pas (même si je plante l’application tant qu’elle est encore liée au service). Cela fonctionne aussi si je ne lie jamais au service. Mais la combinaison du démarrage, de l’attachement et du détachement semble ne jamais laisser mon service mourir.

L’utilisation de dumpsys avant de tuer l’application montre ceci:

 $ adb shell dumpsys activity services com.tavianator.servicerestart ACTIVITY MANAGER SERVICES (dumpsys activity services) Active services: * ServiceRecord{43099410 com.tavianator.servicerestart/.BoundService} intent={cmp=com.tavianator.servicerestart/.BoundService} packageName=com.tavianator.servicerestart processName=com.tavianator.servicerestart baseDir=/data/app/com.tavianator.servicerestart-2.apk dataDir=/data/data/com.tavianator.servicerestart app=ProcessRecord{424fb5c8 20473:com.tavianator.servicerestart/u0a96} createTime=-20s825ms lastActivity=-20s825ms executingStart=-5s0ms restartTime=-20s825ms startRequested=true stopIfKilled=true callStart=true lastStartId=1 Bindings: * IntentBindRecord{42e5e7c0}: intent={cmp=com.tavianator.servicerestart/.BoundService} binder=android.os.BinderProxy@42aee778 requested=true received=true hasBound=false doRebind=false 

Regardez cette section du document : Modifications du cycle de vie du service (depuis 1.6)

actualisé

après quelques investigations (avoir créé un projet, exécuter la commande décrite étape par étape, etc.), j’ai trouvé que le code fonctionnait exactement comme décrit dans “Modifications du cycle de vie du service”

ce que j’ai trouvé:
adb shell am kill com.tavianator.servicerestart ne tue rien
(essayez le adb shell , alors dans le shell am kill com.tavianator.servicerestart
Vous serez confronté à un message d’ Error: Unknown command: kill )

alors,
lancez votre application,
exécuter le adb shell
en shell, lance la commande ps
trouver le numéro PID de votre application
dans le shell, exécutez la commande kill
où est votre numéro PID
répéter les étapes de mise à mort pour le service (s’il est exécuté dans son propre processus)
vérifier si le service est en cours d’exécution (ne devrait pas), redémarré après 5-7 secondes
mettre à jour
une solution (pas assez bonne, mais utilisable dans certains cas) est stopSelf() par exemple:

 @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show(); stopSelf(); return START_NOT_STICKY; } 

mettre à jour
solution mise à jour

 void writeState(int state) { Editor editor = getSharedPreferences("serviceStart", MODE_MULTI_PROCESS) .edit(); editor.clear(); editor.putInt("normalStart", state); editor.commit(); } int getState() { return getApplicationContext().getSharedPreferences("serviceStart", MODE_MULTI_PROCESS).getInt("normalStart", 1); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (getState() == 0) { writeState(1); stopSelf(); } else { writeState(0); Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show(); } return START_NOT_STICKY; } 

Pourquoi le service est-il rétabli lorsque le processus est tué?

Selon ce document :

Lorsqu’un service est démarré, son cycle de vie est indépendant du composant qui l’a démarré et le service peut s’exécuter indéfiniment en arrière-plan, même si le composant qui l’a démarré est détruit. En tant que tel, le service doit s’arrêter lorsque son travail est effectué en appelant stopSelf () , ou un autre composant peut l’arrêter en appelant stopService () .
Attention : il est important que votre application arrête ses services lorsqu’elle est terminée, afin d’éviter de gaspiller les ressources du système et de consumr de l’énergie. Si nécessaire, d’autres composants peuvent arrêter le service en appelant stopService (). Même si vous activez la liaison pour le service, vous devez toujours arrêter le service vous même s’il a déjà reçu un appel à onStartCommand ()

d’autre part le document dit:

* START_NOT_STICKY * – Si le système tue le service après le retour de onStartCommand (), ne recréez pas le service, sauf si des intentions sont en attente de livraison. C’est l’option la plus sûre pour éviter d’exécuter votre service lorsque cela n’est pas nécessaire et lorsque votre application peut simplement redémarrer des tâches inachevées.

Donc, après avoir lu ce document et quelques expériences, je pense que le système traite les services tués manuellement comme inachevés (crash: @see W/ActivityManager(306): Scheduling restart of crashed service ) et le redémarre malgré la valeur renvoyée par onStartCommand.

stopSelf () ou stopService () – aucun redémarrage , pourquoi pas si le travail est terminé?

Je crois que le problème ici est que pour un service qui a été lancé en appelant startService() , la comptabilité interne de ses liaisons (affectée par les appels à bindService() / unbindService() ) empêche en quelque sorte le service d’être arrêté après. est programmé pour redémarrer lorsque le processus est tué.

Selon votre préférence, il semble qu’il y ait trois alternatives pour éviter ce problème (vous avez déjà mentionné les deux premières dans votre question):

  • Utilisez startService() / stopService() , n’utilisez pas du tout bindService() / unbindService() .

  • Utilisez bindService() / unbindService() (avec BIND_AUTO_CREATE ), n’utilisez pas du tout startService() / stopService() .

  • Si vous avez besoin de startService() / stopService() et bindService() / unbindService() , remplacez la méthode onUnbind() dans votre service et appelez stopSelf() partir de celui-ci:

     @Override public boolean onUnbind(Intent intent) { stopSelf(); return super.onUnbind(intent); } 

De mes tests:

  • En utilisant la première alternative, cette ligne sera imprimée dans le journal:

    W / ActivityManager (nnn): Planification du redémarrage du service crashé xxxx dans 5000ms

    mais le service ne sera pas vraiment redémarré après cela.

  • La deuxième alternative entraînera la destruction de votre service après vous être détaché (dans le onStop() l’activité dans votre exemple).

  • La troisième alternative consistera essentiellement à faire en sorte que votre code se comporte comme la deuxième alternative (sans avoir à le changer beaucoup).

J’espère que cela t’aides!

Que diriez-vous de reconsidérer votre modèle ?

Le drapeau START_NOT_STICKY est apparu à un moment donné pendant l’évolution d’Android (après 1.6?). Très probablement, vous faites face à une régression subtile du cycle de vie des services, qui a été introduite à cette époque. Ainsi, même si une solution tout aussi subtile est trouvée pour le moment – et vérifiée par magie pour fonctionner sur tous les périphériques possibles – elle ne fonctionnera pas nécessairement sous de nouvelles versions Android.

Quoi qu’il en soit, ce cycle de vie semble inhabituel, et cela pourrait être la raison même pour laquelle vous vous heurtez à ce problème exotique.

Considérons deux cas.

  1. Ordinaire (?). Une activité commence le service. Peu de temps après, il devient lié, utilisé, non lié – alors quoi? Arrêté en quelque sorte? Comment se fait-il qu’il n’y ait pas de perte de batterie significative dans ce cas?

  2. Anormal. Service tué à un point (aléatoire?) Au redémarrage, il implémente une hypothèse erronée et rest actif, faisant quelque chose qui draine la batterie?

Tout semble plutôt étrange.

J’parsingrais les processus concurrents dans votre produit. Tous les cas significatifs. Quelques diagrammes, etc. Probablement, par conséquent, le besoin même d’un tel modèle serait éliminé.

Sinon, il semblerait que toute solution de rechange soit un hack fragile.