Un componente di fondamentale importanza nell’architettura
Android è il Service. Esso è distinto dall’Activity, in quanto non necessita di
una UI, avendo il compito di restare in esecuzione in background a prescindere
dall’interfaccia grafica. Un componente (ad esempio una Activity), potrà
connettersi ad un particolare servizio con l’obiettivo di avviarlo o fermarlo.
Un esempio di servizio può essere un lettore musicale.
Secondo quanto fornito nella documentazione di Android, un
servizio può assumere due forme:
- Started o Locale: un servizio è “avviato” quando un componente richiama su di esso il metodo start Service(). Una volta avviato, esso può girare in background in maniera indefinita anche se il componente che lo ha avviato viene distrutto. Di solito, un servizio avviato esegue una singola operazione e non ritorna un risultato al chiamante. Per esempio potrebbe essere il download o l’upload di un file in rete. Quando l’operazione è terminata il servizio è in grado di stopparsi da solo.
- Bound: un servizio è “bound” quando un componente si lega ad esso attraverso il metodo bindService(). Un servizio di questo tipo può essere visto come la parte server di una interfaccia client-server che permette ai componenti di interagire con il servizio, inviare richieste, ottenere risposte, il tutto in maniera trasversale ai diversi processi attraverso l’IPC (interprocess communication). Un servizio “bound” vive fin quando un componente è legato ad esso. Più componenti possono legarsi al servizio, ma quando tutti non sono più legati allo stesso, il servizio viene eliminato. A differenza del servizio locale esso non è pensato per girare in background con un tempo indefinito.
E’ bene precisare che anche se i due tipi di servizi vengono
trattati in maniera separata, uno stesso servizio è in grado di lavorare in
entrambi i modi. Esso può essere “Started” e allo stesso tempo anche “Bound”. Il
tutto viene gestito attraverso il file Manifest di una applicazione,
all’interno del quale i servizi devono essere dichiarati con gli opportuni
parametri.
Ma attenzione! Un servizio gira nel thread principale del
processo che l’ha creato, quindi, quando viene avviato non viene creato un
thread a parte. Ciò vuol dire che se si prevede
l’utilizzo della CPU in maniera intensiva è opportuno prevedere la creazione di
un nuovo thread all’interno del service. In questo modo si può ridurre il
rischio di errore ANR (Application Not Responding) e il thread principale
dell’applicazione può dedicarsi esclusivamente all’interazione utente.
A questo punto prima di procedere nei dettagli è opportuno precisare quando può essere utile un Service e quando è assolutamente sconsigliato.
Domanda: Come
faccio a sapere se devo usare un Service o un Thread?
Risposta: Un
servizio è un componente che gira in background anche se l’utente non
interagisce con l’applicazione che lo ha definito. La creazione di un servizio
è raccomandata solo se strettamente necessaria. Se si ha la necessità di
effettuare una operazione abbastanza pensante, mentre l’utente interagisce con
l’applicazione allora è il caso di non utilizzare un Service ma un apposito
Thread. Facendo un esempio: se si vuole far partire un player musicale ma
soltanto quando l’utente visualizza la nostra applicazione allora è il caso di
utilizzare un Thread. Se si vuole che il player continui a funzionare anche
dopo la chiusura dell’applicazione allora è il caso di utilizzare un Service.
Le basi del
Service
Per creare un servizio, bisogna creare una sottoclasse di
Service. Nell’implementazione di questa sottoclasse, bisogna fare l’override di
alcuni metodi di callback che gestiscono gli aspetti chiave del ciclo di vita
di un servizio e forniscono un meccanismo per i componenti che si vogliono
legare (operazione di bind) ad esso. I metodi più importanti da tenere presente
sono:
- onStartCommand(): Il sistema richiama questo metodo quando un componente, come una activity, richiedere che il servizio sia avviato attraverso il metodo start Service(). Una volta che il metodo è eseguito, il servizio è avviato e può girare in background in maniera indefinita. Se il servizio viene implementato in questo modo, è responsabilità dello sviluppatore stopparlo quando il suo lavoro è terminato, attraverso i metodi stopSelf() o stop Service(). Se si vuole fornire solo il binding, questo metodo non deve prevedere una implementazione.
- onBind(): Il sistema chiama questo metodo quando un altro componente intende legarsi al servizi attraverso il metodo bindService(). Nell’implementazione di questo metodo, bisogna fornire una interfaccia che i clients utilizzano per comunicare con il servizio, ritornando un oggetto IBinder. Bisogna sempre riscrivere questo metodo, ma se non si vuole prevedere il binding, allora bisogna ritornare null.
- onCreate(): Il sistema richiama questo metodo quando il servizio viene creato, per eseguire una procedura di setup. Se un servizio è già in esecuzione questo metodo non viene richiamato.
- onDestroy(): il sistema richiama questo metodo quando il servizio non è più utilizzato e deve essere distrutto. L’implementazione di questo metodo deve prevedere il rilascio di eventuali risorse utilizzate.
Android fermerà un servizio solo quando la disponibilità di
memoria sarà bassa e avrà la necessità di recuperare risorse per l’activity che
viene visualizzata dall’utente. Se il servizio è legato ad una activity che ha
il focus dell’utente, allora è meno probabile che esso venga terminato. Se il
servizio è dichiarato per girare in foreground (da approfondire nella
documentazione Adnroid), allora esso non sarà quasi mai terminato.
La prima operazione:
dichiarazione nel Manifest
Per notificare al sistema Android che una determinata
classe deve essere considerate come un Servizio è necessario dichiararlo nel file
Manifest. Il codice da utilizzare è il seguente:
<manifest ... >
...
<application ... >
<service android:name=".NomeServizio" />
...
</application>
</manifest>
...
<application ... >
<service android:name=".NomeServizio" />
...
</application>
</manifest>
L’elemento Service gode di numerosi attributi, come per
esempio la possibilità di dichiarare che il servizio può essere utilizzato solo
all’interno della propria applicazione (attraverso l’attributo android:exported
settato a false).
Per gli ulteriori dettagli dell’elemento Service è
consigliabile consultare la documentazione di Android.
Creazione del
servizio locale
Un servizio locale può essere avviato da qualsiasi altro
componente attraverso il metodo startService() che risulta poi essere una chiamata
al metodo onStartCommand().
Quando un servizio viene avviato, esso ha un ciclo di vita
indipendente rispetto al componente che lo ha avviato e può girare in
background per un tempo indefinito fino a quando lo stesso servizio richiama il
metodo stopSelf() oppure un altro componente può richiamare il metodo
stopService().
Un componente come una activity può avviare il servizio
attraverso il metodo startService() e passare un Intent che specifica il
servizio e include qualsiasi dato da utilizzare all'interno del servizio. Il servizio
riceve l’Intent nel metodo onStartCommand(). Tradizionalmente ci sono due classi che possono essere estese
per creare un servizio di questo tipo:
- Service: E’ la classe base per tutti i servizi. Quando viene estesa, è importante creare un nuovo thread all’interno del quale eseguire tutto il lavoro previsto per il servizio, poiché, come già precisato, il servizio può impattare sulle performance del dispositivo in quanto gira di default sul thread principale del processo che lo ha creato.
- IntentService: è una sottoclasse di Service che usa un Worker Thread per gestire tutte le richieste di avvio, una alla volta. Questa è il miglior modo di implementare un servizio se non si hanno particolari richieste di simultaneità tra richieste. In questo caso, quello che bisogna fare è implementare il metodo onHandleIntent() che riceve l’intent per ogni richiesta di avvio così che si può implementare il lavoro da svolgere.
Per meglio comprendere come implementare un servizio
ipotizziamo di voler creare una applicazione che inizializzi un player musicale
per la riproduzione di un qualsiasi suono a scelta. Il nome del progetto è il
seguente: PlayerMusicService.
Prima di procedere si scelga un file audio e lo si inserisca
in una cartella raw, appositamente creata nella cartella res del progetto
Android.
Successivamente si crei una classe MyService che estenderà
Service. L’IDE ci obbligherà a ridefinire il metodo onBind() che verrà lasciato
in maniera da ritornare un valore null. Come già precisato precedentemente, un
Service va dichiarato nel file Manifest. Nel caso del progetto in oggetto basta
aggiungere il seguente elemento xml come child del tag application:
<service android:enabled="true"
android:name=".MyService" />
Passo successivo: creare due Button nell’activity principale
ai quali si assegnerà la responsabilità di avviare e stoppare il servizio. Ciò
può essere implementato attraverso un metodo di inizializzazione della classe
MainActivity di nome inizializza(). Esso deve essere richiamato appena viene
creata l’Activity.
private void inizializza()
{
startButton = (Button) findViewById(R.id.startButton);
stopButton = (Button)
findViewById(R.id.stopButton);
startButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
startService(new Intent(getBaseContext(), MyService.class));
}
});
stopButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
stopService(new Intent(getBaseContext(), MyService.class));
}
});
}
A questo punto, non resta altro che spostare l’attenzione
sulla classe MyService, il cui codice è riportato per intero:
public class MyService extends Service
{
private MediaPlayer mPlayer;
private Context context;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
public void onCreate()
{
super.onCreate();
mPlayer = MediaPlayer.create(this, R.raw.audiotest);
context = MyService.this.getApplicationContext();
}
@Override
public void onDestroy()
{
super.onDestroy();
Toast.makeText(this, "Player terminato", Toast.LENGTH_SHORT).show();
mPlayer.reset();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Toast.makeText(this, "Player Avviato", Toast.LENGTH_SHORT).show();
mPlayer.start();
return START_STICKY;
}
}
Un aspetto da tenere presente è che il metodo
onStartCommand() prevede un valore di ritorno. Esso stabilisce come si dovrà
comportare il sistema nel caso in cui il servizio venga eliminato. Nel caso del valore START_STICKY il Service
viene mantenuto nello stato di started ma l’Intent ricevuto non viene
mantenuto. Android proverà a ricreare il servizio invocando nuovamente il
comando onStartCommand() passando però un Intent a Null a meno che non ve ne
siano di pendenti. Per i possibili valori è consigliato consultare la
documentazione di Android.
Avviando l’applicazione si potrà notare come premendo il
tasto Start e uscendo dall’applicazione, l’esecuzione dell’audio non verrà
interrotta.
Tuttavia, come già ampiamente discusso, l’esecuzione di un
Service viene sempre effettuata nel thread principale. Sarà quindi il caso di
sfruttare un Thread apposito oppure la classe IntentService che si preoccuperà
di eseguire il servizio in un Worker Thread.
0 commenti:
Posta un commento