Panoramica dei servizi

Un Service è un componente dell'applicazione in grado di eseguire operazioni a lunga esecuzione in background. Non fornisce un'interfaccia utente. Una volta avviato, un servizio potrebbe continuare a essere eseguito per un po' di tempo, anche dopo che l'utente è passato a un'altra applicazione. Inoltre, un componente può associarsi a un servizio per interagire con quest'ultimo e persino eseguire una comunicazione tra processi (IPC). Ad esempio, un servizio può gestire transazioni di rete, riprodurre musica, eseguire I/O di file o interagire con un fornitore di contenuti, il tutto in background.

Attenzione: un servizio viene eseguito nel thread principale del proprio processo di hosting; il servizio non crea un proprio thread e non viene eseguito in un processo separato, se non diversamente specificato. Dovresti eseguire eventuali operazioni di blocco su un thread separato all'interno del servizio per evitare errori L'applicazione non risponde (ANR).

Tipi di servizi

Ecco i tre diversi tipi di servizi:

Primo piano

Un servizio in primo piano esegue un'operazione evidente per l'utente. Ad esempio, un'app audio utilizza un servizio in primo piano per riprodurre una traccia audio. Per i servizi in primo piano deve essere visualizzata una notifica. I servizi in primo piano continuano a essere eseguiti anche quando l'utente non interagisce con l'app.

Quando utilizzi un servizio in primo piano, devi visualizzare una notifica in modo che gli utenti siano consapevoli che il servizio è in esecuzione. Questa notifica non può essere ignorata a meno che il servizio non venga interrotto o rimosso dal primo piano.

Scopri di più su come configurare i servizi in primo piano nella tua app.

Nota: l'API WorkManager offre un modo flessibile per pianificare le attività e, se necessario, è in grado di eseguire questi job come servizi in primo piano. In molti casi, è preferibile utilizzare WorkManager che utilizzare direttamente i servizi in primo piano.

Premessa
Un servizio in background esegue un'operazione non immediatamente rilevata dall'utente. Ad esempio, se un'app ha utilizzato un servizio per compattare lo spazio di archiviazione, di solito si tratta di un servizio in background.

Nota:se la tua app ha come target il livello API 26 o versioni successive, il sistema impone limitazioni sull'esecuzione dei servizi in background quando l'app non è in primo piano. Nella maggior parte dei casi, ad esempio, non dovresti accedere alle informazioni sulla posizione in background. Puoi invece pianificare le attività utilizzando WorkManager.

Associato
Un servizio è associato quando un componente dell'applicazione si associa al servizio chiamando bindService(). Un servizio associato offre un'interfaccia client-server che consente ai componenti di interagire con il servizio, inviare richieste, ricevere risultati e persino farlo nei processi con comunicazione tra processi (IPC). Un servizio associato viene eseguito solo finché è associato un altro componente dell'applicazione. Al servizio possono essere associati più componenti contemporaneamente, ma quando vengono svincolati tutti, il servizio viene eliminato.

Anche se questa documentazione illustra in genere i servizi avviati e associati separatamente, il tuo servizio può funzionare in entrambi i modi: può essere avviato (per essere eseguito a tempo indeterminato) e consente anche l'associazione. Occorre semplicemente implementare un paio di metodi di callback: onStartCommand() per consentire ai componenti di avviarlo e onBind() per consentire l'associazione.

Indipendentemente dal fatto che il servizio sia avviato, associato o entrambi, qualsiasi componente dell'applicazione può utilizzare il servizio (anche da un'applicazione separata) nello stesso modo in cui qualsiasi componente può utilizzare un'attività, iniziando con un Intent. Tuttavia, puoi dichiarare il servizio come privato nel file manifest e bloccare l'accesso da altre applicazioni. Questo argomento viene discusso in modo più approfondito nella sezione relativa alla dichiarazione del servizio nel file manifest.

Scegliere tra un servizio e un thread

Un servizio è semplicemente un componente che può essere eseguito in background, anche quando l'utente non interagisce con la tua applicazione, quindi dovresti creare un servizio solo se è ciò di cui hai bisogno.

Se devi eseguire operazioni all'esterno del thread principale, ma solo mentre l'utente interagisce con l'applicazione, dovrai creare un nuovo thread nel contesto di un altro componente dell'applicazione. Ad esempio, se vuoi ascoltare un po' di musica, ma solo mentre l'attività è in esecuzione, potresti creare un thread in onCreate(), iniziare a eseguirlo in onStart() e interromperlo in onStop(). Valuta anche l'utilizzo di pool di thread ed esecutori del pacchetto java.util.concurrent o delle coroutine di Kotlin anziché della tradizionale classe Thread. Consulta il documento Threading su Android per ulteriori informazioni sullo spostamento dell'esecuzione in thread in background.

Ricorda che se utilizzi un servizio, questo viene comunque eseguito per impostazione predefinita nel thread principale dell'applicazione, quindi dovrai comunque creare un nuovo thread all'interno del servizio se esegue operazioni intensive o di blocco.

Nozioni di base

Per creare un servizio, devi creare una sottoclasse Service o utilizzare una delle relative sottoclassi esistenti. Nella tua implementazione, devi sostituire alcuni metodi di callback che gestiscono gli aspetti chiave del ciclo di vita del servizio e fornire un meccanismo che consenta ai componenti di associarsi al servizio, se opportuno. Di seguito sono riportati i metodi di callback più importanti di cui dovresti eseguire l'override:

onStartCommand()
Il sistema richiama questo metodo chiamando startService() quando un altro componente (ad esempio un'attività) richiede l'avvio del servizio. Quando questo metodo viene eseguito, il servizio viene avviato e può essere eseguito in background a tempo indeterminato. Se implementi questa impostazione, è tua responsabilità interrompere il servizio al termine del suo lavoro chiamando stopSelf() o stopService(). Se vuoi solo fornire l'associazione, non è necessario implementare questo metodo.
onBind()
Il sistema richiama questo metodo chiamando bindService() quando un altro componente vuole associarsi al servizio (ad esempio per eseguire RPC). Nell'implementazione di questo metodo, devi fornire un'interfaccia che i client utilizzino per comunicare con il servizio restituendo un IBinder. Devi sempre implementare questo metodo; tuttavia, se non vuoi consentire l'associazione, devi restituire un valore null.
onCreate()
Il sistema richiama questo metodo per eseguire procedure di configurazione una tantum quando il servizio viene creato all'inizio (prima di chiamare onStartCommand() o onBind()). Se il servizio è già in esecuzione, questo metodo non viene chiamato.
onDestroy()
Il sistema richiama questo metodo quando il servizio non è più in uso ed è in fase di eliminazione. Il tuo servizio deve implementarla per eseguire la pulizia di eventuali risorse come thread, ascoltatori registrati o ricevitori. Questa è l'ultima chiamata ricevuta dal servizio.

Se un componente avvia il servizio chiamando startService() (che comporta una chiamata a onStartCommand()), il servizio continuerà a essere eseguito finché non si arresta con stopSelf() o un altro componente lo arresta chiamando stopService().

Se un componente chiama bindService() per creare il servizio e onStartCommand() non viene chiamato, il servizio viene eseguito solo se il componente è associato. Dopo l'annullamento dell'associazione del servizio da tutti i client, il sistema lo elimina.

Il sistema Android interrompe un servizio solo quando la memoria è quasi esaurita e deve recuperare le risorse di sistema per l'attività incentrata sull'utente. Se il servizio è associato a un'attività focalizzata sull'utente, è meno probabile che venga interrotto. Se il servizio viene eseguito in primo piano, raramente viene interrotto. Se il servizio è avviato ed è a lunga esecuzione, nel tempo il sistema abbassa la sua posizione nell'elenco delle attività in background e il servizio diventa altamente suscettibile all'arresto: se il servizio viene avviato, devi progettarlo per gestire agevolmente i riavvii da parte del sistema. Se il sistema termina il servizio, lo riavvia non appena le risorse diventano disponibili, ma questo dipende anche dal valore restituito da onStartCommand(). Per ulteriori informazioni su quando il sistema potrebbe eliminare un servizio, consulta il documento Processi e thread.

Nelle sezioni seguenti scoprirai come creare i metodi dei servizi startService() e bindService(), nonché utilizzarli da altri componenti dell'applicazione.

Dichiarazione di un servizio nel manifest

Devi dichiarare tutti i servizi nel file manifest della tua applicazione, proprio come fai per le attività e gli altri componenti.

Per dichiarare il servizio, aggiungi un elemento <service> come elemento secondario dell'elemento <application>. Ecco un esempio:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

Per ulteriori informazioni sulla dichiarazione del servizio nel manifest, consulta la documentazione di riferimento sull'elemento <service>.

Esistono altri attributi che puoi includere nell'elemento <service> per definire proprietà come le autorizzazioni necessarie per avviare il servizio e il processo in cui deve essere eseguito. L'attributo android:name è l'unico attributo obbligatorio, in quanto specifica il nome della classe del servizio. Dopo aver pubblicato l'applicazione, lascia questo nome invariato per evitare il rischio di danneggiare il codice dovuto alla dipendenza da intent espliciti di avviare o associare il servizio (leggi il post del blog, Cose che non possono cambiare).

Attenzione: per assicurarti che la tua app sia sicura, usa sempre un intent esplicito quando avvii Service e non dichiarare filtri per intent per i tuoi servizi. L'utilizzo di un intent implicito per avviare un servizio rappresenta un pericolo per la sicurezza, perché non puoi essere certo del servizio che risponda all'intent e l'utente non può vedere quale servizio viene avviato. A partire da Android 5.0 (livello API 21), il sistema genera un'eccezione se chiami bindService() con un intent implicito.

Puoi assicurarti che il servizio sia disponibile solo per la tua app includendo l'attributo android:exported e impostandolo su false. Questo impedisce ad altre app di avviare il servizio, anche se utilizzi un intent esplicito.

Nota: gli utenti possono vedere quali servizi sono in esecuzione sul loro dispositivo. Se vedono un servizio che non riconosce o non considera attendibile, può interromperlo. Per evitare che il servizio venga interrotto accidentalmente dagli utenti, devi aggiungere l'attributo android:description all'elemento <service> nel file manifest dell'app. Nella descrizione, fornisci una breve frase che spieghi che cosa fa il servizio e quali vantaggi offre.

Creazione di un servizio avviato

Un servizio avviato è un servizio che viene avviato da un altro componente chiamando startService(), che comporta una chiamata al metodo onStartCommand() del servizio.

Quando un servizio viene avviato, ha un ciclo di vita indipendente dal componente che lo ha avviato. Il servizio può essere eseguito in background a tempo indeterminato, anche se il componente che l'ha avviato viene eliminato. Di conseguenza, il servizio dovrebbe arrestarsi automaticamente al termine del job chiamando stopSelf() oppure un altro componente può arrestarlo chiamando stopService().

Un componente dell'applicazione, ad esempio un'attività, può avviare il servizio chiamando startService() e passando un Intent che specifichi il servizio e includa eventuali dati da utilizzare. Il servizio riceve questo Intent nel metodo onStartCommand().

Ad esempio, supponiamo che un'attività debba salvare alcuni dati in un database online. L'attività può avviare un servizio companion e fornirgli i dati da salvare passando un intent a startService(). Il servizio riceve l'intent in onStartCommand(), si connette a internet ed esegue la transazione del database. Al termine della transazione, il servizio si arresta e viene distrutto.

Attenzione: un servizio viene eseguito nello stesso processo dell'applicazione in cui è dichiarato e nel thread principale dell'applicazione per impostazione predefinita. Se il servizio esegue operazioni intensive o di blocco mentre l'utente interagisce con un'attività della stessa applicazione, il servizio rallenta le prestazioni dell'attività. Per evitare di influire sulle prestazioni dell'applicazione, avvia un nuovo thread all'interno del servizio.

La classe Service è la classe base per tutti i servizi. Quando estendi questa classe, è importante creare un nuovo thread in cui il servizio può completare tutto il suo lavoro; il servizio utilizza per impostazione predefinita il thread principale dell'applicazione, il che può rallentare le prestazioni di qualsiasi attività in esecuzione nell'applicazione.

Il framework Android fornisce anche la sottoclasse IntentService di Service che utilizza un thread di worker per gestire tutte le richieste di avvio, una alla volta. L'utilizzo di questo corso non è consigliato per le nuove app, poiché non funzionerà bene a partire da Android 8 Oreo, a causa dell'introduzione dei limiti di esecuzione in background. Inoltre, è stato ritirato a partire da Android 11. Puoi utilizzare JobIntentService come sostituzione di IntentService compatibile con le versioni più recenti di Android.

Le seguenti sezioni descrivono come implementare il tuo servizio personalizzato, ma per la maggior parte dei casi d'uso ti consigliamo di utilizzare WorkManager. Consulta la guida all'elaborazione in background su Android per vedere se esiste una soluzione adatta alle tue esigenze.

Estensione della classe Service

Puoi estendere la classe Service per gestire ogni intent in arrivo. Ecco come potrebbe presentarsi un'implementazione di base:

Kotlin

class HelloService : Service() {

    private var serviceLooper: Looper? = null
    private var serviceHandler: ServiceHandler? = null

    // Handler that receives messages from the thread
    private inner class ServiceHandler(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            // Normally we would do some work here, like download a file.
            // For our sample, we just sleep for 5 seconds.
            try {
                Thread.sleep(5000)
            } catch (e: InterruptedException) {
                // Restore interrupt status.
                Thread.currentThread().interrupt()
            }

            // Stop the service using the startId, so that we don't stop
            // the service in the middle of handling another job
            stopSelf(msg.arg1)
        }
    }

    override fun onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
            start()

            // Get the HandlerThread's Looper and use it for our Handler
            serviceLooper = looper
            serviceHandler = ServiceHandler(looper)
        }
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()

        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        serviceHandler?.obtainMessage()?.also { msg ->
            msg.arg1 = startId
            serviceHandler?.sendMessage(msg)
        }

        // If we get killed, after returning from here, restart
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        // We don't provide binding, so return null
        return null
    }

    override fun onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
    }
}

Java

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service. Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block. We also make it
    // background priority so CPU-intensive work doesn't disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

Il codice di esempio gestisce tutte le chiamate in arrivo in onStartCommand() e pubblica il lavoro in un Handler in esecuzione in un thread in background. Funziona come un IntentService ed elabora tutte le richieste in serie, una dopo l'altra. Puoi modificare il codice per eseguire il lavoro su un pool di thread, ad esempio se vuoi eseguire più richieste contemporaneamente.

Tieni presente che il metodo onStartCommand() deve restituire un numero intero. Il numero intero è un valore che descrive in che modo il sistema deve continuare il servizio nel caso in cui venga interrotto. Il valore restituito da onStartCommand() deve essere una delle seguenti costanti:

START_NOT_STICKY
Se il sistema termina il servizio dopo il ritorno di onStartCommand(), non ricrearlo a meno che non ci siano intent in attesa da consegnare. Questa è l'opzione più sicura per evitare di eseguire il servizio quando non è necessario e quando l'applicazione può semplicemente riavviare i job non completati.
START_STICKY
Se il sistema termina il servizio dopo il ritorno di onStartCommand(), ricrealo e chiama onStartCommand(), ma non consegnare di nuovo l'ultimo intent. Il sistema chiama invece onStartCommand() con un intent nullo, a meno che non ci siano intent in attesa per avviare il servizio. In questo caso, questi intenti vengono sfruttati. Questo è adatto per media player (o servizi simili) che non eseguono comandi, ma sono in esecuzione a tempo indeterminato e in attesa di un job.
START_REDELIVER_INTENT
Se il sistema termina il servizio dopo il ritorno di onStartCommand(), ricrealo e chiama onStartCommand() con l'ultimo intent fornito al servizio. Gli eventuali intent in sospeso vengono inviati a loro volta. È adatto per i servizi che eseguono attivamente un job che deve essere ripreso immediatamente, ad esempio il download di un file.

Per ulteriori dettagli su questi valori restituiti, consulta la documentazione di riferimento collegata per ogni costante.

Avvio di un servizio

Puoi avviare un servizio da un'attività o da un altro componente dell'applicazione passando un Intent a startService() o startForegroundService(). Il sistema Android chiama il metodo onStartCommand() del servizio e gli passa Intent, che specifica quale servizio avviare.

Nota: se la tua app ha come target il livello API 26 o versioni successive, il sistema impone limitazioni all'utilizzo o alla creazione di servizi in background, a meno che l'app stessa non sia in primo piano. Se un'app deve creare un servizio in primo piano, deve chiamare startForegroundService(). Questo metodo crea un servizio in background, ma il metodo segnala al sistema che il servizio si promuoverà in primo piano. Una volta creato, il servizio deve chiamare il suo metodo startForeground() entro cinque secondi.

Ad esempio, un'attività può avviare il servizio di esempio nella sezione precedente (HelloService) utilizzando un intent esplicito con startService(), come mostrato qui:

Kotlin

startService(Intent(this, HelloService::class.java))

Java

startService(new Intent(this, HelloService.class));

Il metodo startService() restituisce immediatamente e il sistema Android chiama il metodo onStartCommand() del servizio. Se il servizio non è già in esecuzione, il sistema chiama prima onCreate(), quindi chiama onStartCommand().

Se il servizio non fornisce anche l'associazione, l'intent fornito con startService() è l'unica modalità di comunicazione tra il componente dell'applicazione e il servizio. Tuttavia, se vuoi che il servizio ti restituisca un risultato, il client che avvia il servizio può creare un PendingIntent per una trasmissione (con getBroadcast()) e consegnarlo al servizio nell'Intent che avvia il servizio. Il servizio può quindi usare la trasmissione per fornire un risultato.

Più richieste di avvio del servizio generano più chiamate corrispondenti al onStartCommand() del servizio. Tuttavia, è necessaria una sola richiesta per arrestare il servizio (con stopSelf() o stopService()).

Interruzione di un servizio

Un servizio avviato deve gestire il proprio ciclo di vita. In altre parole, il sistema non arresta o elimina il servizio, a meno che non debba recuperare la memoria di sistema e il servizio continui a essere eseguito dopo la restituzione di onStartCommand(). Il servizio deve arrestarsi automaticamente chiamando stopSelf(), oppure un altro componente può arrestarlo chiamando stopService().

Dopo che è stato richiesto l'arresto con stopSelf() o stopService(), il sistema elimina il servizio non appena possibile.

Se il servizio gestisce più richieste a onStartCommand() contemporaneamente, non devi interrompere il servizio quando hai terminato l'elaborazione di una richiesta di avvio, perché potresti aver ricevuto una nuova richiesta di avvio (interrompersi alla fine della prima richiesta comporta la terminazione della seconda). Per evitare questo problema, puoi utilizzare stopSelf(int) per assicurarti che la tua richiesta di interruzione del servizio sia sempre basata sulla richiesta di avvio più recente. In altre parole, quando chiami stopSelf(int), passi l'ID della richiesta di avvio (la startId consegnata a onStartCommand()) a cui corrisponde la tua richiesta di interruzione. Quindi, se il servizio riceve una nuova richiesta di avvio prima che tu possa chiamare stopSelf(int), l'ID non corrisponde e il servizio non viene interrotto.

Attenzione:per evitare di sprecare risorse di sistema e consumare batteria, assicurati che l'applicazione interrompa i propri servizi quando ha finito di funzionare. Se necessario, altri componenti possono interrompere il servizio chiamando stopService(). Anche se abiliti l'associazione per il servizio, devi sempre arrestarlo autonomamente se riceve una chiamata a onStartCommand().

Per saperne di più sul ciclo di vita di un servizio, vedi la sezione seguente sulla gestione del ciclo di vita di un servizio.

Creazione di un servizio associato

Un servizio associato consente ai componenti dell'applicazione di associarsi chiamando bindService() per creare una connessione di lunga data. Generalmente non consente ai componenti di avviarlo chiamando startService().

Crea un servizio associato quando vuoi interagire con il servizio dalle attività e da altri componenti dell'applicazione o per esporre alcune delle funzionalità della tua applicazione ad altre applicazioni attraverso la comunicazione tra processi (IPC).

Per creare un servizio associato, implementa il metodo di callback onBind() per restituire un IBinder che definisce l'interfaccia per la comunicazione con il servizio. Altri componenti dell'applicazione possono quindi chiamare bindService() per recuperare l'interfaccia e iniziare a chiamare i metodi sul servizio. Il servizio risiede solo per gestire il componente dell'applicazione associato, quindi se non esistono componenti associati al servizio, il sistema lo elimina. Non è necessario arrestare un servizio associato nello stesso modo in cui devi arrestare un servizio associato quando viene avviato tramite onStartCommand().

Per creare un servizio associato, devi definire l'interfaccia che specifica in che modo un client può comunicare con il servizio. Questa interfaccia tra il servizio e un client deve essere un'implementazione di IBinder ed è ciò che il servizio deve restituire dal metodo di callback onBind(). Dopo che il client riceve IBinder, può iniziare a interagire con il servizio tramite questa interfaccia.

Più client possono essere associati contemporaneamente al servizio. Quando un client ha terminato di interagire con il servizio, chiama unbindService() per svincolarlo. Se non esistono client associati al servizio, il sistema elimina il servizio.

Esistono diversi modi per implementare un servizio associato e l'implementazione è più complicata di un servizio avviato. Per questi motivi, la discussione sui servizi associati viene visualizzata in un documento separato sui servizi associati.

Invio di notifiche all'utente

Quando un servizio è in esecuzione, può informare l'utente degli eventi utilizzando le notifiche della barra di stato o le notifiche della barra di stato.

Una notifica della snackbar è un messaggio che appare sulla superficie della finestra corrente solo per un momento prima di scomparire. Una notifica nella barra di stato fornisce un'icona nella barra di stato con un messaggio, che l'utente può selezionare per eseguire un'azione (ad esempio avviare un'attività).

Di solito, una notifica della barra di stato è la tecnica migliore da utilizzare quando le operazioni in background, ad esempio il download di un file, sono state completate e l'utente può ora intervenire. Quando l'utente seleziona la notifica dalla visualizzazione espansa, la notifica può avviare un'attività (ad esempio, visualizzare il file scaricato).

Gestione del ciclo di vita di un servizio

Il ciclo di vita di un servizio è molto più semplice di quello di un'attività. Tuttavia, è ancora più importante fare attenzione a come viene creato ed eliminato il servizio perché può essere eseguito in background all'insaputa dell'utente.

Il ciclo di vita del servizio, da quando viene creato a quando viene eliminato, può seguire uno di questi due percorsi:

  • Un servizio avviato

    Il servizio viene creato quando un altro componente chiama startService(). Il servizio viene quindi eseguito a tempo indeterminato e deve arrestarsi chiamando stopSelf(). Un altro componente può anche interrompere il servizio chiamando stopService(). Quando il servizio viene interrotto, il sistema lo elimina.

  • Un servizio associato

    Il servizio viene creato quando un altro componente (un client) chiama bindService(). Il client comunica quindi con il servizio tramite un'interfaccia IBinder. Il client può chiudere la connessione chiamando unbindService(). Più client possono essere associati allo stesso servizio e, quando tutti vengono svincolati, il sistema elimina il servizio. Il servizio non deve arrestarsi automaticamente.

Questi due percorsi non sono completamente separati. Puoi eseguire l'associazione a un servizio già avviato con startService(). Ad esempio, puoi avviare un servizio di musica in background chiamando startService() con un Intent che identifica la musica da riprodurre. In seguito, se in un secondo momento l'utente vuole esercitare un certo controllo sul player o ottenere informazioni sul brano corrente, un'attività può essere associata al servizio chiamando bindService(). In questi casi, stopService() o stopSelf() non interrompe effettivamente il servizio fino a quando tutti i client non si svincolano.

Implementazione dei callback del ciclo di vita

Come per un'attività, un servizio ha metodi di callback del ciclo di vita che puoi implementare per monitorare le modifiche allo stato del servizio ed eseguire le operazioni nei momenti appropriati. Lo scheletro di questo servizio descrive ciascuno dei metodi del ciclo di vita:

Kotlin

class ExampleService : Service() {
    private var startMode: Int = 0             // indicates how to behave if the service is killed
    private var binder: IBinder? = null        // interface for clients that bind
    private var allowRebind: Boolean = false   // indicates whether onRebind should be used

    override fun onCreate() {
        // The service is being created
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // The service is starting, due to a call to startService()
        return startMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // A client is binding to the service with bindService()
        return binder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // All clients have unbound with unbindService()
        return allowRebind
    }

    override fun onRebind(intent: Intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    override fun onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Java

public class ExampleService extends Service {
    int startMode;       // indicates how to behave if the service is killed
    IBinder binder;      // interface for clients that bind
    boolean allowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return startMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return binder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return allowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Nota: a differenza dei metodi di callback del ciclo di vita delle attività, non è necessario chiamare l'implementazione della superclasse di questi metodi di callback.

Figura 2. Il ciclo di vita del servizio. Il diagramma a sinistra mostra il ciclo di vita in caso di creazione del servizio con startService(), mentre il diagramma a destra mostra il ciclo di vita in cui il servizio viene creato con bindService().

La figura 2 illustra i tipici metodi di callback di un servizio. Anche se la figura separa i servizi creati da startService() da quelli creati da bindService(), tieni presente che qualsiasi servizio, a prescindere da come viene avviato, può potenzialmente consentire ai client di essere associato. Un servizio avviato inizialmente con onStartCommand() (da un cliente che chiama startService()) può comunque ricevere una chiamata a onBind() (quando un cliente chiama bindService()).

Implementando questi metodi, puoi monitorare questi due loop nidificati del ciclo di vita del servizio:

  • L'intera durata di un servizio si verifica tra il momento in cui viene chiamato onCreate() e il momento in cui viene restituito onDestroy(). Come un'attività, un servizio esegue la configurazione iniziale in onCreate() e rilascia tutte le risorse rimanenti in onDestroy(). Ad esempio, un servizio di riproduzione musicale può creare il thread in cui viene riprodotta la musica in onCreate() e poi interromperlo in onDestroy().

    Nota: i metodi onCreate() e onDestroy() vengono richiesti per tutti i servizi, indipendentemente dal fatto che siano stati creati da startService() o bindService().

  • La durata attiva di un servizio inizia con una chiamata a onStartCommand() o onBind(). A ogni metodo viene assegnato il Intent che è stato trasmesso a startService() o bindService().

    Se il servizio viene avviato, la durata attiva termina nello stesso momento in cui termina l'intero ciclo di vita (il servizio è ancora attivo anche dopo il reso di onStartCommand()). Se il servizio è associato, la durata attiva termina quando viene restituito onUnbind().

Nota: anche se un servizio avviato viene interrotto da una chiamata al servizio stopSelf() o stopService(), non esiste un callback corrispondente per il servizio (non esiste un callback onStop()). A meno che il servizio non sia associato a un client, il sistema lo elimina quando il servizio viene interrotto: onDestroy() è l'unico callback ricevuto.

Per ulteriori informazioni sulla creazione di un servizio che fornisce l'associazione, consulta il documento Servizi associati, che include ulteriori informazioni sul metodo di callback onRebind() nella sezione sulla gestione del ciclo di vita di un servizio associato.