Servizi, BroadcastReceiver, Notifiche, SharedPreferences e Toast: i componenti più visibili e oscuri di Android

Roberto Orgiu
Roberto Orgiu
Servizi, BroadcastReceiver, Notifiche, SharedPreferences e Toast: i componenti più visibili e oscuri di Android

Siamo abituati a vedere apparire sulla nostra barra le più svariate notifiche, ma come fanno gli sviluppatori a farle comparire? E le notifiche push? Sono davvero un qualcosa di così astratto?

Partiamo subito col rispondere all'ultima domanda: no, non sono così astratte o difficili, anzi, sono pressoché normali notifiche che appaiono nella nostra status bar, ma procediamo con un esempio noto ai più: Whatsapp. Quando un nostro contatto ci scrive, sulla nostra barra appare per magia la classica iconcina verde, ma come funziona? In realtà, il meccanismo delle notifiche push si basa su 2 dei 4 elementi che appaiono nel titolo di questo articolo: per ricevere un messaggio è necessario avere installato il famoso software di messaggistica, che si occuperà di inizializzare un servizio che periodicamente (con intervalli molto brevi, a dirla tutta) si connetterà al server della società e scaricherà tutti i messaggi che ci sono stati spediti, inviando al NotificationManager l'avviso che abbiamo qualcosa da leggere.

Semplice, no? Il meccanismo in sé lo è sicuramente, ma Google ci spiega (pubblicando quest'ottimo articolo in lingua inglese.) che esistono metodi meno dispendiosi per implementare tale servizio.

Vediamo ora una panoramica di cosa sono questi componenti "sconosciuti" e come possiamo utilizzarli per le nostre applicazioni:

  • Servizio: sostanzialmente è una classe simile all'Activity, ma ha metodi diversi e non ha una UI, è molto utile per svolgere operazioni in background, ed è alla base della ricezione di notifiche per i programmi che necessitano una connessione ad internet, ma può essere usato praticamente per ogni cosa;
  • BroadcastReceiver: è un tipo particolare di ricevitore, nel senso che si occupa di ricevere (e gestire) gli Intent che il sistema invia in broadcast;
  • Notifica: è una segnalazione non invasiva e si usa quando si vuole notificare l'utente di qualche cosa senza interrompere l'attività che sta svolgendo;
  • Toast: sono quei simpatici messaggi che appaiono e scompaiono automaticamente, tipicamente scritti in bianco con uno sfondo rettangolare arrotondato nero leggermente trasparente;
  • SharedPreferences: è un particolare file che permette di utilizzare una mappa (coppie chiave-valore, quindi) per gestire le preferenze delle nostre applicazioni.

Iniziamo dall'ultimo elemento, che è anche il più semplice da affrontare: la classe astratta delle preferenze condivise ci fornisce infatti dei semplici metodi per accedere e modificare delle coppie di chiavi e valori, esattamente come faremmo per le normali mappe; per istanziare questo particolare oggetto faremo ricorso a questa istruzione:

pref = getSharedPreferences(MY_PREF,MODO_DI_ACCESSO);

dove MY_PREF è una stringa contente il nome che vogliamo dare al file delle preferenze, mentre MODO_DI_ACCESSO è un intero che troviamo in Context (troviamo qui la lista completa dei modi e del loro impiego) che specifica in quale modo vogliamo accedere (o creare) le nostre opzioni, mentre per accedere ad una determinata chiave, useremo

boolean valoreStartAtBoot = pref.getBoolean(CHIAVE_VARIABILE, VALORE_DI_DEFAULT_DELLA_CHIAVE);

Naturalmente, SharedPreferences ci fornisce metodi per accedere ad ogni tipo di dato standard, oltre che per settarli, anche se, in quest'ultimo caso, la procedura è leggermente più macchinosa

SharedPreferences.Editor editor = pref.edit();
editor.putBoolean(CHIAVE_VARIABILE, NUOVO_VALORE_VARIABILE);
editor.commit();

bisogna infatti passare tramite un'istanza della sottoclasse Editor per poter inserire il nuovo valore e successivamente effettuarne il commit, ma le preferenze sono decisamente facili da gestire (oltre che utili).

Passiamo ora ai Toast, molto utili se si vuole notificare all'utente un qualche avvenimento che non necessiti la loro diretta interazione; anche in questo caso, il meccanismo è piuttosto intuitivo e il codice si spiega praticamente da solo:

CharSequence testo = "Hai fatto tap!";
int durata = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(getApplicationContext(), testo, durata);
toast.show();

facciamo comunque due brevi appunti:

  1. il primo parametro è il context dell'applicazione
  2. il parametro durata, può assumere solamente un valore, oltre quello che abbiamo utilizzato, ed è Toast.LENGTH_LONG, che sono le due durate standard forniteci da Google.

Inoltriamoci ora in qualcosa di leggermente più compesso, le notifiche, che ci richiederanno poche righe di codice ma sono leggermente più contorte dei due esempi precedenti. Iniziamo dando un'occhiata al codice:

NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notifica = new Notification(R.drawable.ic_launcher,"Sono una notifica",System.currentTimeMillis());
PendingIntent pendingIntent = PendingIntent.getService(this, 0, new Intent(this,ToastExample.class), PendingIntent.FLAG_CANCEL_CURRENT);
notifica.setLatestEventInfo(this, "Tiwiz Tutorial", "Demo servizi e notifiche", pendingIntent);
notifica.flags = Notification.FLAG_AUTO_CANCEL;
manager.notify(ID_NOTIFICA, notifica);

Vediamo quindi i passi che dobbiamo compiere per utilizzare questa funzionalità:

  1. Otteniamo un puntatore (abbiamo preso in prestito questo concetto dal C, ma ci sembrava più appropriato della parola istanza) al motore di gestione delle notifiche, legato al context;
  2. impostiamo la notifica, specificando l'icona, il testo che appare nella barra alla comparsa della notifica e quando si vuole mostrare la notifica (con System.currentTimeMillis() specifichiamo che dev'essere mostrata subito);
  3. definiamo l'azione che vogliamo venga avviata al tap sulla nostra notifica: la classe PendingIntent ci permette di specificare, infatti se vogliamo avviare (come in questo caso) un servizio o un Activity (tramite getActivity, che accetta gli stessi parametri di getService), impostando come parametri il context dell'applicazione, il codice di richiesta, l'intent che vogliamo lanciare e con quali flag;
  4. impostiamo infine le ultime informazioni, ovvero il context, il titolo che vedremo nella tendina, seguito dalla descrizione e dal servizio sopra dichiarato. Da notare che è possibile (come abbiamo fatto) inserire due testi diversi, uno per quando la notifica appare ed uno per quando tiriamo giù il menu a tendina e andiamo a fare tap sulla nostra icona;
  5. abbiniamo quindi i flag che ci interessano (la lista completa è qui), nel nostro caso  abbiamo deciso che la notifica venga cancellata al tap;
  6. mostriamo la notifica tramite un ID di nostra scelta (ID_NOTIFICA).

E il gioco è fatto!

Avventuriamoci ora nelle parti più interessanti di questo articolo: i BroadcastReceiver e Services.

Entrambi vanno dichiarati nel file AndroidManifest, secondo due diverse sintassi:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver android:name=".ServiceReceiver">
   <intent-filter>
      <action android:name="android.intent.action.BOOT_COMPLETED" />
   </intent-filter>
</receiver>

per i BroadcastReceiver e

<service android:enabled="true" android:name=".ServiceDemo" />

per i servizi.

Nel primo caso, abbiamo inserito anche il relativo permesso perché lo specifico compito che volevamo assolvere lo richiedeva, ma è un'istruzione che non sempre è necessaria, mentre la spiegazione del nome della classe (ServiceReceiver) e dell'intent che deve servire (android.intent.action.BOOT_COMPLETED) sono necessari per l'applicazione. In questo caso, abbiamo dimostrato come avviare un servizio al boot dell'applicazione, anche se questo procedimento potrebbe causare delle criticità: il sito degli sviluppatori dice infatti che, a seconda della versione di Android utilizzata, il messaggio broadcast viene inviato prima dell'inizializzazione della SD esterna, quindi, in caso di installazione sulla microSD del telefono, il programma non riceverebbe il messaggio e il receiver non avrebbe quindi il proprio input e non partirebbe, facciamo quindi attenzione, se decidiamo di usare questo tipo di servizio.

Per quanto riguarda il servizio, la sintassi è semplice e riguarda, essenzialmente, l'abilitazione all'esecuzione (android:enabled="true") e il nome del servizio (ServiceDemo).

Vediamo ora come si combina l'XML con il Java per portare avanti queste implementazioni; innanzitutto, dobbiamo specificare che il nostro receiver implementa l'apposita istanza delle API di Android:

public class ServiceReceiver extends BroadcastReceiver

e procedere quindi con l'overriding del metodo onReceive, che si occupa di gestire l'intent (che possiamo filtrare poi con il metodo getAction)

public void onReceive(Context context, Intent intent)

Arrivati a questo punto, possiamo tirare un po' il fiato, siamo all'ultima parte del nostro percorso e ci occupiamo di come creare un servizio, estendendo la relativa classe e facendo l'override dei quattro metodi che ci servono (il servizio ha un ciclo di vita diverso da quello dell'Activity, a grandi linee viene prima creato, poi avviato ed in seguito distrutto, ma potete leggerne i dettagli qui):

public class ServiceDemo extends Service{

@Override
public IBinder onBind(Intent arg0) {
return null;
}

public void onCreate(){
//quando il servizio viene creato
}

public void onDestroy(){
//quando il servizio viene killato
}

public void onStart(Intent intent, int startid){
//il core del servizio
}

Il metodo onStart sarà dove andremo a scrivere il nostro codice, che è in realtà un po' l'equivalente del metodo onCreate dell'Activity.

Vediamo quindi come avviare o arrestare il servizio che abbiamo creato e scopriamo di quanto sia, in realtà, semplicissimo: per avviarlo, ci basta una sola riga di codice, in cui specifichiamo (sotto le spoglie di intent) quale servizio avviare, e analoga è la situazione se vogliamo arrestarlo

startService(new Intent(this,ServiceDemo.class)); //avvio il servizio

stopService(new Intent(this,ServiceDemo.class)); //killo il servizio

Se vogliamo invece che il nostro servizio termini autonomamente, basta utilizzare l'apposita API

this.stopSelf();

Eccoci dunque al termine del nostro percorso e vi lasciamo con il link al nostro forum, dove potrete trovare i sorgenti pronti per essere spulciati ;)