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
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
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