Okey dokey. Ho passato l'inferno e sono tornato su questo problema. Ecco come procedere. Ci sono insetti. Questo messaggio descrive come analizzare i bug nell'implementazione e aggirare i problemi.
Solo per riassumere, ecco come dovrebbero funzionare le cose. I servizi in esecuzione verranno regolarmente eliminati e terminati ogni 30 minuti circa. I servizi che desiderano rimanere attivi più a lungo di questo devono chiamare Service.startForeground, che inserisce una notifica sulla barra di notifica, in modo che gli utenti sappiano che il tuo servizio è in esecuzione in modo permanente e potenzialmente risucchia la durata della batteria. Solo 3 processi di servizio possono nominarsi come servizi in primo piano in un dato momento. Se sono presenti più di tre servizi in primo piano, Android nominerà il servizio più vecchio come candidato per lo scavenging e la terminazione.
Sfortunatamente, ci sono bug in Android riguardo all'assegnazione di priorità ai servizi in primo piano, che vengono attivati da varie combinazioni di flag di associazione del servizio. Anche se hai correttamente nominato il tuo servizio come servizio in primo piano, Android potrebbe comunque terminare il tuo servizio, se sono state effettuate connessioni ai servizi nel tuo processo con determinate combinazioni di flag vincolanti. I dettagli sono forniti di seguito.
Nota che pochissimi servizi devono essere servizi in primo piano. In genere, è necessario essere un servizio in primo piano solo se si dispone di una connessione Internet costantemente attiva o di lunga durata che può essere attivata e disattivata o annullata dagli utenti. Esempi di servizi che richiedono lo stato in primo piano: server UPNP, download di lunga durata di file molto grandi, sincronizzazione di file system tramite Wi-Fi e riproduzione di musica.
Se esegui il polling solo occasionalmente o attendi ricevitori di trasmissione del sistema o eventi di sistema, faresti meglio a riattivare il tuo servizio su un timer o in risposta ai ricevitori di trasmissione e quindi lasciare che il tuo servizio muoia una volta completato. Questo è il comportamento previsto per i servizi. Se devi semplicemente rimanere in vita, continua a leggere.
Dopo aver selezionato le caselle su requisiti ben noti (ad esempio chiamando Service.startForeground), il prossimo punto da guardare sono i flag che usi nelle chiamate Context.bindService. I flag utilizzati per eseguire il binding influiscono sulla priorità del processo del servizio di destinazione in diversi modi imprevisti. In particolare, l'uso di determinati flag di associazione può causare un errato downgrade da parte di Android del servizio in primo piano a un servizio regolare. Il codice utilizzato per assegnare la priorità al processo è stato ribaltato piuttosto pesantemente. In particolare, ci sono revisioni nell'API 14+ che possono causare bug quando si utilizzano flag di binding meno recenti; e ci sono bug definiti in 4.2.1.
Il tuo amico in tutto questo è l'utilità sysdump, che può essere utilizzata per capire quale priorità il gestore delle attività ha assegnato al tuo processo di servizio e individuare i casi in cui ha assegnato una priorità errata. Ottieni il tuo servizio attivo e funzionante, quindi immetti il seguente comando da un prompt dei comandi sul tuo computer host:
adb shell dumpsys processi attività> tmp.txt
Usa il blocco note (non wordpad / write) per esaminare il contenuto.
Innanzitutto verifica di essere riuscito a eseguire correttamente il tuo servizio in primo piano. La prima sezione del file dumpsys contiene una descrizione delle proprietà ActivityManager per ogni processo. Cerca una riga come la seguente che corrisponde alla tua applicazione nella prima sezione del file dumpsys:
APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}
Verificare che foregroundServices = true nella sezione seguente. Non preoccuparti delle impostazioni nascoste e vuote; descrivono lo stato delle attività nel processo e non sembrano essere particolarmente rilevanti per i processi con servizi al loro interno. Se foregroundService non è true, è necessario chiamare Service.startForeground per renderlo vero.
La prossima cosa che devi guardare è la sezione vicino alla fine del file intitolata "Process LRU list (ordinato per oom_adj):". Le voci in questo elenco ti consentono di determinare se Android ha effettivamente classificato la tua applicazione come servizio in primo piano. Se il tuo processo è in fondo a questo elenco, è un ottimo candidato per lo sterminio sommario. Se il tuo processo si trova in cima alla lista, è praticamente indistruttibile.
Diamo un'occhiata a una riga in questa tabella:
Proc
Questo è un esempio di un servizio in primo piano che ha eseguito tutto correttamente. Il campo chiave qui è il campo "adj =". Ciò indica la priorità assegnata al processo da ActivityManagerService dopo che tutto è stato detto fatto. Vuoi che sia "adj = prcp" (servizio in primo piano visibile); o "adj = vis" (processo visibile con un'attività) o "Fore" (processo con un'attività in primo piano). Se è "adj = svc" (processo di servizio), "adj = svcb" (servizio precedente?) O "adj = bak" (processo in background vuoto), il processo è un probabile candidato per la chiusura e verrà terminato non meno frequentemente di ogni 30 minuti anche se non c'è alcuna pressione per recuperare la memoria. I flag rimanenti sulla riga sono per lo più informazioni di debug diagnostiche per i tecnici di Google. Le decisioni sulla risoluzione vengono prese in base ai campi adj. In breve, / FS indica un servizio in primo piano; / FA indica un processo in primo piano con un'attività. / B indica un servizio in background. L'etichetta alla fine indica la regola generale in base alla quale al processo è stata assegnata una priorità. Di solito dovrebbe corrispondere al campo adj =; ma il valore adj = può essere regolato verso l'alto o verso il basso in alcuni casi a causa di flag vincolanti su associazioni attive con altri servizi o attività.
Se sei inciampato in un bug con flag vincolanti, la riga dumpsys sarà simile a questa:
Proc
Nota come il valore del campo adj è impostato in modo errato su "adj = bak" (processo in background vuoto), che si traduce approssimativamente in "per favore, per favore, terminami ora in modo che io possa porre fine a questa esistenza inutile" ai fini dello scavenging del processo. Nota anche il flag (fg-service) alla fine della riga che indica che "le regole del servizio in primo piano sono state utilizzate per determinare l'impostazione" adj ". Nonostante siano state utilizzate le regole del servizio fg, a questo processo è stata assegnata un'impostazione adj "bak", e non vivrà a lungo. In parole povere, questo è un bug.
Quindi l'obiettivo è garantire che il tuo processo ottenga sempre "adj = prcp" (o migliore). E il metodo per raggiungere questo obiettivo è modificare i flag di associazione fino a quando non riesci a evitare bug nell'assegnazione delle priorità.
Ecco i bug che conosco. (1) Se QUALSIASI servizio o attività è mai stato associato al servizio utilizzando Context.BIND_ABOVE_CLIENT, si corre il rischio che l'impostazione adj = venga declassata a "bak" anche se tale associazione non è più attiva. Ciò è particolarmente vero se si dispone anche di associazioni tra servizi. Un bug evidente nei sorgenti 4.2.1. (2) Sicuramente non utilizzare mai BIND_ABOVE_CLIENT per un'associazione da servizio a servizio. Non usarlo nemmeno per le connessioni da attività a servizio. Il flag utilizzato per implementare il comportamento BIND_ABOVE_CLIENT sembra essere impostato in base al processo, anziché in base alla connessione, quindi innesca bug con associazioni da servizio a servizio anche se non c'è un'attività attiva a servizio vincolante con la bandiera impostata. Sembrano inoltre esserci problemi nello stabilire la priorità quando sono presenti più servizi nel processo, con associazioni da servizio a servizio. L'uso di Context.BIND_WAIVE_PRIORITY (API 14) sui collegamenti da servizio a servizio sembra aiutare. Context.BIND_IMPORTANT sembra essere più o meno una buona idea quando si esegue il binding da un'attività a un servizio. In questo modo la priorità del processo aumenta di una tacca quando l'attività è in primo piano, senza causare alcun danno apparente quando l'attività è sospesa o terminata.
Ma nel complesso, la strategia consiste nel regolare i flag di bindService fino a quando sysdump non indica che il processo ha ricevuto la priorità corretta.
Per i miei scopi, utilizzando Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT per i collegamenti da attività a servizio e Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY per i collegamenti da servizio a servizio sembra fare la cosa giusta. Il tuo chilometraggio potrebbe variare.
La mia app è abbastanza complessa: due servizi in background, ciascuno dei quali può contenere indipendentemente gli stati del servizio in primo piano, più un terzo che può anche assumere lo stato del servizio in primo piano; due dei servizi si vincolano tra loro in modo condizionale; il terzo si lega al primo, sempre. Inoltre, le attività vengono eseguite in un processo separato (rende l'animazione più fluida). L'esecuzione delle attività e dei servizi nello stesso processo non sembrava fare alcuna differenza.
L'implementazione delle regole per i processi di scavenging (e il codice sorgente utilizzato per generare il contenuto dei file sysdump) può essere trovato nel file Android principale
frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.
Bon chance.
PS: ecco l'interpretazione delle stringhe sysdump per Android 5.0. Non ho lavorato con loro, quindi fanne quello che vuoi. Credo che tu voglia che 4 sia 'A' o 'S' e 5 sia "IF" o "IB" e 1 sia il più basso possibile (probabilmente inferiore a 3, poiché solo 3 e tre processi di servizio in primo piano sono mantenuti attivi nella configurazione predefinita).
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