Dal capitolo precedente si è fatta la conoscenza di AsyncTask che, a suo modo, permette di avviare attività asincrone a supporto dell'interfaccia utente. I task gestiti da questa classe non dovrebbero essere molto lunghi, “a few seconds at the most” ('pochi secondi al massimo') come dichiara esplicitamente la documentazione ufficiale.
Per lavori di durata lunga o addiritura indeterminata, si deve ricorrere ad un'appropriata componente Android, i Service.
Per utilizzare un Service è necessario svolgere due operazioni:
- creare una classe Java che estenda Service o un suo derivato;
- registrare il service nel manifest con il nodo
<service>
<service android:name="LogService"/>
L'attributo android:name definisce quale classe Java implementa il service, in questo caso sarebbe la classe LogService.
Tipologie di Service
I Service sono classificabili in due tipologie, dipendentemente dal modo in cui vengono avviati:
- i service Started vengono avviati tramite il metodo
startService()
. La loro particolarità è di essere eseguiti in background indefinitamente anche se la componente che li ha avviati viene terminata. Generalmente non offrono interazione con il chiamante e proseguono finchè non vengono interrotti con il metodostopService
o si auto-interrompono constopSelf()
; - i service Bound vivono in una modalità client-server. Hanno senso solo se qualche altra componente vi si collega. Vengono interrotti nel momento in cui non vi sono più client ad essi collegati.
Chiariamo subito che i service delle due categorie non sono radicalmente diversi. Ciò che li distingue è il modo in cui vengono avviati ed i metodi di callback implementati al loro interno. Uno stesso service può essere avviato in maniera started o bound.
Emerge comunque una differenza nei tipi di lavori che sono più consoni all'uno o all'altra categoria.
I Service Started sono da predilligere per operazioni con una loro finalità indipendente dallo stato delle altre applicazioni. Si potrebbero occupare di aggiornamenti dati in background, scaricamento di file o immagini, sincronizzazione remota verso server esterni, etc. Considerando che il service Started rimarrà in background a lungo la sua esistenza deve essere giustificata dalla finalità preposta.
I Service Bound svolgono il ruolo di supporto ad altre applicazioni. Non rischiano pertanto di essere “dimenticati” in background come potrebbe malauguratamente succedere agli Started ma non sono adatti a lavori da eseguire continuamente in background.
La differenza tra le due tipologie si riflette anche sul ciclo di vita. Il diagramma (fonte: documentazione ufficiale Android) seguente li mette a confronto:
Nell'immagine, sfilano le fasi attraversate da un Service Started (sulla sinistra) e da uno Bound (sulla destra). Entrambi i cicli di vita iniziano e terminano con i metodi di callback onCreate
e onDestroy
. Le differenze si concentrano nella fase in cui il Service viene attivato. Mentre l'avvio di un service Started viene notificato per mezzo di onStartCommand
l'inizio e la fine della connessione con un service bound viene segnalato dai metodi onBind
e onUnbind
.
Service e Thread
Nel ciclo di vita dei Service, non c'è alcun metodo che viene eseguito in background. Non si trova traccia di qualcosa che ricordi il doInBackground di AsyncTask o il run dei Thread. Questo perchè il Service, di suo, non possiede alcun thread. Fondamentalmente, il suo funzionamento è “sincrono”.
Per permettere l'attività asincrona del service è necessario fornirlo almeno di un thread secondario. Quindi si ripresenta il problema paventato nel capitolo precedente: il thread deve essere fornito mediante estensione della classe Thread o avvio di Executors. Queste sono operazioni che possono essere svolte bene ed in maniera efficiente con un po' di esperienza ma un neo-programmatore potrebbe risultarne scoraggiato. Android, come al solito, offre un'alternativa “pratica” anche in questo caso. Si può usare un discendente di Service, IntentService, che nasce già con un thread incorporato.
Al di fuori delle operazioni in background – da collocare all'interno del metodo onHandleIntent – l'IntentService non richiede molto lavoro. L'unico altro metodo obbligatorio da creare è un costruttore senza parametri.
LogService: il primo servizio
Un esempio classico che permette un rapido approccio ai Service è quello di creare un servizio di log avviato mediante Activity.
Prendiamo un'interfaccia utente dotata di due pulsanti, “Avvia” e “Arresta”. Come il nome lascia presagire il primo avvia un Service, il secondo l'arresta.
Questo il layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Avvia"
android:onClick="startService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Arresta"
android:onClick="stopService"/>
</LinearLayout>
questo invece il codice dell'Activity:
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startService(View v)
{
startService(new Intent(this,LogService.class));
}
public void stopService(View v)
{
stopService(new Intent(this,LogService.class));
}
}
Niente di nuovo in entrambi oltre all'uso dei metodi startService e stopService i quali, come spiegato, denunciano l'utilizzo di un Service avviato in modalità Started.
Molto importante: non dimenticare di dichiarare il Service nell'AndroidManifest.
Il Service utilizzato è di tipo IntentService. Non dovremo fornirlo di altro se non di un costruttore senza parametri in input e dell'implementazione di un metodo onHandleIntent
:
public class LogService extends IntentService
{
public LogService()
{
super("LogService");
}
@Override
protected void onHandleIntent(Intent i)
{
int n=0;
while(true)
{
Log.i("PROVA SERVICE", "Evento n."+n++);
try {
Thread.sleep(10000);
}
catch (InterruptedException e)
{ }
}
}
@Override
public void onDestroy()
{
Log.i("PROVA SERVICE", "Distruzione Service");
}
}
L'esecuzione in background del thread secondario produrrà l'immissione di messaggi di log in Logcat sfruttando il metodo i della classe Log. Il risultato saranno messaggi simili, nel formato, a quelli visibili in figura:
Ultima nota, il Service è stato fornito di un'implementazione di onDestroy, chiamata in causa al momento dell'arresto del servizio, che aggiunge un messaggio finale di log.