giovedì 10 marzo 2011

Intent e intent-filter

Il concetto di intent è, a mio avviso, il concetto più interessante introdotto da Google per la programmazione di applicazioni su Android. Un intent è un oggetto (generato dall'omonima classe Intent, nel package android.content) che descrive un'azione da far effettuare a un componente su un insieme di dati. Si tratta, in altre parole, di un meccanismo che consente, attraverso lo scambio di oggetti Intent, di avviare altri componenti della stessa applicazione oppure, cosa assai più interessante, di avviare componenti di altre applicazioni!
Come già detto in precedenza, le activity di un'applicazione realizzano una oppure tutte le funzionalità previste per l'interazione con l'utente. Molto dipende dalla complessità dell'applicazione e da come essa viene scritta dal programmatore. Un'activity di un'applicazione può realizzare, con la dovuta efficienza, una precisa funzionalità al punto che la stessa sia poi richiesta all'interno di altre applicazioni.
Se sul dispositivo esiste già un componente capace di portare a termine l'azione necessaria all'applicazione, il programmatore, anziché scrivere altro codice, può riciclare quest'ultimo attraverso l'invocazione di un intent. La risoluzione di un intent può inoltre stabilire la presenza di più componenti capaci di portare a termine la stessa azione. In tal caso verrà offerto all'utente la possibilità di decidere quale componente attivare per far proseguire l'applicazione.
Un intent può avviare, con metodi diversi, activity, service o broadcast receiver. In questo articolo mi occuperò dei metodi e dei meccanismi che Android mette a disposizione del programmatore per l'avvio di activity.

E' possibile ricondurre un'activity in una delle seguenti classi:
  • intent espliciti: sono intent che avviano un componente già noto, rintracciandolo attraverso il nome a lui assegnato;
  • intent impliciti: sono intent che tentano di avviare un componente le cui caratteristiche coincidono con quelle descritte nell'intent stesso;
Il meccanismo risolutivo per gli intent impliciti va sotto il nome di intent resolution. Per stabilire i migliori componenti presenti sul dispositivo e capaci di aiutare l'activity chiamante, il meccanismo di intent resolution cerca di accoppiare le caratteristiche date all'oggetto Intent con quelle dichiarate dalle activity nel proprio manifesto (il file AndroidManifest.xml), con elementi intent-filter.
Gli intent-filter descrivono, allora, le competenze e le capacità delle activity installate sul dispositivo. Un activity senza intent-filter riceverà, pertanto, solo intent espliciti.
L'istanza di un oggetto Intent è caratterizzata dai seguenti campi:
  • ComponentName: è il nome del componente che dovrebbe ricevere l'intent e gestirne, eventualmente, i contenuti extra (spiegherò in avanti come). Si tratta di un parametro usato, quindi, per l'invocazione di intent espliciti. E' possibile passarlo a un costruttore oppure impostarlo in un secondo momento, attraverso i metodi setComponent(), setClass() oppure setClassName(). Un intent esplicito verrà sempre preferito a un intent implicito, ignorando, se presenti nell'oggetto, tutte le altre proprietà in esso contenute. La classe ComponentName offre diversi costruttori (come ad esempio ComponentName(String package_name,String class_name), ComponentName(Context package,String class_name) e ComponentName(Context package, Class class)) ognuno di questi permette di identificare uno specifico componente di un'applicazione. Il metodo getComponent() ritorna il ComponentName impostato per l'intent;
  • Action: rappresenta, con una stringa, l'azione da eseguire. Nella classe Intent sono definite molte azioni di default e altre possono essere aggiunte dal programmatore. I metodi setAction() e getAction() permettono, rispettivamente, di impostare e leggere l'azione espressa nell'intent. Per un elenco completo delle costanti di default vi invito a consultare la documentazione in linea della classe Intent. E' preferibile rispettare, per la descrizione dell'action, la seguente sintassi: package_name.intent.action.ACTION. Alcune action più usate sono ACTION_MAIN (per avviare un'activity segnata come activity principale) e ACTION_EDIT (a cui corrisponde l'azione per la modifica dei dati passati). Un'activity dichiara nel proprio manifesto le azioni che è in grado di gestire attraverso elementi action, a cui va passato il parametro action:name con la stringa dell'azione;
  • Data: indica l'indirizzo (con un URI) e il tipo di dati su cui agire. Può essere impostato con setData(), setType() o setDataAndType(). Le activity dichiarano nel loro manifesto (con elementi data e attributo android:mimeType) le tipologie di dati che sono in grado di manipolare. Il tipo di dati permette di inquadrare meglio l'azione da effettuare che altrimenti rimarrebbe generica. Ad esempio, con l'azione ACTION_EDIT si possono editare immagini e/o testo. Se però indichiamo nel manifesto la proprietà android:mimeType=image/* per l'elemento data del filter-intent ne aumentiamo sicuramente la descrizione (in questo caso l'azione ACTION_EDIT è dunque orientata alla modifica di immagini). Il riferimento ai dati è specificato per mezzo della sintassi scheme://host:port/path. Ogni singolo elemento della sintassi appena vista prevede il proprio attributo da sistemare all'interno del manifesto (se necessario). Esistono infatti, per l'elemento data, gli attributi android:host, android:port, android:path e android:scheme. L'attributo host e port formano assieme l'authority e permette di individuare il content provider;
  • Category: permette di suggerire nell'intent le activity in base al compito che queste hanno all'interno di altre applicazioni. Come per le azioni, la classe Intent definisce molte categorie. Vi invito pertanto a vedere un elenco completo delle categorie disponibili sulla guida in linea per Android. Ad esempio, le activity che figurano all'interno del launcher di Android (quelle che avviano l'applicazione, con l'azione ACTION_MAIN) hanno tutte la categoria CATEGORY_LAUNCHER. I metodi addCategory() e getCategory() permettono, rispettivamente, di aggiungere o leggere il nome di una categoria. La categoria di un un'activity viene dichiarata all'interno del manifesto attraverso l'elemento category e l'attributo android:category (che contiene la stringa che identifica la categoria);
  • Extras: sono coppie chiave-valore associate all'intent attraverso il metodo putExtras(Bundle extras). L'activity ricevente può estrarre i valori aggiunti in fase di richiesta con il metodo getExtras(). La classe Intent dispone di molti metodi set, il valore inserito viene legato a una chiave che ne permette in seguito la restituzione, con uno dei metodi get (dipende dal tipo aggiunto all'intent). Possiamo assegnare all'intent anche un oggetto a patto di dichiarare la classe dell'oggetto come parte dell'interfaccia Serializable (e usare il metodo putExtras(String key,Serializable obj)). Trovate un elenco completo dei metodi per gli extras nella documentazione in linea di Android;
  • Flags: sono impostazioni di vario tipo usate dal sistema operativo per caratterizzare l'avvio di un'activity. Ogni impostazione viene passata con il metodo setFlags(), a cui va passata una costante (definita nella classe Intent) che la rappresenta. Come al solito, trovate nella guida in linea un elenco completo dei flag. Quelli più usati sono FLAG_ACTIVITY_SINGLE_TOP (che permette di non avviare una nuova istanza per l'activity se già presente, l'activity verrà riciclata) e FLAG_ACTIVITY_NEW_TASK (che avvia l'activity all'interno di un nuovo task che ha la stessa affinità);
Dopo aver caratterizzato un intent con le proprietà volute, possiamo avviare la sua risoluzione (che avverrà a run-time) attraverso il metodo startActivity(). Ecco, ad esempio, un pezzo di codice che crea un intent esplicito è lo avvia:
Intent helpIntent=new Intent(requestActivity.this,helpActivity.class);
startActivity(intent);
Trattandosi di un intent esplicito occorre precisare nella richiesta il componente che la riceverà (il secondo parametro passato al costruttore). In questo esempio, invece:
Intent helpIntent=new Intent();
helpIntent.setAction(android.intent.action.ACTION_EDIT);
helpIntent.putExtras(message,userText);
helpIntent.setType(text/plain);
startActivity(helpIntent);
viene tentato di avviare un intent implicito, il componente dovrà avere la capacità di editare un testo.
Il meccanismo di intent-resolution analizza esclusivamente i campi Action, Data e Category dell'oggetto Intent. Gli attributi Extras e Flag non influenzano la ricerca. L'intent-resulotion entra gioco solo se l'intent invocato è di tipo implicito. Per consegnare la richiesta descritta nell'intent a un componente vengono controllati i tre campi dati detti sopra, il test coinvolgerà inevitabilmente tutti campi dati presenti nell'intent-filter. Poiché un elemento intent-filter può contenere più elementi action, category e data, il test sui campi dati sarà superato con successo solo se le proprietà dell'oggetto Intent coincidono con almeno un elemento dell'intent-filter. Ad esempio, se l'intent-filter di un componente descrive più action, il test sul campo dati Action verrà superato solo se l'azione nell'oggetto Intent è fra gli intent-filter del componente (ricordate, infatti, che un oggetto Intent può contenere una sola Action mentre un intent-filter ne può contenere più di una). L'esempio appena visto vale anche per i campi dati Data e Category, che pure possono essere numerosi nell'intent-filter (dipende dalle capacità del componente). Ogni componente, anche se non indicato nell'intent-filter, viene assegnato alla categoria android.intent.category.DEFAULT. Il meccanismo di intent-resolution potrebbe, infine, non trovare nessun componente capace di gestire l'intent. In tal caso verrà lanciata un eccezzione e l'activity richiedente verrà dunque chiusa.

1 commento: