Servizio in primo piano interrotto da Android


85

Aggiornamento : non ho trovato una vera soluzione al problema. Quello che ho trovato è stato un metodo per ricollegarmi automaticamente a un dispositivo Bluetooth precedente ogni volta che la connessione viene persa. Non è l'ideale, ma sembra funzionare abbastanza bene. Tuttavia, mi piacerebbe ricevere altri suggerimenti in merito.

Ho più o meno lo stesso problema di questa domanda: servizio viene interrotto mentre si tiene il wakelock e dopo aver chiamato startForeground incluso il dispositivo (Asus Transformer), il periodo di tempo prima dell'interruzione del servizio (30-45 minuti), l'uso di wakelock, l'uso di startForeground () e il fatto che il problema non si verifica se l'app è aperta quando lo schermo si spegne.

La mia app mantiene una connessione Bluetooth con un altro dispositivo e invia i dati tra i due, quindi deve essere sempre attiva per ascoltare i dati. L'utente è in grado di avviare e interrompere il servizio a suo piacimento, e infatti questo è l'unico modo che ho implementato per avviare o arrestare il servizio. Una volta riavviato il servizio, la connessione Bluetooth con l'altro dispositivo viene persa.

Secondo la risposta nella domanda collegata, startForeground () "riduce la probabilità che un servizio venga interrotto, ma non lo impedisce". Capisco che sia così, tuttavia ho visto molti esempi di altre app che non hanno questo problema (Tasker, ad esempio).

L'utilità della mia app sarà notevolmente ridotta senza la possibilità che il servizio venga eseguito fino a quando non viene interrotto dall'utente. C'è un modo per evitarlo ???

Lo vedo nel mio logcat ogni volta che il servizio viene interrotto:

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: Dovrei anche notare, questo non sembra accadere sull'altro dispositivo a cui sono connesso: HTC Legend con Cyanogen

EDIT: Ecco l'output di 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}

E l'output di 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)

Questi sembrano mostrare che il servizio è in esecuzione in primo piano.


Date un'occhiata a questa risposta - lavoro potrebbe per voi stackoverflow.com/a/21157035/624109~~V~~singular~~3rd
Muzikant

Risposte:


218

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 #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

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 #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

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 

4
@Robin Davies, ho una piccola domanda. Devo davvero chiamare bindService()se ho bisogno di un servizio costantemente attivo? Non è sufficiente chiamare startForeground()in servizio? Per la comunicazione con il server che utilizzo EventBus.
ar-g

Chiami Context.bindService da un'attività per far funzionare il servizio in primo luogo. Il metodo Service.startService viene chiamato dal codice nel servizio per spostare un servizio che è stato avviato nello stato "in primo piano". Presumo che la libreria EventBus stia chiamando Context.bindService per tuo conto a un certo punto per avviare il servizio. Se esiste un altro modo per avviare un servizio, non lo conosco.
Robin Davies

3
Ottimo post! Votato. Un pezzo che volevo aggiungere a questo commento, che credo sia rilevante. Se vuoi un servizio costantemente attivo, come ha detto Robin, devi avviarlo in qualche modo. È possibile chiamare startService (servizio Intent) direttamente all'interno dell'attività anziché bindService (), quindi una volta avviato il servizio è possibile chiamare il metodo startForeground (). Lo chiamo in onStartCommand () della classe di servizio. Per quanto ne so, questo dovrebbe rendere il tuo servizio non vincolato, ma mantenerlo in esecuzione in attesa di problemi di risorse. Si spera che questo aiuti qualcuno.
Dave

Ottimo lavoro!! Vorrei aggiungere un aggiornamento a questo. Innanzitutto il formato di output di adb è leggermente cambiato (gennaio 2016). Ho testato questo processo su due dispositivi LG Volt 4.4.2 e Nexus 5x 6.0.1, entrambi i dispositivi sono ancora affetti dal bug. Posso solo riprodurre il problema usando Context.BIND_ABOVE_CLIENT: Proc # 4: cch F / S / SF trm: 0 12354: com.test / u0a78 (fg-service) L'uso del flag problematico causa l'uccisione istantanea per la maggior parte del tempo sui vecchi dispositivo dopo essere usciti dall'attività. Tutti gli altri flag sembrano funzionare bene su entrambe le versioni di Android.
user3259330

1
@ Dave ehi Dave, utilizzo esattamente questo metodo, oltre a restituire START_STICKY, ma il mio servizio muore sempre dopo circa un'ora quando il dispositivo è inattivo. Hai qualche idea su cosa potrebbe succedere
Ruchir Baronia

7

Se sta dicendo "non voglio più ...", quel processo non ha un servizio attivo in esso che è attualmente nello stato startForeground (). Controlla per assicurarti che la tua chiamata a quello stia effettivamente riuscendo - che stai vedendo la notifica pubblicata, non ci sono messaggi nel registro a quel punto che si lamentano di qualcosa, ecc. Usa anche "adb shell dumpsys activity services" per guardare il stato del servizio e assicurati che sia effettivamente contrassegnato come in primo piano. Inoltre, se è correttamente in primo piano, nell'output di "adb shell dumpsys activity" vedrai nella sezione che mostra l'adattamento OOM dei processi che il tuo processo è attualmente al livello di primo piano a causa di quel servizio.


Grazie dell'aiuto! Ho modificato la mia domanda con l'output dei comandi che hai menzionato. Sembrano indicare che il servizio è in esecuzione in primo piano.
howettl

C'è una sezione di codice che potrei pubblicare che potrebbe aiutare con la diagnosi?
howettl

1
Sicuramente non dovrebbe essere ucciso mentre è in primo piano, e so per certo che cose come la musica nella piattaforma standard non lo sono. Considera l'idea di segnalare un bug con il codice per riprodurre il problema. Una cosa da guardare è se ti muovi dentro e fuori dal primo piano in qualsiasi punto che potrebbe permettergli di essere ucciso.
hackbod

1
È possibile che l'aggiornamento della notifica in corso chiamando notify () invece di chiamare di nuovo startForeground () possa portarlo fuori dallo stato di primo piano? Ho anche FLAG_ALERT_ONLY_ONCE abilitato sulla notifica se è importante.
howettl

2
Sicuramente non aggiornarlo tramite il gestore delle notifiche. Lo stai pubblicando tramite il servizio e dovresti continuare ad aggiornarlo tramite il servizio.
hackbod
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.