Service de premier plan tué par Android

85

Mise à jour : je n'ai pas trouvé de vraie solution au problème. Ce que j'ai proposé, c'est une méthode de reconnexion automatique à un ancien appareil Bluetooth à chaque fois que la connexion est perdue. Ce n'est pas idéal, mais cela semble fonctionner assez bien. Je serais ravi d'entendre d'autres suggestions à ce sujet.

J'ai à peu près le même problème que dans cette question: le service est tué tout en maintenant le verrouillage du réveil et après avoir appelé startForeground, y compris l'appareil (Asus Transformer), la durée avant l'arrêt du service (30-45 minutes), l'utilisation de Wake Lock, l'utilisation de startForeground () et le fait que le problème ne se produit pas si l'application est ouverte lorsque l'écran s'éteint.

Mon application maintient une connexion Bluetooth avec un autre appareil et envoie des données entre les deux, elle doit donc être active à tout moment pour écouter les données. L'utilisateur est capable de démarrer et d'arrêter le service à volonté, et en fait c'est le seul moyen que j'ai mis en œuvre pour démarrer ou arrêter le service. Une fois le service redémarré, la connexion Bluetooth à l'autre appareil est perdue.

Selon la réponse à la question liée, startForeground () "réduit la probabilité qu'un service soit tué, mais ne l'empêche pas". Je comprends que c'est le cas, mais j'ai vu de nombreux exemples d'autres applications qui n'ont pas ce problème (Tasker, par exemple).

L'utilité de mon application sera considérablement réduite sans la possibilité pour le service de s'exécuter jusqu'à ce qu'il soit arrêté par l'utilisateur. Y-a-t-il un moyen d'éviter ça???

Je vois cela dans mon logcat chaque fois que le service est arrêté:

ActivityManager: No longer want com.howettl.textab (pid 32321): hidden #16
WindowManager: WIN DEATH: Window{40e2d968 com.howettl.textab/com.howettl.textab.TexTab paused=false
ActivityManager: Scheduling restart of crashed service com.howettl.textab/.TexTabService in 5000ms

EDIT: Je dois également noter que cela ne semble pas se produire sur l'autre appareil auquel je suis connecté: HTC Legend exécutant Cyanogen

EDIT: Voici la sortie de adb shell dumpsys activity services:

* ServiceRecord{40f632e8 com.howettl.textab/.TexTabService}

intent={cmp=com.howettl.textab/.TexTabService}

packageName=com.howettl.textab

processName=com.howettl.textab

baseDir=/data/app/com.howettl.textab-1.apk

resDir=/data/app/com.howettl.textab-1.apk

dataDir=/data/data/com.howettl.textab

app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

isForeground=true foregroundId=2 foregroundNoti=Notification(contentView=com.howettl.textab/0x1090087 vibrate=null,sound=null,defaults=0x0,flags=0x6a)

createTime=-25m42s123ms lastActivity=-25m42s27ms

 executingStart=-25m42s27ms restartTime=-25m42s124ms

startRequested=true stopIfKilled=false callStart=true lastStartId=1

Bindings:

* IntentBindRecord{40a02618}:

  intent={cmp=com.howettl.textab/.TexTabService}

  binder=android.os.BinderProxy@40a9ff70

  requested=true received=true hasBound=true doRebind=false

  * Client AppBindRecord{40a3b780 ProcessRecord{40bb0098 2995:com.howettl.textab/10104}}

    Per-process Connections:

      ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

All Connections:

  ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

Et la sortie de adb shell dumpsys activity:

* TaskRecord{40f5c050 #23 A com.howettl.textab}

numActivities=1 rootWasReset=false

affinity=com.howettl.textab

intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab}

realActivity=com.howettl.textab/.TexTab

lastActiveTime=4877757 (inactive for 702s)

* Hist #1: ActivityRecord{40a776c8 com.howettl.textab/.TexTab}

    packageName=com.howettl.textab processName=com.howettl.textab

    launchedFromUid=2000 app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab }

    frontOfTask=true task=TaskRecord{40f5c050 #23 A com.howettl.textab}

    taskAffinity=com.howettl.textab

    realActivity=com.howettl.textab/.TexTab

    base=/data/app/com.howettl.textab-1.apk/data/app/com.howettl.textab-1.apk data=/data/data/com.howettl.textab

    labelRes=0x7f060000 icon=0x7f020000 theme=0x0

    stateNotNeeded=false componentSpecified=true isHomeActivity=false

    configuration={ scale=1.0 imsi=0/0 loc=en_CA touch=3 keys=2/1/1 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=6}

    launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=1644]

    state=STOPPED stopped=true delayedResume=false finishing=false

    keysPaused=false inHistory=true visible=false sleeping=true idle=true

    fullscreen=true noDisplay=false immersive=false launchMode=2

    frozenBeforeDestroy=false thumbnailNeeded=false

    connections=[ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}]

...

Proc #15: adj=prcp /F 40e75070 959:android.process.acore/10006 (provider)

          com.android.providers.contacts/.ContactsProvider2<=Proc{40bb0098 2995:com.howettl.textab/10104}

Proc #16: adj=bak+2/F 40bb0098 2995:com.howettl.textab/10104 (foreground-service)

Celles-ci semblent indiquer que le service fonctionne au premier plan.

hurlement
la source
Jetez un oeil à cette réponse - pourrait fonctionner pour vous stackoverflow.com/a/21157035/624109
Muzikant

Réponses:

218

Okey dokey. J'ai traversé l'enfer et je suis revenu sur ce problème. Voici comment procéder. Il y a des bugs. Cette publication décrit comment analyser les bogues dans l'implémentation et contourner les problèmes.

Pour résumer, voici comment les choses sont censées fonctionner. Les services en cours d'exécution seront systématiquement nettoyés et interrompus toutes les 30 minutes environ. Les services qui souhaitent rester en vie plus longtemps que cela doivent appeler Service.startForeground, qui place une notification sur la barre de notification, afin que les utilisateurs sachent que votre service est en cours d'exécution en permanence et que la durée de vie de la batterie est potentiellement épuisée. Seuls 3 processus de service peuvent se désigner comme services de premier plan à un moment donné. S'il y a plus de trois services de premier plan, Android désignera le service le plus ancien comme candidat pour le nettoyage et la résiliation.

Malheureusement, il existe des bogues dans Android concernant la hiérarchisation des services de premier plan, qui sont déclenchés par diverses combinaisons d'indicateurs de liaison de service. Même si vous avez correctement désigné votre service en tant que service de premier plan, Android peut de toute façon mettre fin à votre service, si des connexions aux services de votre processus ont déjà été établies avec certaines combinaisons d'indicateurs de liaison. Les détails sont donnés ci-dessous.

Notez que très peu de services doivent être des services de premier plan. En règle générale, vous n'avez besoin d'être un service de premier plan que si vous disposez d'une connexion Internet constamment active ou de longue durée qui peut être activée et désactivée, ou annulée par les utilisateurs. Exemples de services nécessitant un statut de premier plan: serveurs UPNP, téléchargements de longue durée de très gros fichiers, synchronisation des systèmes de fichiers par Wi-Fi et lecture de musique.

Si vous ne faites qu'interroger occasionnellement, ou attendez des récepteurs de diffusion système ou des événements système, vous feriez mieux de réveiller votre service sur une minuterie ou en réponse à des récepteurs de diffusion, puis de laisser votre service mourir une fois terminé. C'est le comportement tel que conçu pour les services. Si vous devez simplement rester en vie, poursuivez votre lecture.

Après avoir coché les cases sur les exigences bien connues (par exemple, appeler Service.startForeground), le prochain endroit à regarder est les indicateurs que vous utilisez dans les appels Context.bindService. Les indicateurs utilisés pour la liaison affectent la priorité du processus de service cible de diverses manières inattendues. Plus particulièrement, l'utilisation de certains indicateurs de liaison peut amener Android à rétrograder incorrectement votre service de premier plan vers un service régulier. Le code utilisé pour attribuer la priorité au processus a été très fortement modifié. Notamment, il existe des révisions dans l'API 14+ qui peuvent provoquer des bogues lors de l'utilisation d'anciens indicateurs de liaison; et il y a des bogues certains dans 4.2.1.

Votre ami dans tout cela est l'utilitaire sysdump, qui peut être utilisé pour déterminer la priorité que le gestionnaire d'activité a attribuée à votre processus de service et repérer les cas où il a attribué une priorité incorrecte. Obtenez votre service opérationnel, puis exécutez la commande suivante à partir d'une invite de commandes sur votre ordinateur hôte:

processus d'activité adb shell dumpsys> tmp.txt

Utilisez le bloc-notes (pas wordpad / write) pour examiner le contenu.

Vérifiez d'abord que vous avez réussi à exécuter votre service à l'état de premier plan. La première section du fichier dumpsys contient une description des propriétés ActivityManager pour chaque processus. Recherchez une ligne comme celle-ci qui correspond à votre application dans la première section du fichier dumpsys:

APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}

Vérifiez que foregroundServices = true dans la section suivante. Ne vous inquiétez pas des paramètres cachés et vides; ils décrivent l'état des activités dans le processus et ne semblent pas particulièrement pertinents pour les processus comportant des services. Si foregroundService n'est pas vrai, vous devez appeler Service.startForeground pour le rendre vrai.

La prochaine chose que vous devez regarder est la section près de la fin du fichier intitulée "Traiter la liste LRU (triée par oom_adj):". Les entrées de cette liste vous permettent de déterminer si Android a réellement classé votre application en tant que service de premier plan. Si votre processus est au bas de cette liste, c'est un candidat de choix pour l'extermination sommaire. Si votre processus est en haut de la liste, il est pratiquement indestructible.

Regardons une ligne dans ce tableau:

  Proc #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Voici un exemple de service de premier plan qui a tout fait correctement. Le champ clé ici est le champ "adj =". Cela indique la priorité que votre processus a été assignée par ActivityManagerService une fois que tout a été dit. Vous voulez que ce soit "adj = prcp" (service de premier plan visible); ou "adj = vis" (processus visible avec une activité) ou "avant" (processus avec une activité de premier plan). S'il s'agit de "adj = svc" (processus de service), ou "adj = svcb" (service hérité?), Ou "adj = bak" (processus d'arrière-plan vide), alors votre processus est un candidat probable à l'arrêt et sera interrompu au moins toutes les 30 minutes, même s'il n'y a aucune pression pour récupérer la mémoire. Les indicateurs restants sur la ligne sont principalement des informations de débogage de diagnostic pour les ingénieurs de Google. Les décisions de résiliation sont prises en fonction des champs adj. En bref, / FS indique un service de premier plan; / FA indique un processus de premier plan avec une activité. / B indique un service d'arrière-plan. L'étiquette à la fin indique la règle générale en vertu de laquelle le processus s'est vu attribuer une priorité. Habituellement, il doit correspondre au champ adj =; mais la valeur adj = peut être ajustée à la hausse ou à la baisse dans certains cas en raison de la liaison des indicateurs sur les liaisons actives avec d'autres services ou activités.

Si vous avez trébuché sur un bogue avec des indicateurs de liaison, la ligne dumpsys ressemblera à ceci:

  Proc #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Notez comment la valeur du champ adj est incorrectement définie sur "adj = bak" (processus d'arrière-plan vide), ce qui se traduit en gros par "s'il vous plaît, terminez-moi maintenant afin que je puisse mettre fin à cette existence inutile" aux fins du nettoyage du processus. Notez également l'indicateur (fg-service) à la fin de la ligne qui indique que "des règles de service forground ont été utilisées pour déterminer le paramètre" adj ". Malgré le fait que des règles de service fg ont été utilisées, ce processus s'est vu attribuer un paramètre adj. "bak", et ça ne durera pas longtemps. En clair, c'est un bug.

Donc, le but est de s'assurer que votre processus obtient toujours "adj = prcp" (ou mieux). Et la méthode pour atteindre cet objectif consiste à modifier les indicateurs de liaison jusqu'à ce que vous parveniez à éviter les bogues dans l'attribution de priorité.

Voici les bugs que je connais. (1) Si TOUT service ou activité a déjà été lié au service à l'aide de Context.BIND_ABOVE_CLIENT, vous courez le risque que le paramètre adj = soit rétrogradé à «bak» même si cette liaison n'est plus active. Cela est particulièrement vrai si vous avez également des liaisons entre les services. Un bug clair dans les sources 4.2.1. (2) N'utilisez certainement jamais BIND_ABOVE_CLIENT pour une liaison de service à service. Ne l'utilisez pas non plus pour les connexions activité-service. L'indicateur utilisé pour implémenter le comportement BIND_ABOVE_CLIENT semble être défini sur une base par processus, au lieu d'une base par connexion, donc il déclenche des bogues avec des liaisons de service à service même s'il n'y a pas d'activité active à service liaison avec le jeu de drapeaux. Il semble également y avoir des problèmes avec l'établissement de la priorité lorsqu'il y a plusieurs services dans le processus, avec des liaisons de service à service. L'utilisation de Context.BIND_WAIVE_PRIORITY (API 14) sur les liaisons de service à service semble aider. Context.BIND_IMPORTANT semble être une plus ou moins bonne idée lors de la liaison d'une activité à un service. Cela augmente la priorité de votre processus d'un cran lorsque l'activité est au premier plan, sans nuire apparemment lorsque l'activité est mise en pause ou terminée.

Mais dans l'ensemble, la stratégie consiste à ajuster vos indicateurs bindService jusqu'à ce que sysdump indique que votre processus a reçu la priorité correcte.

Pour mes besoins, en utilisant Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT pour les liaisons activité-service et Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY pour les liaisons de service à service semble faire la bonne chose. Votre kilométrage peut différer.

Mon application est assez complexe: deux services d'arrière-plan, chacun pouvant contenir indépendamment des états de service de premier plan, plus un troisième qui peut également prendre l'état de service de premier plan; deux des services se lient conditionnellement; le troisième se lie au premier, toujours. De plus, les activités s'exécutent dans un processus séparé (rend l'animation plus fluide). L'exécution des activités et des services dans le même processus ne semblait pas faire de différence.

La mise en œuvre des règles de nettoyage des processus (et du code source utilisé pour générer le contenu des fichiers sysdump) peut être trouvée dans le fichier Android de base

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.

Bonne chance.

PS: Voici l'interprétation des chaînes sysdump pour Android 5.0. Je n'ai pas travaillé avec eux, alors fais d'eux ce que tu veux. Je pense que vous voulez que 4 soit «A» ou «S», et 5 soit «IF» ou «IB», et 1 soit aussi bas que possible (probablement en dessous de 3, puisque seuls 3 processus de service de premier plan restent actifs dans la configuration par défaut).

Example:
   Proc # : prcp  F/S/IF trm: 0 31719: neirotech.cerebrum.attention:blePrcs/u0a77 (fg-service)

Format:
   Proc # {1}: {2}  {3}/{4}/{5} trm: {6} {7}: {8}/{9} ({10}

1: Order in list: lower is less likely to get trimmed.

2: Not sure.

3:
    B: Process.THREAD_GROUP_BG_NONINTERACTIVE
    F: Process.THREAD_GROUP_DEFAULT

4:
    A: Foreground Activity
    S: Foreground Service
    ' ': Other.

5:
    -1: procState = "N ";
        ActivityManager.PROCESS_STATE_PERSISTENT: procState = "P ";
    ActivityManager.PROCESS_STATE_PERSISTENT_UI:procState = "PU";
    ActivityManager.PROCESS_STATE_TOP: procState = "T ";
    ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF";
    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: procState = "IB";
    ActivityManager.PROCESS_STATE_BACKUP:procState = "BU";
    ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: procState = "HW";
    ActivityManager.PROCESS_STATE_SERVICE: procState = "S ";
    ActivityManager.PROCESS_STATE_RECEIVER: procState = "R ";
    ActivityManager.PROCESS_STATE_HOME: procState = "HO";
    ActivityManager.PROCESS_STATE_LAST_ACTIVITY: procState = "LA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: procState = "CA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "Ca";
    ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CE";

{6}: trimMemoryLevel

{8} Process ID.
{9} process name
{10} appUid 
Robin Davies
la source
4
@Robin Davies, j'ai une petite question. Dois-je vraiment appeler bindService()si j'ai besoin d'un service en cours d'exécution en permanence? N'est-il pas suffisant d'appeler le startForeground()service? Pour la communication avec le serveur I en utilisant EventBus.
ar-g
Vous appelez Context.bindService à partir d'une activité afin que le service s'exécute en premier lieu. La méthode Service.startService est appelée par code dans le service afin de déplacer un service qui a été démarré dans l'état "premier plan". Je suppose que la bibliothèque EventBus appelle Context.bindService en votre nom à un moment donné afin de démarrer le service. S'il existe une autre façon de démarrer un service, je ne la connais pas.
Robin Davies
3
Très bonne publication! J'ai voté pour. Un élément que je voulais ajouter à ce commentaire, que je crois pertinent. Si vous voulez un service fonctionnant en permanence, comme Robin l'a mentionné, vous devez le démarrer d'une manière ou d'une autre. Il est possible d'appeler startService (service Intent) directement dans votre activité plutôt que bindService (), puis une fois que votre service démarre, vous pouvez appeler la méthode startForeground (). J'appelle cela dans le onStartCommand () de la classe de service. Pour autant que je sache, cela devrait rendre votre service non lié, mais le maintenir en cours d'exécution des problèmes de ressources en attente. Espérons que cela aide quelqu'un.
Dave
Bon travail!! Je voudrais ajouter une mise à jour à cela. Tout d'abord, le format de sortie de adb a légèrement changé (janvier 2016). J'ai testé ce processus sur deux appareils LG Volt 4.4.2 et Nexus 5x 6.0.1, les deux appareils sont toujours affectés par le bogue. Je ne peux reproduire le problème qu'en utilisant Context.BIND_ABOVE_CLIENT: Proc # 4: cch F / S / SF trm: 0 12354: com.test / u0a78 (fg-service) L'utilisation du drapeau troublé provoque la mort instantanée la plupart du temps sur l'ancien appareil après avoir quitté l'activité. Tous les autres indicateurs semblent fonctionner correctement sur les deux versions d'Android.
user3259330
1
@Dave hey Dave, j'utilise exactement cette méthode, ainsi que le retour START_STICKY, mais mon service meurt toujours après une heure environ lorsque l'appareil est inactif. Avez-vous une idée de ce qui pourrait se passer
Ruchir Baronia
7

S'il dit "ne veut plus ...", alors ce processus n'a pas de service actif en lui qui est actuellement dans l'état startForeground (). Assurez-vous que votre appel réussit réellement - que vous voyez la notification publiée, qu'il n'y a aucun message dans le journal à ce moment-là pour se plaindre de quoi que ce soit, etc. Utilisez également "adb shell dumpsys activity services" pour consulter le état de votre service et assurez-vous qu'il est réellement marqué comme premier plan. De plus, s'il est correctement au premier plan, dans la sortie de "l'activité adb shell dumpsys", vous verrez dans la section montrant le MOO adj des processus que votre processus est actuellement au premier plan en raison de ce service.

hackbod
la source
Merci pour ton aide! J'ai édité ma question avec la sortie des commandes que vous avez mentionnées. Ils semblent indiquer que le service fonctionne au premier plan.
howettl le
Y a-t-il une section de code que je pourrais publier qui pourrait aider au diagnostic?
howettl
1
Il ne devrait certainement pas être tué au premier plan, et je sais avec certitude que des choses comme la musique dans la plate-forme standard ne le sont pas. Pensez à déposer un bogue avec du code pour reproduire le problème. Une chose à regarder est si vous vous déplacez dans et hors du premier plan à tout moment qui pourrait lui permettre d'être tué.
hackbod
1
Est-il possible que la mise à jour de la notification en cours en appelant notify () au lieu d'appeler à nouveau startForeground () puisse la retirer du statut de premier plan? J'ai également FLAG_ALERT_ONLY_ONCE activé sur la notification si cela compte.
howettl
2
Ne le mettez certainement pas à jour via le gestionnaire de notifications. Vous publiez ceci via le service et devez continuer à le mettre à jour via le service.
hackbod