web-dev-qa-db-fra.com

Service de premier plan tué par Android

Mettre à jour: Je n'ai pas trouvé de vraie solution au problème. Ce que j'ai trouvé était une méthode de reconnexion automatique à un ancien appareil Bluetooth à chaque fois que la connexion était 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 de réveil et après avoir appelé startForeground y compris le périphérique (Asus Transformer), la durée avant l'arrêt du service (30- 45 minutes), l'utilisation du verrouillage de réveil, 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 à 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 en mesure 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 ce soit 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 que le service puisse 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)

Ceux-ci semblent montrer que le service fonctionne au premier plan.

76
howettl

Okey dokey. J'ai vécu 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 arrêtés 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 fonctionne en permanence et risque de perdre la vie de la batterie. Seuls 3 processus de service peuvent se proposer comme services de premier plan à un moment donné. S'il existe plus de trois services de premier plan, Android désignera le service le plus ancien comme candidat au nettoyage et à la résiliation.

Malheureusement, il existe des bogues dans Android en ce qui concerne la priorité 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 nommé 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 ne devez ê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 état de premier plan: serveurs UPNP, longs téléchargements de fichiers très volumineux, synchronisation des systèmes de fichiers par Wi-Fi et lecture de musique.

Si vous interrogez 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 entraîner le Android rétrogradation incorrecte de votre service de premier plan vers un service normal. Le code utilisé pour attribuer la priorité du processus a été très fortement baratté. Notamment, il existe des révisions dans API 14+ qui peut provoquer des bogues lors de l'utilisation d'anciens indicateurs de liaison; et il existe des bogues définis dans 4.2.1.

Votre ami dans tout cela est l'utilitaire sysdump, qui peut être utilisé pour déterminer quelle priorité le gestionnaire d'activité a attribué à 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 commande sur votre ordinateur hôte:

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

Utilisez le bloc-notes (et non le clavier/écriture) pour examiner le contenu.

Vérifiez d'abord que vous avez réussi à exécuter votre service en premier plan. La première section du fichier dumpsys contient une description des propriétés ActivityManager pour chaque processus. Recherchez une ligne comme la suivante 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 contenant des services. Si foregroundService n'est pas vrai, vous devez appeler Service.startForeground pour que cela soit vrai.

La prochaine chose que vous devez regarder est la section près de la fin du fichier intitulée "Liste des processus LRU (triés 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, il s'agit d'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)

Ceci est un exemple d'un 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é attribuée par ActivityManagerService après 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 "Fore" (processus avec une activité de premier plan). Si c'est "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 à la résiliation et sera terminé pas moins fréquemment que toutes les 30 minutes même s'il n'y a aucune pression pour récupérer de 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 a reçu 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 drapeaux de liaison sur les liaisons actives avec d'autres services ou activités.

Si vous avez déclenché 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 approximativement par "s'il vous plaît, mettez-moi fin maintenant afin que je puisse mettre fin à cette existence inutile" à des fins de nettoyage du processus. Notez également l'indicateur (fg-service) à la fin de la ligne qui indique que "les règles de service de fond ont été utilisées pour déterminer le paramètre" adj ". Malgré le fait que les règles de service fg ont été utilisées, ce processus a reçu un paramètre adj "bak", et il ne vivra pas longtemps. En clair, c'est un bug.

Le but est donc 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é s'est déjà 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é à service active reliure avec l'ensemble 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 idée plus ou moins bonne lors de la liaison d'une activité à un service. Cela augmente la priorité de votre processus d'un cran plus haut lorsque l'activité est au premier plan, sans faire de mal apparent lorsque l'activité est 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.

À mes fins, 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 détenir 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 l'un à l'autre sous condition; le troisième se lie au premier, toujours. De plus, les activités s'exécutent dans un processus distinct (rend l'animation plus fluide). L'exécution des activités et des services dans le même processus ne semble pas faire de différence.

La mise en œuvre des règles de nettoyage des processus (et le code source utilisé pour générer le contenu des fichiers sysdump) se trouve dans le noyau Android

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 faites-en ce que vous voulez. Je crois que vous voulez que 4 soit 'A' ou ' S ', et 5 pour être "IF" ou "IB", et 1 pour être aussi bas que possible (probablement en dessous de 3, car seulement 3 trois processus de service de premier plan sont maintenus 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 
204
Robin Davies

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

6
hackbod