matteo bonifazi, alessandro martellucci, stefano sanna android · 6 android rogrammazione avanzata...

45
Fabio Collini Matteo Bonifazi, Alessandro Martellucci, Stefano Sanna Android Programmazione avanzata BESTSELLER BESTSELLER 2 a E D I Z I O N E 2 a E D I Z I O N E Sviluppo multidevice >> Android Wear, Chromecast, Bluetooth Low Energy >> Programmazione funzionale con RxJava >> Testing e qualità del codice >>

Upload: phungtuong

Post on 25-Feb-2019

221 views

Category:

Documents


0 download

TRANSCRIPT

Fabio Collini Matteo Bonifazi, Alessandro Martellucci, Stefano Sanna

AndroidProgrammazione avanzata

BESTSELLERBESTSELLER

A COLORI

A COLORI

NUOV

A RISTAMPA

NUOV

A RISTAMPA

BESTSELLERBESTSELLER

2a EDIZIONE

2a EDIZIONE

Sviluppo multidevice >>

Android Wear, Chromecast, Bluetooth Low Energy >>

Programmazione funzionale con RxJava >>

Testing e qualità del codice >>

AndroidProgrammazione avanzata

Seconda edizione

Fabio ColliniMatteo Bonifazi, Alessandro Martellucci, Stefano Sanna

Android | Programmazione avanzataSeconda edizione

Autori: Fabio Collini, Matteo Bonifazi, Alessandro Martellucci, Stefano Sanna

Collana:

Editor in Chief: Marco AleottiProgetto grafico: Roberta VenturieriImmagine di copertina: © scanrail | Thinkstock

© 2015 Edizioni Lswr* – Tutti i diritti riservati

ISBN: 978-88-6895-071-2

I diritti di traduzione, di memorizzazione elettronica, di riproduzione e adattamento totale o parziale con qualsiasi mezzo (com-presi i microfilm e le copie fotostatiche), sono riservati per tutti i Paesi. Le fotocopie per uso personale del lettore possono essere effettuate nei limiti del 15% di ciascun volume dietro pagamento alla SIAE del compenso previsto dall’art. 68, commi 4 e 5, della legge 22 aprile 1941 n. 633.

Le fotocopie effettuate per finalità di carattere professionale, economico o commerciale o comunque per uso diverso da quello personale possono essere effettuate a seguito di specifica autorizzazione rilasciata da CLEARedi, Centro Licenze e Autorizzazioni per le Riproduzioni Editoriali, Corso di Porta Romana 108, 20122 Milano, e-mail [email protected] e sito web www.clearedi.org.

La presente pubblicazione contiene le opinioni dell’autore e ha lo scopo di fornire informazioni precise e accurate. L’elaborazione dei testi, anche se curata con scrupolosa attenzione, non può comportare specifiche responsabilità in capo all’autore e/o all’edi-tore per eventuali errori o inesattezze.

L’Editore ha compiuto ogni sforzo per ottenere e citare le fonti esatte delle illustrazioni. Qualora in qualche caso non fosse riuscito a reperire gli aventi diritto è a disposizione per rimediare a eventuali involontarie omissioni o errori nei riferimenti citati.

Tutti i marchi registrati citati appartengono ai legittimi proprietari.

Via G. Spadolini, 720141 Milano (MI)Tel. 02 881841www.edizionilswr.it

Printed in Italy

Finito di stampare nel mese di giugno 2015 presso “Rotolito Lombarda” S.p.A., Pioltello (MI)

(*) Edizioni Lswr è un marchio di La Tribuna Srl. La Tribuna Srl fa parte di .

5

Sommario

PREFAZIONE .............................................................................................................. 9

INTRODUZIONE .......................................................................................................11

1. ACTIVITY E TASK IN BACKGROUND di Fabio Collini ................................17Ciclo di vita di una Activity ......................................................................................................17Gestione dei metodi di callback comuni a più Activity ................................................... 18Flusso delle callback del ciclo di vita di una Actvity .........................................................22Salvataggio dello stato di una Activity.................................................................................23Generazione automatica delle implementazioni di Parcelable................................................................................................................................26Tipi di dati da salvare in una Activity ...................................................................................28UI Thread e concorrenza .........................................................................................................29Tipologie di task in background .............................................................................................33AsyncTask e Loader ..................................................................................................................34IntentService e LocalBroadcastManager ............................................................................35EventBus ed Executor ............................................................................................................... 41

2. PROGRAMMAZIONE FUNZIONALE di Fabio Collini ................................47Lambda expression e method reference ............................................................................ 48Retrolambda ................................................................................................................................51Linguaggi alternativi a Java su Android ...............................................................................52Manipolazione di dati con gli Stream di Java 8 .................................................................53RxJava...........................................................................................................................................57Flussi di dati asincroni ...............................................................................................................61Gestione delle Subscription ................................................................................................... 66Gestione degli errori ................................................................................................................ 68Chiamate a servizi REST con Retrofit ....................................................................................71Combinare più flussi di dati con RxJava ..............................................................................73Hot e cold Observable ............................................................................................................ 80Manipolazione di flussi di dati .............................................................................................. 84Utilizzo dei Subject .................................................................................................................. 86Eventi della UI con RxJava .......................................................................................................92Gestione dei nested Observable .......................................................................................... 94Task in background collegati al ciclo di vita di una Activity .......................................... 96

6

Android | Programmazione avanzata

3. GRAFICA E INTERFACCIA UTENTE di Fabio Collini ................................109Density e screen size ..............................................................................................................109Immagini 9patch .......................................................................................................................111Drawable complessi .................................................................................................................111Custom View per implementare un flat Button................................................................ 115Immagini Mipmap .................................................................................................................... 117Vector Drawable ....................................................................................................................... 117Organizzazione delle risorse .................................................................................................118Canvas, Paint e Shader ............................................................................................................ 121Color filter ................................................................................................................................. 126Utilizzo delle Custom View per migliorare le performance .......................................... 128Animazioni Android 2.x ......................................................................................................... 136Animazioni Android 3.0 ........................................................................................................ 138Animazioni Android 4.4 ........................................................................................................ 143Animazioni Android 5.0 ........................................................................................................144Gestione degli eventi touch ..................................................................................................148Drag di una View con animazioni........................................................................................150

4. SUPPORTO MULTIDEVICE di Fabio Collini................................................. 157Dimensioni degli schermi dei dispositivi Android .......................................................... 157Gestione delle risorse al variare della dimensione dello schermo.............................. 159Gestione della orientation ..................................................................................................... 162Gestione dei layout in base alla larghezza dello schermo ............................................ 163Adattamento di un layout in base alle dimensioni ......................................................... 163Utilizzo dei Fragment ..............................................................................................................164Gestione di una Activity multiFragment ........................................................................... 167Gestione delle transaction .................................................................................................... 172Altri utilizzi dei Fragment ...................................................................................................... 174Activity con singolo Fragment ............................................................................................. 174Custom View Vs Fragment ................................................................................................... 176

5. BLUETOOTH di Stefano Sanna ....................................................................... 179L’ultimo immortale .................................................................................................................. 179Bluetooth Classic e Bluetooth Low Energy .......................................................................180Panoramica del protocollo Bluetooth Classic .................................................................. 182Panoramica del protocollo Bluetooth Low Energy ..........................................................189Setup per sperimentazione ................................................................................................... 195Bluetooth Classic su Android ..............................................................................................209Attivazione e visibilità ............................................................................................................210Discovery dei dispositivi ....................................................................................................... 212Pairing.........................................................................................................................................216Discovery dei servizi ...............................................................................................................219Apertura diretta di connessioni RFCOMM ...................................................................... 222Bluetooth Low Energy su Android ...................................................................................... 223Bluetooth beacon ...................................................................................................................230

7

Sommario

La tecnologia iBeacon ........................................................................................................... 232Implementazione di un beacon .......................................................................................... 235Scansione dei beacon in ambiente Android .................................................................... 238Beacon e geofencing .............................................................................................................. 242Inversione di ruoli ................................................................................................................... 243Sicurezza e privacy................................................................................................................. 245

6. ANDROID WEAR di Matteo Bonifazi ........................................................... 249Android Wear idea .................................................................................................................250Android Wear design ............................................................................................................ 252Notifiche tramite Android Wear ........................................................................................ 255Android Wear App ................................................................................................................. 265Android Wear Watch face ................................................................................................... 287

7. CHROMECAST E GOOGLE CAST di Alessandro Martellucci .................. 305Che cos’è il Chromecast .......................................................................................................305Installazione e configurazione ............................................................................................306Chromecast App .................................................................................................................... 307Google Cast .............................................................................................................................. 313Applicazione Sender ...............................................................................................................314Il bottone di Cast .....................................................................................................................318Il media router framework .................................................................................................... 319Il ruolo del Google Play Services ......................................................................................... 331La gestione del receiver da parte dell’applicazione client .............................................333La riproduzione del contenuto ............................................................................................340La comunicazione tra le applicazioni ................................................................................ 345Il controllo del volume ...........................................................................................................350I sottotitoli ................................................................................................................................. 351Il logging ................................................................................................................................... 354Applicazione receiver ............................................................................................................ 354Applicazione receiver: concetti avanzati .......................................................................... 363Tipi e formati supportati ....................................................................................................... 369Android TV .............................................................................................................................. 370

8. QUALITÀ DEL CODICE di Fabio Collini .......................................................375Build dei progetti con Gradle ................................................................................................375Testing del codice ................................................................................................................... 378Tipologie di testing ................................................................................................................ 379Unit test con JUnit ..................................................................................................................381Test di integrazione ................................................................................................................ 387Robolectric ............................................................................................................................... 387Testing end to end .................................................................................................................. 389Acceptance test ...................................................................................................................... 396Monkey testing ....................................................................................................................... 397Copertura dei test .................................................................................................................. 397

8

Android | Programmazione avanzata

Analizzatori statici del codice ............................................................................................. 398Continuous Integration ......................................................................................................... 399Strategia di test di una applicazione Android .................................................................402Testing di codice legacy ........................................................................................................402Servizi web remoti ................................................................................................................. 404Dependency injection ...........................................................................................................405Dagger ........................................................................................................................................412Testing di codice dipendente dal tempo ........................................................................... 425Testing su Java Virtual Machine di una applicazione Android ...................................426Testing della UI con Espresso .............................................................................................. 433Model View Presenter .......................................................................................................... 435Task asincroni con Model View Presenter e RxJava ......................................................447Oggetti fake con Javassist ....................................................................................................450BDD e TDD con Espresso e Model View Presenter ...................................................... 455

APPENDICE: SICUREZZA .................................................................................. 457La sicurezza nell’azienda ...................................................................................................... 457Utilizzo delle WebView.........................................................................................................463Android permission ...............................................................................................................468

INDICE ANALITICO ............................................................................................ 475

9

Quando gli autori di Android - Programmazione avanzata mi hanno chiesto di scrivere la prefazione alla nuova edizione, il mio pensiero è andato immediatamente ai nu-meri impressionanti che il sistema operativo di Google ha macinato in questi ultimi anni, alla strada percorsa dal lancio del 2008 sul primo dispositivo targato HTC.Senza voler entrare nel dettaglio dei singoli dati, basti pensare che, secondo le stime di IdC, negli ultimi due anni circa 80 smartphone venduti su 100 sono stati equipag-giati con il sistema operativo del robottino. Proiettato su scala globale, questo dato significa un mercato potenziale di centinaia di milioni di persone.Significa inoltre che, attorno al 2018, il fatturato delle app per Android supererà quello generato dal principale concorrente, ioS di Apple. non a caso, Flipboard ha lanciato la propria versione per Android due anni dopo la versione per ioS, ma in poco più di un anno la metà degli accessi arriva ormai da smartphone Android.La motivazione per imparare a programmare applicativi per Android, però, non deve limitarsi alla metrica numerica. Perché leggere – e studiare – un volume dedicato a chi sviluppa in maniera avanzata per Android? La risposta sta nell’impressionante successo e nelle previsioni di cre-scita future, certo. un volume che tratta argomenti comuni per gli sviluppatori, con un approccio fuori dal comune, e si spinge a trattare argomenti che i classici testi di programmazione per Android spesso non considerano, non avrebbe un senso se non nella convinzione che “Google stia per conquistare le nostre vite”, per riprendere le parole di Fastcode.Provenendo dal mondo delle Telco, dove ho avuto la fortuna di lavorare al lancio della prima rete uMTS al mondo, del primo cellulare con TV digitale mobile e del primo Skypephone low cost, quando ancora gli smartphone non erano così diffusi, il sistema operativo Android non è qualcosa di nuovo. Prima ancora, come giornalista tecnologico, ho avuto l’opportunità di vivere le principali trasformazioni della telefo-nia mobile, tra cui il declino dei sistemi operativi proprietari e l’esplosione delle app.

Prefazione

10

Android | Programmazione avanzata

negli ultimi due anni sono successe, però, diverse cose che mi hanno fatto compren-dere quanto pervasivo possa essere – parallelamente all’avanzata dell’Internet degli oggetti – un sistema operativo così versatile e diffuso. Sono stato tra i pochi italiani a poter ricevere i Google Glass durante il programma Explorer, e Android Auto è qualcosa su cui ho lavorato negli ultimi 24 mesi per il debutto di Google nel mondo dell’auto.È da questo punto di vista, come osservatore privilegiato, che ho imparato ad ap-prezzare Google come un risolutore di problemi in (quasi) ogni ambito della nostra vita, una presenza amorfa che il Material design – l’unificazione di tutti i prodotti Google e app di terze parti sotto un unico cappello – rende concreta e possibile, un sistema operativo che rompe le regole logiche e fisiche note fino a oggi per ri-disegnare completamente il mondo che ci circonda. un Google che da prodotto si trasforma in presenza, con la giusta informazione sullo schermo giusto al momento giusto. Cellulari, auto, console gaming, schermi del desktop, computer portatili, we-arable e (forse) occhiali.In un mondo così variegato, la completa padronanza del sistema operativo targato Google è fondamentale per chi sviluppa applicativi complessi. Alessandro, Fabio, Matteo e Stefano ci guidano, attraverso i capitoli di Android Programmazione Avan-zata, nell’esplorazione di questo mondo affascinante e allo stesso tempo complesso. Per chi condivide la visione sul futuro di Android (e Google), sviluppando applicativi articolati, conoscerne gli anfratti più nascosti non può che rappresentare una tappa fondamentale del proprio percorso professionale.

Massimo CavazziniGlobal Uconnect – Head of Marketing&User Experience EMEA region

Fiat Chrysler Automobiles

11

Questo libro parla di una scommessa vinta. Android è il sistema operativo per dispo-sitivi mobili più diffuso al mondo. In realtà, non solo dà vita a smartphone e tablet, ma è presente in smartwatch, apparati televisivi, autovetture e sistemi embedded. E non solo in prodotti commerciali, ma anche in progetti indipendenti, vista la sua natura open source. L’ecosistema di Android si arricchisce costantemente di nuove API, nuove librerie, nuovi strumenti di sviluppo, nuovo hardware compatibile, nuove estensioni. Per la gioia degli sviluppatori e degli utenti. Sviluppatori motivati realiz-zano prodotti che entusiasmano gli utenti, che a loro volta aumentano la domanda di nuovi servizi e nuove applicazioni, che a sua volta alimenta la domanda di sviluppa-tori sempre più specializzati. È inevitabile: laddove c’è una tecnologia così pervasiva e ubiqua nella vita delle persone, in poco tempo si aprono spazi infiniti per nuove idee, nuove possibilità. L’obiettivo di questo libro è fornire agli sviluppatori che già conoscono le basi del-la programmazione su Android le conoscenze per affrontare problematiche meno comuni e trarre ispirazione dalle potenzialità più intriganti del sistema operativo. Partendo dalla propria esperienza professionale consolidata, gli autori hanno sele-zionato gli argomenti mirando alla crescita del lettore su due fronti: irrobustire la co-noscenza dei temi “core”, al fine di migliorare la propria padronanza della piattafor-ma (si vedano i temi sul supporto multidevice o la gestione dei task in background) e stimolare nuove esplorazioni sui temi più attuali (wearable e interazione con ap-parati televisivi).Gli argomenti discussi nei vari capitoli comprendono riferimenti non solo alle li-brerie standard di Android, ma anche ai molti progetti open source utilizzabili per lo sviluppo di applicazioni per questo sistema operativo. negli ultimi anni, infatti, molte aziende (un tempo startup e adesso colossi spesso quotati in borsa) stanno rilasciando librerie open source che semplificano varie fasi dello sviluppo. Square è sicuramente una delle realtà più attive, ma anche Facebook, yahoo!, Instagram

Introduzione

12

Android | Programmazione avanzata

e netFlix annoverano vari progetti open source attraverso i quali stanno facendo crescere varie community di sviluppatori. un esempio molto significativo è quello di RxJava (affrontato nel capitolo dedicato alla programmazione funzionale), una libreria open source sviluppata da netflix che sta rivoluzionando il modo di scrivere il codice delle applicazioni Android.In questo senso, pur se cristallizzato nelle sue pagine, questo libro è “vivo”, attualiz-zato non solo nella mera versione delle API, ma anche negli obiettivi. È interessante, infatti, notare come temi ritenuti avanzati nella prima edizione non lo siano più nella seconda: è il caso delle notifiche push, ormai consolidate sia client-side sia server-side, sulle quali è possibile trovare in rete numerosissimi esempi di codice e servi-zi gratuiti che implementano le funzionalità di backend (per esempio, parse.com). Altri, come nFC, hanno avuto poche evoluzioni (se non l’importante introduzione della modalità HCE) e rispetto alle aspettative di tre anni fa sono rimasti argomenti di nicchia.

A chi si rivolge questo libroQuesto libro è rivolto agli sviluppatori che hanno già una esperienza nello sviluppo di applicazioni Android e che vogliono migliorare le proprie skill di programmazione su questo sistema operativo mobile. Gli argomenti sono trattati partendo dal presuppo-sto che il lettore abbia una conoscenza dei principali concetti dello sviluppo di una applicazione Android.

Gli autoriFabio Collini si occupa, all’interno dell’acceleratore di startup nana Bianca di Firenze, dello sviluppo di varie applicazioni disponibili nel Play Store. dopo una esperienza su piattaforma Java Enterprise, dal 2009 si occupa di progettazione e sviluppo di ap-plicazioni Android. È attivo nella community sia come blogger (ha fondato e scrive su cosenonjaviste.it) sia come speaker nelle principali conferenze a livello nazionale. Come freelance, ha rilasciato due applicazioni che hanno ottenuto un buon numero di download.Matteo Bonifazi è Senior Android developer di open Reply (Gruppo Reply) e Google developer Expert per la piattaforma Android. Ha partecipato alla realizzazione di im-portanti progetti Android in campo nazionale e internazionale, riguardanti lo sviluppo di applicazioni Android innovative e personalizzazioni del sistema operativo Android. Collabora attivamente con il Google developer Group di Roma, dove si occupa prin-cipalmente di far conoscere alla comunità tutte le novità dell’ecosistema Android. È speaker per le più importanti conferenze italiane e internazionali.

13

Alessandro Martellucci è laureato in Informatica e da diversi anni segue il mercato mobile, con particolare interesse per l’ecosistema Android. A oggi è Android deve-loper presso openReply, società del gruppo Reply, e partecipa a progetti con diverse peculiarità: dal video streaming all’installazione di Android su dispositivi con hardware personalizzato. nel 2014 ha partecipato al droidcon uk con un seminario dedicato al video streaming in Android, parlando del Chromecast e dell’Android TV. Stefano Sanna è attualmente Manager presso open Reply (Gruppo Reply), dove coor-dina le attività di sviluppo di app native e ibride. Ha iniziato a sviluppare software per dispositivi mobili nel 1999, su un Psion 5MX. da allora ha lavorato su Java Micro Edi-tion, Symbian, ioS e dal 2009 si occupa di Android per applicazioni commerciali. Ha scritto due libri sullo sviluppo mobile e tenuto numerosi seminari su Java, Android e Bluetooth in Italia e all’estero. Lasciati gli smartphone, ama giocare con i suoi bambini a costruire robot e navi spaziali con i LEGo.

La strutturaIl libro è diviso in otto capitoli, ognuno dei quali contiene la trattazione di un argomen-to specifico. In alcuni casi sono presenti riferimenti ad altri capitoli, per questo motivo è consigliata la lettura nell’ordine in cui sono proposti. Il Capitolo 1 descrive nel dettaglio il ciclo di vita delle Activity; pur essendo uno dei primi argomenti affrontati quando ci si avvicina ad Android, non è sempre facile da di-gerire. In questo capitolo sono affrontate le problematiche più comuni, prima fra tutte la gestione dei task in background.Il Capitolo 2 affronta la programmazione funzionale su Android: purtroppo ancora non è possibile utilizzare Java 8 per lo sviluppo di applicazioni Android, anche se, come mostrato in questo capitolo, Retrolambda è un’ottima alternativa. Gran parte di questo capitolo è dedicata al framework RxJava, mostrando sia i concetti base sia quelli più avanzati.La grafica e la gestione dell’interfaccia utente sono l’argomento del Capitolo 3. Que-sto capitolo contiene varie tecniche da utilizzare per sviluppare la parte di uI di una applicazione. Fra gli argomenti trattati ci sono i drawable, le animazioni, i Canvas e la gestione degli eventi touch.Gestire al meglio le risorse è fondamentale (ma non sempre facile) per creare layout che si adattino ai vari dispositivi: il Capitolo 4 contiene un approfondimento su questo tema, con particolare riferimento alla gestione del supporto multidevice e all’utilizzo dei Fragment.L’argomento presentato nel Capitolo 5 è Bluetooth, con una dettagliata trattazione di Bluetooth Low Energy (BLE), la cui API ha fatto la sua comparsa su Android 4.3. BLE

Introduzione

14

Android | Programmazione avanzata

consente l’interfacciamento a dispositivi indossabili (tra cui le varie “band” per il fit-ness) e, più in generale, è la tecnologia abilitante per la Internet of Things.Tra il 2015 e il 2016 si prevede che il numero dei televisori connessi alla rete sfiorerà il miliardo di unità; Chromecast e Android TV (trattati nel Capitolo 6) rappresentano solo l’inizio di questa nuova era tecnologica che colpirà le nostre vite. Il supporto da parte di Mountain View, con il suo Google Cast, offre a tutti gli sviluppatori un mon-do di possibilità ancora inesplorate: l’on demand, il Gaming e l’Entertainment sono solo alcuni tra gli argomenti più interessanti, sui quali tutti gli sviluppatori dovrebbero scommettere.Come cambia la user experience sui nostri polsi? Il Capitolo 7, dedicato ad Android Wear, mostra nel dettaglio come sarà possibile estendere le proprie applicazioni su tutti i nuovi smartwatch powered by Google.Il testing su Android è il principale argomento del Capitolo 8: la prima parte contiene una panoramica sui vari tool disponibili, da quelli per il testing fino a quelli per l’analisi statica del codice e la continuous integration. nella seconda parte sono analizzate al-cune tecniche (come la dependency injection usando dagger e il pattern Model View Presenter) da utilizzare per scrivere codice testabile.Completa il volume l’Appendice, dedicata alla sicurezza delle applicazioni Android; questo tema viene affrontato mostrando alcune funzionalità messe a disposizione dal sistema operativo utilizzabili per aumentare la sicurezza delle applicazioni Android.

RisorseSito web ufficiale del libro: http://www.androidavanzato.itRepository codice: https://github.com/androidavanzatoE-mail per supporto e segnalazioni: [email protected]: @androidavanzato

RingraziamentiGli autori ringraziano l’editore per la fiducia riposta, in particolare Marco Aleotti per il suo supporto instancabile durante la scrittura e l’evoluzione dell’opera.Fabio ringrazia Letizia per averlo sopportato nonostante, durante la scrittura del libro, sia stato ancora più asociale del solito. Inoltre ringrazia Stefano per averlo coinvolto in questo progetto, e Matteo e Alessandro per aver arricchito, con i loro importanti contributi, gli argomenti trattati in questo libro.Matteo dedica il suo lavoro alle persone che lo hanno aiutato in modo diretto e indiret-to nella scrittura di questo libro. Push the limits!

15

Alessandro ringrazia, in egual maniera, tutti coloro che lo hanno supportato e aiutato durante la stesura del libro. È grazie a loro se il suo contributo è oggi migliore rispetto a com’era all’inizio.Stefano ringrazia in primis Fabio: senza la sua enorme tenacia e solidissima prepa-razione questa seconda edizione non avrebbe conosciuto gli onori della stampa. un grazie sincero va ad Alessandro e Matteo, che hanno accolto con entusiasmo questo progetto, contribuendo con temi freschi e appassionanti. Infine, dedica il risultato di questo lavoro ai piccoli Riccardo e Alessandro, la cui intuizione, nel chiedere “Papà, anche qui dentro c’è un piccolo computer?”, è misura della pervasività delle tecnologie raccontate in questo libro.

Introduzione

17

Questo capitolo mostra nel dettaglio l’utilizzo delle Activity

all’interno di una applicazione, ponendo particolare attenzione

sull’esecuzione dei task in background. Sono analizzate le

varie soluzioni disponibili: sia quelle contenute nel framework

Android (come gli Async Task e gli Intent Service) sia

quelle utilizzabili attraverso librerie di terze parti.

Ciclo di vita di una ActivityIl concetto di Activity è uno dei più importanti nello sviluppo di una applicazione An-droid ed è sicuramente il primo che ogni sviluppatore che si avvicina a questo siste-ma operativo apprende. nei primi esempi, a una Activity corrisponde una schermata dell’applicazione; questa corrispondenza non è vera negli esempi più complessi, ma è comunque un buon modo per familiarizzare con il concetto di Activity.Scrivendo una applicazione Android che implementa il classico hello world, è necessa-rio creare una classe che estende Activity e riscrivere un solo metodo: onCreate. Que-sto metodo di callback viene invocato automaticamente in corrispondenza della cre-azione dell’Activity, e di solito contiene il codice per inizializzare l’interfaccia grafica. Il concetto di callback è ormai abbastanza comune a molti framework di sviluppo; in una applicazione Android uno sviluppatore non deve scrivere una classe con un metodo statico main invocato all’avvio. un metodo main esiste, ma è nelle classi del framework Android, in particolare nella classe ActivityThread. In pratica, non sono le classi scritte

Activity e task in backgrounddi Fabio Collini

1

18

Android | Programmazione avanzata

da uno sviluppatore che richiamano i metodi del framework; tali classi sono inserite all’interno del framework e contengono dei metodi (chiamati, appunto, di callback) richiamati in corrispondenza di alcuni eventi. Il principio che sta dietro questo modo di organizzare le classi è chiamato anche principio Hollywood e può essere enunciato con la frase don’t call me, I’ll call you.Il ciclo di vita di una Activity non è banale da gestire; per questo motivo, il metodo on-Create non è l’unica callback disponibile. Le callback principali sono sei; per analizzarle meglio è possibile raggrupparle a coppie:

• onCreate/onDestroy: richiamati, rispettivamente, dopo la creazione e prima che l’Activity sia distrutta. Il metodo onCreate contiene solitamente la creazione dell’interfaccia grafica; eventuali oggetti creati in questo metodo che necessi-tano di una chiusura esplicita (per esempio, le connessioni a database) sono gestiti nel metodo onDestroy;

• onStart/onStop: richiamati prima che l’Activity diventi visibile e quando questa viene coperta da altre Activity. La callback onStart è il metodo giusto in cui scri-vere il popolamento dell’interfaccia grafica;

• onResume/onPause: la differenza con le callback onStart/onStop è abbastanza sottile; sono richiamati sempre in sequenza tranne nel caso in cui l’Activity venga coperta da un’altra in modo parziale. In questo caso viene richiamato solo onPause e non onStop; quando l’Activity torna in primo piano viene richia-mato solo onResume. In pratica, dopo l’esecuzione di onStart e prima di onStop vi è la certezza che l’Activity sia visibile anche solo parzialmente; dopo onResume e prima di onPause l’Activity è visibile e in primo piano (per questo l’utente può anche interagire con le View contenute). Spesso la distinzione fra queste coppie di callback non è importante e, per questo motivo, non è necessario riscrivere tutti i metodi.

un aspetto del ciclo di vita da tenere presente per non incorrere in errori consiste nel fatto che i metodi onCreate e onDestroy sono invocati una sola volta su un oggetto; tutti gli altri possono essere invocati più di una volta. È infatti abbastanza frequente il caso in cui una Activity venga messa in background e ritorni successivamente in primo piano (per esempio, dopo la pressione del tasto back).

Gestione dei metodi di callback comuni a più ActivityIn alcuni casi può essere utile eseguire del codice in corrispondenza di una callback per ogni Activity presente nell’applicazione; si pensi, per esempio, all’inizializzazione di alcune librerie che permettono di tracciare l’utilizzo da parte degli utenti. Il modo

19

Activity e task in background Capitolo 1

più semplice è ovviamente quello di aggiungere l’invocazione di un metodo dentro ogni callback; può essere utilizzato nel caso in cui il numero di Activity da gestire è basso. Se l’applicazione contiene molte Activity, il copia incolla non è mai la soluzione migliore e porta a codice difficilmente gestibile.

Classe base per il codice a comuneuna soluzione per evitare il copia incolla è quella che viene comunemente usata nello sviluppo di codice object oriented: è possibile creare una classe base estesa da tutte le Activity in cui fattorizzare il codice a comune. Anche questa soluzione ha comunque delle controindicazioni dovute principalmente all’assenza dell’ereditarietà multipla in Java. una Activity può estendere una sola classe e quindi la gestione diventa proble-matica nel caso in cui ci sia la necessità di estendere classi diverse (per esempio, Acti-vity e ListActivity). In questi casi, avendo più classi da estendere, sarebbe comunque necessario duplicare il codice a comune.

Utilizzo di ActivityLifecycleCallbacksCon la release di Android 4.0 è stata aggiunta l’interfaccia ActivityLifecycleCallbacks all’interno della classe Application. Per sfruttarla è necessario creare una classe che estende Application e registrarla all’interno del manifest. All’interno della callback on-Create della sottoclasse di Application è possibile registrare un oggetto che implemen-ta ActivityLifecycleCallbacks; i metodi contenuti verranno invocati automaticamente in corrispondenza delle callback di ogni Activity. un esempio di utilizzo è il seguente:

@Override public void onCreate() { super.onCreate(); registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated( Activity activity, Bundle savedInstanceState) { Log.d(TAG, "Activity created: " + activity.getClass().getSimpleName()); }

@Override public void onActivityStarted(Activity activity) { Log.d(TAG, "Activity started: " + activity.getClass().getSimpleName()); }

@Override public void onActivityResumed(Activity activity) { Log.d(TAG, "Activity resumed: " + activity.getClass().getSimpleName()); }

@Override public void onActivityPaused(Activity activity) { Log.d(TAG, "Activity paused: " + activity.getClass().getSimpleName()); }

@Override public void onActivityStopped(Activity activity) {

20

Android | Programmazione avanzata

Log.d(TAG, "Activity stopped: " + activity.getClass().getSimpleName()); }

@Override public void onActivitySaveInstanceState( Activity activity, Bundle outState) { Log.d(TAG, "State saved: " + activity.getClass().getSimpleName()); }

@Override public void onActivityDestroyed(Activity activity) { Log.d(TAG, "Activity destroyed: " + activity.getClass().getSimpleName()); } });}

In questo modo la logica può essere scritta in un posto solo, potendo comunque ge-stire comportamenti diversi in base all’Activity in oggetto sfruttando il parametro pas-sato in ogni metodo.

Aspect Oriented ProgrammingL’aspect oriented è un paradigma di programmazione che può essere utilizzato per risolvere problemi complicati sfruttando solo l’object oriented. La teoria che c’è dietro l’aspect oriented è abbastanza complessa e i concetti che la compongono non sono pochi. In questo paragrafo saranno illustrati solo quelli indispensabili per capire come utilizzare l’aspect oriented. Il concetto principale è quello di aspect: rappresenta un aspetto comune a più classi non necessariamente collegate fra loro da una gerarchia. Sfruttando un aspect, è possibile modificare il comportamento delle classi sostituen-do (in parte o completamente) alcune invocazioni di metodi presenti nel bytecode delle classi. Gli esempi classici di aspect sono il logging e la gestione delle transazioni su un database in una applicazione Java enterprise.un aspect può essere definito in modo concreto sfruttando altri due concetti:

• pointcut: rappresentato solitamente da una stringa simile a una regular expres-sion, specifica quando un aspect deve essere applicato. È possibile agire su vari fattori per specificare quali metodi selezionare: oltre al nome del package, della classe o del metodo, è possibile selezionare anche tutte le sottoclassi di una certa classe o tutti i metodi con una specifica annotation;

• advice: è il codice da eseguire ogni volta che viene eseguito un metodo compa-tibile con il pointcut corrispondente.

un aspect può essere di tre tipi (è possibile scegliere quale usare in base alle esigenze):• before: l’advice viene eseguito prima del metodo originale;• after: l’advice viene eseguito dopo il metodo originale; è possibile scegliere se

eseguirlo sempre oppure solo nel caso di terminazione corretta o con eccezioni;

21

Activity e task in background Capitolo 1

• around: l’advice viene eseguito al posto del metodo originale. All’interno dell’advice è possibile decidere in quali casi eseguire il metodo originale.

Per esempio, per loggare tutte le esecuzioni di uno o più metodi è possibile definire un aspect di tipo before; per gestire le transazioni su un database è invece necessario usare un aspect di tipo around. un altro esempio facilmente implementabile usando l’aspect oriented è la gestione di una cache delle chiamate a un metodo: usando un aspect di tipo around, è possibile accedere ai parametri del metodo originale e al valo-re di ritorno del metodo. Mantenendo in memoria una mappa che associa i parametri al valore di ritorno, è possibile decidere se eseguire il metodo originale o ritornare un valore precedentemente aggiunto nella mappa.La cosa da sottolineare in questi casi è che l’advice è solitamente indipendente dal metodo finale a cui è applicato; per esempio, nel caso di logging, è possibile usare un solo advice per loggare tutti i metodi definiti dal pointcut. Questo è il motivo principale per cui viene utilizzato: permette in modo semplice di scrivere codice a comune fra classi non legate fra loro.L’aspect oriented si basa su una modifica del codice per aggiungere la chiamata all’advice; questa modifica può avvenire in tre momenti diversi:

• build time: viene aggiunto uno step nel processo di build per modificare le classi compilate;

• load time: le classi sono modificate nel momento in cui sono caricate in memo-ria dal class loader;

• runtime: al posto delle classi originali sono utilizzati dei proxy che si occupano di gestire le chiamate agli advice.

Esistono varie librerie che permettono di usare l’aspect oriented in Java, ognuna delle quali supporta una o più delle tre modalità appena viste. Per quanto riguarda lo svilup-po su piattaforma Android, la libreria più usata è AspectJ, che funziona aggiungendo uno step nel processo di build. dopo che il compilatore Java ha creato i file .class, viene lanciato il compilatore AspectJ che si occupa di modificare questi file. utilizzando il progetto gradle-android-aspectj-plugin (https://github.com/uPhyca/gradle-android-aspectj-plugin), la configurazione del processo di build è banale, basta aggiungere al-cune righe di configurazione nel file di build di Gradle:

buildscript { repositories { mavenCentral() } dependencies { classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.+' }

22

Android | Programmazione avanzata

}apply plugin: 'com.android.application'apply plugin: 'android-aspectj'

A questo punto, per definire un aspect è possibile creare una nuova classe contenente le annotation di AspectJ:

@Aspectpublic class LogAspect { @Before("execution(* android.app.Activity+.*(..))") public void logMethod(JoinPoint joinPoint) { Log.d("callbacks", joinPoint.getSignature().getName()); }}

Per definire la classe come un aspect è stata utilizzata l’annotation Aspect, il pointcut è definito utilizzando l’annotation Before. In queste poche righe di codice è stato definito un blocco di codice (contenuto all’interno del metodo logMethod) che sarà richiamato prima dell’esecuzione di un qualunque metodo di una sottoclasse di Activity (la regu-lar expression passata all’annotation Before indica questo). utilizzando il parametro passato al metodo di tipo JoinPoint, è possibile accedere alle informazioni sul metodo originale; da notare che il codice non dipende dalla signature del metodo originale. In questo modo è possibile utilizzare lo stesso advice per gestire metodi di classi diverse con signature differenti.Per provare questa classe è necessario inserirla in un progetto Android opportuna-mente configurato. Mettendo un breakpoint all’interno di un metodo di una Activity e all’interno dell’advice, è possibile verificare che prima viene invocato l’advice e succes-sivamente il metodo originale.Per adesso l’aspect oriented non è molto diffuso all’interno della community di svilup-patori Android; l’utilizzo probabilmente più famoso è all’interno della libreria di logging hugo, sviluppata da Jake Wharton. In questa libreria sono sfruttati due pointcut per identificare i metodi e i costruttori annotati con l’annotation DebugLog. L’advice è tipo around, in quanto ha bisogno di eseguire una parte di codice prima del metodo origi-nale per prendere il timestamp di inizio e dopo il metodo per calcolare la durata.

Flusso delle callback del ciclo di vita di una ActvitySono stati già analizzati i possibili metodi di callback relativi al ciclo di vita di una Activity; in questo paragrafo saranno analizzate le sequenze più comuni in cui queste callback possono essere invocate in base al comportamento dell’utente. Il caso più comune (e anche il più semplice) è quello in cui una Activity viene creata per la prima volta e visualizzata: sono richiamati, nell’ordine, i metodi onCreate, onStart e onResu-me. A questo punto, l’Activity è visibile e l’utente può interagire con essa; le callback

23

Activity e task in background Capitolo 1

richiamate in seguito dipendono dall’interazione dell’utente e da altri eventi esterni all’applicazione gestiti dal sistema operativo (per esempio, una chiamata in arrivo). In particolare, possono esserci tre casi:

1. l’utente preme il tasto back oppure l’Activity termina spontaneamente richia-mando il metodo finish: sono invocati nell’ordine i metodi onPause, onStop e onDe-stroy. A questo punto, l’Activity viene distrutta e il garbage collector di Java può liberare la memoria distruggendo l’oggetto corrispondente;

2. l’Activity viene messa in background in quanto un’altra Activity viene eseguita: questo accade spesso e può verificarsi in diversi casi, per esempio quando l’u-tente preme il tasto Home (anche la home di Android è una Activity), quando viene aperta un’altra Activity della stessa applicazione, oppure quando eventi esterni (come una chiamata in arrivo) causano l’apertura di una nuova Activity. In questi casi viene richiamato il metodo onPause e, solo se l’Activity è coperta totalmente dalla nuova Activity, il metodo onStop;

3. cambio di configurazione del dispositivo: avviene quando c’è un cambio di orientation (portrait o landscape), ma anche in casi meno frequenti, per esem-pio quando viene aperta la tastiera fisica di un dispositivo. In questo caso l’Ac-tivity viene distrutta e viene creata una nuova istanza della stessa Activity. Le callback richiamate sono quelle già viste: onPause, onStop e onDestroy sulla vec-chia Activity e onCreate, onStart e onResume sulla nuova Activity.

Gestire i vari casi può non essere semplice; particolare attenzione va posta nel testare il cambio di configurazione, in quanto può avvenire in qualunque momento. In questi casi l’utente si aspetta di ritrovare l’Activity nello stesso stato in cui era in precedenza; il prossimo paragrafo analizzerà questo aspetto.

Salvataggio dello stato di una ActivityIn ogni momento su un dispositivo Android sono in esecuzione più applicazioni, ognu-na delle quali può avere nel proprio stack più Activity aperte. ogni volta, infatti, che viene aperta una Activity, quella di partenza rimane nello stack in modo da poter es-sere riportata in primo piano nel caso di pressione del tasto back. In caso di necessità di memoria, alcune applicazioni (o alcune Activity di una o più applicazioni) possono essere terminate in automatico dal sistema operativo. Per poter salvare il proprio stato interno è necessario sovrascrivere il metodo onSaveIn-stanceState della classe Activity. Questo metodo ha un parametro di tipo Bundle in cui è possibile salvare tutti gli oggetti da ripristinare in seguito. Il restore dei parametri può avvenire in due metodi che possono essere riscritti in una Activity:

24

Android | Programmazione avanzata

• onCreate: il parametro passato a questa callback è un Bundle in cui sono presenti gli oggetti precedentemente salvati. nel caso si tratti della prima esecuzione, questo parametro ha valore null;

• onRestoreInstanceState: callback da riscrivere per eseguire il restore, viene in-vocata in automatico dopo la callback onStart nel caso in cui ci sia uno stato salvato in precedenza.

I Bundle sono molto utilizzati nelle classi del framework di Android, per esempio anche il passaggio di parametri a una Activity avviene usando un Intent che internamen-te utilizza un Bundle. un Bundle può essere visto come una mappa di coppie nome-valore; il nome è sempre una stringa, mentre il valore è un tipo primitivo Java o un oggetto serializzabile. Il concetto di oggetto serializzabile nello sviluppo Android è un po’ diverso da quello standard dello sviluppo Java, infatti comprende anche gli oggetti che implementano l’interfaccia Parcelable oltre a quelli che implementano l’interfaccia Java standard Serializable.L’interfaccia Parcelable è stata introdotta nel framework Android per questioni di per-formance; infatti non fa uso di reflection per leggere e scrivere i valori dei campi di un oggetto, ma delega il tutto a una implementazione che deve essere scritta dallo svilup-patore. La scrittura dei campi avviene nel metodo writeToParcel, mentre la creazione di un oggetto, leggendo i dati salvati in precedenza, avviene nei metodi di un campo statico della classe di tipo Parcelable.Creator. un esempio di implementazione di una classe con due campi è il seguente:

public class Person implements Parcelable { private String name; private String surname;

@Override public int describeContents() { return 0; }

@Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeString(surname); }

protected void readFromParcel(Parcel in) { name = in.readString(); surname = in.readString(); }

public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() { public Person createFromParcel(Parcel in) { Person person = new Person();

25

Activity e task in background Capitolo 1

person.readFromParcel(in); return person; }

public Person[] newArray(int size) { return new Person[size]; } };

//...}

I dati sono salvati in un oggetto di tipo Parcel, i valori sono identificati dalla posizione (non viene usata una chiave per identificarli). In questo modo, il salvataggio è molto veloce ma è necessario porre una attenzione particolare nella scrittura di questi me-todi, soprattutto in caso di classi con molti campi. Se, infatti, non viene rispettato lo stesso ordine nella scrittura e nella lettura, ci sarà un errore runtime o un oggetto con dati non validi (per esempio, il nome al posto del cognome).L’implementazione vista è abbastanza semplice; il metodo readFromParcel può essere utile nel caso di una gerarchia di classi. Per esempio, l’implementazione di una sotto-classe di quella appena vista è la seguente:

public class Worker extends Person { private Company company;

@Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(company, 0); }

@Override protected void readFromParcel(Parcel in) { super.readFromParcel(in); company = in.readParcelable(getClass().getClassLoader()); }

public static final Parcelable.Creator<Worker> CREATOR = new Parcelable.Creator<Worker>() { public Worker createFromParcel(Parcel in) { Worker worker = new Worker(); worker.readFromParcel(in); return worker; }

public Worker[] newArray(int size) { return new Worker[size]; } };

//...}

26

Android | Programmazione avanzata

È possibile notare che sia nella scrittura sia nella lettura dei dati viene richiamato il metodo della classe base; nonostante questo accorgimento, è stato comunque ne-cessario riscrivere anche il campo statico CREATOR per poter creare oggetti della classe derivata. La classe Company implementa a sua volta l’interfaccia Parcelable:

public class CompanyParcelable implements Parcelable {

private String name;

private CompanyParcelable() { }

public CompanyParcelable(String name) { this.name = name; }

@Override public int describeContents() { return 0; }

@Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); }

public static final Parcelable.Creator<CompanyParcelable> CREATOR = new Parcelable.Creator<CompanyParcelable>() { public CompanyParcelable createFromParcel(Parcel in) { CompanyParcelable company = new CompanyParcelable(); company.name = in.readString(); return company; }

public CompanyParcelable[] newArray(int size) { return new CompanyParcelable[size]; } };

//...}

Anche da questo semplice esempio si capisce che, nel caso di un numero elevato di oggetti complessi da gestire, la scrittura manuale dei metodi può essere lunga e por-tare a errori difficili da identificare.

Generazione automatica delle implementazioni di ParcelableEsistono vari framework che permettono di evitare la scrittura manuale di tutto il co-dice, continuando comunque a beneficiare dei vantaggi dell’utilizzo degli oggetti Par-celable. Fra quelli disponibili, due framework molto utili che sfruttano l’annotation pro-

27

Activity e task in background Capitolo 1

cessing sono AutoParcel e Parceler. Lo stesso esempio visto in precedenza può essere riscritto facilmente sfruttando Parceler e aggiungendo alcune annotation:

@Parcelpublic class Person { String name; String surname;

protected Person() { }

public Person(String name, String surname) { this.name = name; this.surname = surname; }

//...}

@Parcelpublic class Worker extends Person { Company company;

Worker() { super(); }

public Worker(String name, String surname, Company company) { super(name, surname); this.company = company; }

//...}

@Parcelpublic class Company {

String name;

Company() { }

public Company(String name) { this.name = name; }

//...}

Ci sono due aspetti da sottolineare in questa implementazione:1. i campi delle classi non sono più privati, ma visibili a livello di package. In questo

modo non è necessario usare la reflection per leggere e scrivere i dati; le classi

28

Android | Programmazione avanzata

generate da Parceler sono nello stesso package della classe originale e quindi possono accedere direttamente ai campi;

2. le classi non estendono più l’interfaccia Parcelable e per questo non possono essere incluse direttamente in un Bundle. È necessario utilizzare i metodi statici Parcels.wrap e Parcels.unwrap prima di poter salvare e leggere gli og-getti dai Bundle.

Questi due aspetti complicano leggermente l’implementazione, ma possono essere considerati come il prezzo da pagare per evitare di scrivere manualmente il codice di implementazione dell’interfaccia Parcelable.

Tipi di dati da salvare in una Activitydopo aver visto come salvare i dati in un Bundle, l’argomento di questo paragrafo è rappresentato dalla tipologia di informazioni che è necessario salvare. In generale, i dati da salvare possono essere divisi in due macrocategorie: il modello dei dati della Activity e lo stato della View.Con “modello della Activity” si intende quello rappresentato generalmente dal mo-del in una implementazione del pattern Model View Presenter. Purtroppo su Android questo pattern non è utilizzato nativamente e implementarlo in modo rigoroso non è banale (più avanti ci saranno alcuni paragrafi dedicati a questo argomento). Quello che invece è abbastanza semplice da fare è mantenere il modello dei dati in un singolo oggetto Parcelable e salvarlo nello stato dell’Activity.L’altra categoria dei dati da salvare è lo stato della View; si pensi, per esempio, al testo scritto in un campo di testo, alla posizione del cursore, al radio button selezionato e a eventuali componenti disabilitati in base ad altre View selezionate. Alcune di queste informazioni sono salvate in automatico dalle View, per esempio un EditText salva automaticamente il testo inserito e la posizione del cursore. Altre informazioni, invece, devono essere salvate manualmente, per esempio la visibilità di una View o l’essere abilitata o disabilitata non vengono salvate in automatico.Il salvataggio dello stato delle View avviene in automatico; questa affermazione è vera, ma è necessario porre attenzione a due aspetti:

• lo stato viene salvato solo per le View che hanno associato un id; solitamente questo non rappresenta un problema, in quanto le View hanno bisogno di un id anche per essere referenziate da codice Java;

• nel caso di ListView, la posizione dello scroll viene salvata solo se l’Adapter ge-stisce oggetti con id stabili (per abilitare questo comportamento è necessario sovrascrivere il metodo hasStableIds).

29

Activity e task in background Capitolo 1

Come già detto, nei Bundle è possibile salvare solo alcuni tipi di oggetto; questa scel-ta è cautelativa, in quanto il Bundle potrebbe essere persistito per liberare memoria. Tuttavia, in alcuni casi c’è la sicurezza che l’Activity sarà distrutta e subito ricreata; ciò avviene, per esempio, in seguito a un cambio di orientation del dispositivo. In questi casi è possibile salvare un oggetto in memoria nella vecchia Activity e recuperarlo nella nuova. Il salvataggio avviene semplicemente mantenendo un riferimento all’og-getto; per questo motivo non è necessario che questo oggetto implementi Parcelable o Serializable. Gli oggetti salvati in questo modo possono essere anche i thread in ese-cuzione; così facendo, è possibile non interrompere un thread al cambio di orientation riagganciandolo dalla nuova Activity.Il modo in cui utilizzare questa tecnica è cambiato con il rilascio di Android 3.0; ini-zialmente, infatti, era possibile usare i metodi onRetainNonConfigurationInstance e getLa-stNonConfigurationInstance della classe Activity rispettivamente per salvare e ricaricare un oggetto. Con Android 3.0 questi metodi sono stati deprecati e adesso è possibile usare un Fragment su cui è stato invocato il metodo setRetainInstance passando il va-lore true. In questo modo tutto il Fragment non sarà distrutto e ricreato durante il cambio di configurazione.un altro metodo introdotto in Android 3.0 e collegato a questo argomento è isChan-gingConfigurations presente nella classe Activity. Può essere usato nelle callback onPau-se, onStop e onDestroy per sapere se l’Activity sta per essere distrutta e ricreata a causa di un cambio di configurazione.

UI Thread e concorrenzaL’architettura di Android è basata sulla gestione di una coda di messaggi; a seguito di eventi gestiti a livello hardware (per esempio, un tocco sul display, ma anche una chiamata in arrivo), sono aggiunti messaggi alla coda che saranno poi gestiti dalle classi del framework di Android. In questo paragrafo saranno analizzati la gestione de-gli eventi e il meccanismo attraverso il quale viene effettuato l’aggiornamento dell’in-terfaccia grafica di una applicazione in relazione a questi eventi.La coda di messaggi è gestita utilizzando la classe MessageQueue; guardando i sorgenti di questa classe, si nota che molti metodi sono implementati come codice nativo per avere performance migliori. una MessageQueue gestisce solo la struttura dati che contiene i messaggi; per utilizzarla è necessario impiegare altre due classi:

• Looper: associa a un thread un oggetto MessageQueue; richiamando il metodo loop, il thread attivo viene destinato a elaborare i messaggi della coda;

• Handler: accetta nel costruttore un parametro di tipo Looper e permette di in-serire messaggi nella coda associata a quest’ultimo. nel caso in cui non sia

30

Android | Programmazione avanzata

passato un Looper viene preso in automatico quello associato al thread in esecuzione; nel caso in cui il thread non abbia un Looper associato viene lan-ciata una eccezione.

L’utilizzo di queste tre classi permette di gestire una serie di eventi creati in uno o più thread e di elaborarli in modo sequenziale. Infatti un oggetto Handler può essere utiliz-zato in qualunque thread; indipendentemente dal thread di origine, il messaggio sarà elaborato sempre nel thread gestito dal Looper.

Figura 1.1 - Interazione di più thread su una Message Queue condivisa attraverso l’utilizzo di Handler.

Per aggiungere un messaggio alla coda è necessario usare uno dei metodi messi a disposizione dalla classe Handler; questi metodi possono essere raggruppati in tre tipi:

• usando il metodo dispatchMessage è possibile aggiungere un oggetto di tipo Mes-sage alla coda; solitamente questi messaggi sono elaborati (nel thread associato al Looper) nell’implementazione del metodo handleMessage dell’oggetto Handler;

31

Activity e task in background Capitolo 1

• attraverso i metodi post, postAtTime e postDelayed è possibile aggiungere una im-plementazione dell’interfaccia Runnable alla coda, specificando eventualmente quando eseguirla;

• il metodo postAtFrontOfQueue permette di aggiungere messaggi all’inizio della coda; anche il javadoc di questo metodo ne sconsiglia l’utilizzo, in quanto può causare side effect difficili da gestire.

Andando a vedere i sorgenti del metodo main che viene lanciato quando l’applicazio-ne parte (presente nella classe ActivityThread), si nota che sono presenti le seguenti istruzioni:

{ //… Looper.prepareMainLooper(); //… Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");}

Invocando il metodo prepareMainLooper, viene associato un Looper al main thread e sarà possibile accedere in seguito a questo Looper richiamando il metodo statico get-MainLooper presente nella classe Looper. Il metodo loop è quello che contiene il ciclo infinito in cui vengono elaborati in sequenza i messaggi aggiunti alla coda; l’eccezione in fondo al metodo non sarà mai lanciata, visto che il metodo loop non termina mai.Il main thread è creato e gestito in modo automatico all’avvio dell’applicazione; nel caso in cui ci sia la necessità di un ulteriore thread che elabora una coda di messag-gi separata, è possibile utilizzare la classe HandlerThread. utilizzando questa classe, è possibile creare in poche righe di codice un thread separato in cui eseguire più ope-razioni in sequenza. Per esempio, è possibile usarlo per creare un thread che esegue un Runnable che schedula automaticamente una nuova esecuzione dopo dieci secondi:

HandlerThread handlerThread = new HandlerThread("myHandlerThread");handlerThread.start();final Handler handler = new Handler(handlerThread.getLooper());handler.post(new Runnable() { @Override public void run() { //... handler.postDelayed(this, 10000); }});

I messaggi inseriti nella coda gestita dal main Looper sono di diversi tipi. Ci sono quelli legati al ciclo di vita delle Activity, quelli legati alla gestione a basso livello delle peri-

32

Android | Programmazione avanzata

feriche hardware e quelli legati in modo più stretto all’interfaccia grafica (per esempio, gli eventi touch). da notare il fatto che non esiste una corrispondenza uno a uno fra i messaggi inseriti nella coda e le callback del ciclo di vita di una Activity. Per esempio, in corrispondenza del messaggio con id LAUNCH_ACTIVITY vengono richiamate le call-back di onCreate, onStart e onResume. Il cambio di orientation viene gestito interamente da un messaggio con id RELAUNCH_ACTIVITY; essendo un messaggio unico, vi è la certez-za che sia le callback di distruzione dell’Activity esistente sia quelle di creazione della nuova Activity siano eseguite in sequenza.Il main thread viene spesso chiamato anche ui thread, in quanto tutte le chiama-te per aggiornare l’interfaccia grafica devono essere effettuate da questo thread. In realtà questa definizione non è del tutto corretta, perché nel main thread sono invocate le callback di altri componenti base di Android, in particolare i Service e i Broadcast Receiver. La gestione dei Service e dei thread su cui sono eseguiti non è semplice come può sembrare; questo argomento sarà approfondito nei prossimi paragrafi di questo capitolo.La gestione degli eventi con una coda di messaggi è una caratteristica comune con altri ambienti in cui è necessario gestire una interfaccia grafica. usando questo pat-tern, c’è la sicurezza che non ci siano due thread in parallelo che aggiornano l’in-terfaccia grafica; in questo modo le classi che rappresentano le View possono non essere thread safe. Inoltre, nel caso di aggiornamenti in conflitto fra loro, ci sarà sempre una situazione consistente in quanto non ci saranno race conditions fra i vari aggiornamenti. nel codice è possibile trarre vantaggio da questa situazione, per esempio non è necessario sincronizzare l’accesso alle variabili che sono utilizzate sempre e solo nel main thread. Anche tutti i listener sull’interfaccia grafica (per esempio, i click listener) sono eseguiti nel main thread. da un lato c’è la sicurezza che l’esecuzione del metodo non sarà in contemporanea ad altri listener, dall’altro è necessario porre attenzione per non oc-cupare il main thread per troppo tempo. Infatti, se un listener esegue direttamente un task lungo, non saranno presi in carico altri listener e non sarà neanche eseguito l’aggiornamento dell’interfaccia utente. Per questo motivo, se il main thread è occupa-to per più di 5 secondi (10 nel caso di broadcast receiver), verrà mostrato il dialog di Application Not Responding (chiamato anche AnR), che permette all’utente di ter-minare l’applicazione. Anche se un listener esegue un’operazione molto più breve di 5 secondi, ma comunque non immediata, è bene spostare l’esecuzione in un thread in background. Infatti si ha già la percezione di lentezza di una applicazione se non c’è un feedback dopo 100-200 millisecondi.Il modo più semplice per eseguire una operazione in background è rappresentato dalla creazione manuale di un nuovo thread fatto partire attraverso il metodo start:

33

Activity e task in background Capitolo 1

new Thread() { @Override public void run() { runBackgroundTask(); runOnUiThread(new Runnable() { @Override public void run() { updateUi(); } }); }}.start();

Se durante l’esecuzione in background vi è la necessità di aggiornare l’interfaccia gra-fica, è necessario eseguire questo aggiornamento nel main thread. Per fare questo è possibile invocare il metodo runOnUiThread della classe Activity passando un oggetto Runnable. usando questo metodo, viene aggiunto un messaggio nella coda di messaggi gestita dal main Looper; in questo modo si ha la sicurezza che l’aggiornamento avven-ga nel thread corretto.Questo esempio di task in background creando un nuovo thread è corretto, ma pre-senta comunque diversi problemi:

• a ogni esecuzione viene creato un nuovo thread; gestendo un thread pool si avrebbe un risparmio della memoria utilizzata;

• non è legato al ciclo di vita dell’Activity che lo contiene; per questo motivo l’ag-giornamento dell’interfaccia grafica viene eseguito anche se durante l’esecuzio-ne del thread l’Activity è stata distrutta.

nei prossimi paragrafi saranno analizzate varie tecniche utilizzabili in ambiente An-droid per eseguire correttamente e in modo efficiente task in background.

Tipologie di task in backgrounddopo aver visto il ciclo di vita delle Activity e la gestione dei task in background, in questo paragrafo e nei prossimi sarà analizzata la relazione fra questi due concetti. Per prima cosa, è possibile dividere in due macrocategorie i possibili task eseguibili in una applicazione Android collegati all’aggiornamento dell’interfaccia grafica. Prendendo in prestito la terminologia dei servizi REST, è possibile definire le seguenti categorie di task asincroni:

• GET: caricamento dei dati da una sorgente dati (locale o remota); viene esegui-ta solo una lettura e nessuna operazione di modifica;

• POST: chiamata per modificare i dati da una sorgente.

una delle differenze fra questi tipi di task risiede nel fatto che i task di tipo GET sono idempotenti, possono essere richiamati più volte senza avere side effect.

34

Android | Programmazione avanzata

nel caso di esecuzione normale di un task (ovvero di una esecuzione senza interruzio-ni), i due tipi sono equivalenti, vengono eseguiti in un thread in background e passano i dati al thread principale per aggiornare l’interfaccia grafica. La situazione si complica quando, durante un task in background, l’Activity che ha lanciato il task viene messa in pausa. Abbiamo visto che questo può succedere in tre casi:

• l’utente preme back per uscire dall’Activity: un task di tipo GET può essere can-cellato senza problemi (oppure il risultato può essere ignorato). nel caso di task di tipo PoST, il comportamento dipende da come è pensata l’interfaccia; se è stata mostrata una finestra di dialogo modale per mostrare il caricamento, il task sarà annullato e per uscire dall’Activity sarà necessaria un’ulteriore pressione del tasto back. nel caso in cui non venga mostrata una finestra di dialogo, l’utente si aspetterebbe un annullamento del task, ma la situazione non è così scontata (se, per esempio, è già partita la chiamata a un server e sta per arrivare la risposta, il task può essere annullato in locale ma è già stato eseguito in remoto);

• un’altra Activity viene aperta in foreground: in questo caso un task di tipo PoST deve essere terminato in modo che, alla successiva apertura dell’Activity, l’u-tente possa vedere il risultato. Per i task di tipo GET la soluzione scelta dipende da vari fattori; per esempio, se i dati ritornati dal task cambiano spesso è pos-sibile annullare la chiamata in modo che, quando l’utente rientra sull’Activity, si troverà i dati aggiornati. non è comunque sbagliato far finire la chiamata e aggiornare l’interfaccia grafica con i nuovi dati;

• cambio di configurazione: come già detto, in questi casi l’Activity viene distrut-ta e ricreata; questo comportamento è tuttavia un dettaglio implementativo. L’utente si aspetta di vedere il task portato a termine nonostante il cambio di orientation. Per i task di tipo GET non rappresenta un errore grave il riavvio al cambio di orientation (al massimo l’utente vedrà una attesa più lunga); può portare invece a inconsistenze dei dati nel caso di task di tipo PoST.

AsyncTask e LoaderLa classe AsyncTask, disponibile fin dalle prime versioni di Android, è stata pensata per eseguire operazioni in background che hanno bisogno di aggiornare l’interfaccia gra-fica o di eseguire altre operazioni sul thread principale. L’utilizzo non è banale; ci sono tre parametri generics da definire nel momento della creazione: la classe corrispon-dente ai parametri iniziali, ai risultati intermedi e al risultato finale. C’è solo un metodo astratto da riscrivere obbligatoriamente, che si occupa di eseguire il task in un thread in background; altre callback possono essere riscritte per gestire il risultato finale, la cancellazione del task ed eventuali risultati intermedi. I thread vengono gestiti auto-

35

Activity e task in background Capitolo 1

maticamente da questa classe, non è quindi necessario gestirli manualmente; ogni metodo verrà eseguito nel thread corretto.L’utilizzo della classe AsyncTask è stato al centro di molte critiche e adesso viene scon-sigliato da molti sviluppatori. Il problema principale è rappresentato dal ciclo di vita dell’Activity, che fa partire un AsyncTask; non c’è, infatti, un legame fra i due oggetti, pertanto il ciclo di vita deve essere gestito manualmente. Per questo motivo possono esserci problemi nel caso in cui l’Activity sia distrutta (per esempio, per un cambio di orientation) durante il task in background. una soluzione possibile è quella vista nei paragrafi precedenti: l’utilizzo di un Fragment che non viene distrutto al cambio di configurazione e in cui memorizzare l’AsyncTask.I thread sono gestiti in automatico all’interno della classe AsyncTask; la gestione non è, tuttavia, così scontata, e cambia in base alla versione di Android utilizzata. Infatti fino ad Android 1.5 il thread in background era uno solo e, per questo motivo, due task lanciati in sequenza non erano mai eseguiti in parallelo. A partire da Android 1.6 e fino ad Android 3.0, i thread utilizzati erano più di uno e quindi più task potevano essere eseguiti paral-lelamente. da Android 3.0 è tornata l’esecuzione in un singolo thread in background, probabilmente per evitare problemi di concorrenza. È tuttavia possibile tornare al com-portamento precedente utilizzando il metodo executeOnExecutor al posto di execute. nel caso in cui si utilizzi un AsyncTask, è bene tenere presente questo aspetto: l’esecuzione in un solo thread in background può essere utile in alcuni casi e limitativa in altri.nella release di Android 3.0 è stata introdotta la classe Loader per cercare di risolvere i problemi collegati all’utilizzo degli AsyncTask. un Loader è collegato strettamente al ciclo di vita dell’Activity che lo contiene e gestisce in automatico il cambio di con-figurazione. nonostante questo pregio non da poco, i Loader non sono mai diventati il modo standard per eseguire un task in background: l’utilizzo è abbastanza macchi-noso e il codice da scrivere per gestirli non è poco. L’utilizzo dei Loader è comunque consigliato nel caso in cui ci sia la necessità di gestire un ContentProvider che ritorna una lista di dati mostrati in una ListView. utilizzando un oggetto di tipo CursorLoader, è possibile agganciarsi facilmente a un ContentProvider e avere in modo automatico e trasparente gli aggiornamenti dei dati. In questo modo l’interfaccia grafica sarà ag-giornata automaticamente ogni volta che sarà necessario senza dover scrivere ma-nualmente il codice di sincronizzazione.

IntentService e LocalBroadcastManagerSono disponibili varie soluzioni per eseguire un task in background; in questo para-grafo sarà mostrata una alternativa agli AsyncTask e ai Loader in cui non sono utilizzate librerie di terze parti ma solo classi standard della piattaforma Android.

36

Android | Programmazione avanzata

Il difetto principale degli AsyncTask è quello di non essere legati al ciclo di vita dell’Ac-tivity; per non avere questo problema, è necessario avere un legame meno forte fra il task in background e l’Activity. Per questo motivo dal task in background non deve essere richiamato direttamente un metodo dell’Activity; è necessario aggiungere un oggetto che gestisce la comunicazione. da un lato sarà possibile inviare messaggi usando questo oggetto, dall’altro i messaggi saranno recapitati agli oggetti che ne fan-no esplicita richiesta.Questo scambio di messaggi esiste già in Android ed è rappresentato dai messaggi di tipo broadcast; gli oggetti che stanno in ascolto sui vari messaggi sono implementa-zioni della classe BroadcastReceiver. Molti componenti standard di Android si basano su questa classe; per esempio, per essere informati del cambio del livello della batteria del dispositivo è possibile registrare opportunamente un BroadcastReceiver nel mani-fest della propria applicazione.I BroadcastReceiver sono globali all’interno del dispositivo; qualunque applicazione può inviare messaggi e stare in ascolto per le varie tipologie. nel caso di un task in background relativo a una singola applicazione, può risultare un problema avere i dati pubblici; per evitarlo, è possibile sfruttare la classe LocalBroadcastManager presente nel-la support library. L’utilizzo è abbastanza semplice; all’interno di una Activity (o di un Fragment) è possibile registrare un Receiver per stare in ascolto a determinati mes-saggi scelti in base all’oggetto IntentFilter passato:

LocalBroadcastManager.getInstance(this).registerReceiver(receiver, new IntentFilter(DownloaderService.EVENT_NAME));

La registrazione può essere effettuata all’interno di uno dei metodi di callback del componente: onCreate, onStart o onResume. A seconda del metodo scelto, si avranno no-tifiche solo quando il componente è visibile (completamente visibile usando onResume o anche in parte usando onStart) oppure anche quando il componente è in background (usando onCreate). nel metodo corrispondente (onDestroy, onStop o onPause) è impor-tante aggiungere la chiamata per annullare la registrazione:

LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);

Il Receiver viene solitamente memorizzato in un campo della classe; una possibile implementazione è la seguente:

private final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { updateProgress(intent.getExtras().getInt("progress")); }};

37

Activity e task in background Capitolo 1

Quando è necessario eseguire un task non legato all’interfaccia grafica su Android, è possibile utilizzare la classe Service. un Service è uno dei componenti standard di Android, viene gestito dal sistema operativo e può essere fatto partire usando un Intent con i parametri da passare. usare l’implementazione base di Service non è comunque la soluzione corretta al problema affrontato in questo paragrafo, in quanto il Service viene eseguito nel thread principale (bloccando quindi l’inter-faccia grafica). Per risolvere questo problema, la soluzione più semplice consiste nell’estendere la classe IntentService, all’interno della quale viene gestito un og-getto HandlerThread per fare in modo che il codice sia eseguito automaticamente in un thread in background.Il ciclo di vita di un IntentService è gestito dal sistema operativo; nel caso in cui ci siano più invocazioni in parallelo, viene riusato lo stesso oggetto. Così come per le Activity, anche in questo caso sarà il sistema operativo che si occuperà di distrugge-re l’oggetto in automatico nel caso in cui ci sia necessità di memoria. L’esecuzione di più invocazioni parallele è gestita in sequenza da un IntentService; questo compor-tamento è dovuto all’utilizzo di un HandlerThread per gestire una coda di messaggi e al fatto che tutte le invocazioni siano gestite da un solo oggetto.una possibile implementazione di un IntentService è la seguente:

public class DownloaderService extends IntentService {

public static final String EVENT_NAME = "download"; public static final String PROGRESS = "progress";

public DownloaderService() { super("DownloaderService"); }

@Override protected void onHandleIntent(Intent intent) { for (int i = 0; i < 10; i++) { //... Intent resIntent = new Intent(EVENT_NAME); resIntent.putExtra(PROGRESS, i + 1); LocalBroadcastManager.getInstance(this).sendBroadcast(resIntent); } }}

Il task effettivo è all’interno del metodo onHandleIntent; l’esecuzione di questo metodo avverrà in automatico in un thread in background. All’interno di questo metodo sono solitamente presenti una o più chiamate al metodo sendBroadcast del LocalBroadcast-Manager per inviare messaggi contenenti informazioni da visualizzare nell’interfaccia grafica. Sia Activity sia IntentService sono sottoclassi di Context, per questo è possibile creare un LocalBroadcastManager passando come parametro this in entrambi i casi.

38

Android | Programmazione avanzata

Per invocare l’esecuzione del Service dall’Activity di partenza è possibile utilizzare il metodo startService passando un opportuno Intent:

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

Figura 1.2 - Gestione di un task in background usando un IntentService.

utilizzando questa implementazione, viene gestito correttamente il cambio di con-figurazione: se durante l’esecuzione dell’IntentService c’è un cambio di orientation, il risultato sarà inoltrato correttamente alla nuova Activity. Il tutto funziona perché dal task in background non è presente un legame all’Activity, ma viene inviato solo un messaggio di broadcast. nel caso di un cambio di configurazione saranno eseguiti i seguenti passaggi:

1. l’istanza A1 dell’Activity A si registra per ricevere messaggi di un certo tipo;2. l’istanza A1 fa partire un service in background;3. a causa di un cambio di configurazione, l’istanza A1 viene distrutta; prima della

distruzione si deregistra dal Broadcast Manager;4. a causa del cambio di configurazione, viene creata una nuova istanza A2 dell’Ac-

tivity A; durante la creazione A2 si registra per ricevere i messaggi;5. alla fine del task in background l’IntentService manda un messaggio utilizzan-

do il Broadcast Manager; questo messaggio sarà recapitato correttamente all’Activity A2.

39

Activity e task in background Capitolo 1

Parlando delle classi Looper e MessageQueue, è stato sottolineato il fatto che la distru-zione e la successiva creazione di una Activity nel caso di cambio di orientation avviene nello stesso messaggio sul thread principale. Adesso è possibile sfruttare questa caratteristica: gli step 3 e 4 saranno eseguiti sempre contemporaneamen-te, e quindi un messaggio broadcast proveniente da un IntentService sarà sempre recapitato a una delle due Activity. ovviamente, per non perdere informazioni, nel caso in cui questo messaggio sia gestito dall’Activity prima del cambio di orientation dovrà essere gestito il salvataggio dello stato dell’Activity come detto nei paragrafi dedicati a questo argomento.

Figura 1.3 - Gestione di un task in background usando un IntentService nel caso di un cambio di confi-gurazione durante l’esecuzione del task.

nell’esempio visto, non è gestito nel modo corretto il caso in cui l’utente esca dall’ap-plicazione premendo il tasto home e rientri successivamente quando il task è termina-to. In questo caso, infatti, quando il task finisce viene inviato normalmente un messag-gio, ma, non essendoci nessuna Activity registrata, tale messaggio non viene inoltrato.un IntentService è un componente gestito direttamente da Android; utilizzandolo, c’è un vantaggio legato al ciclo di vita del processo corrispondente a una applicazione. nel caso, infatti, in cui sia necessario liberare memoria, Android si occuperà non solo di distruggere le Activity in background (salvando lo stato in modo da poterle ripristina-

40

Android | Programmazione avanzata

re), ma anche di terminare i processi relativi ad alcune applicazioni in esecuzione. La priorità in base alla quale vengono scelti i processi da terminare è calcolata in base alle Activity in esecuzione, a quelle in background e al numero di Service e BroadcastRe-ceiver attivi. Il processo corrispondente a una applicazione con Activity in esecuzione molto probabilmente non sarà mai terminato; nel caso in cui non ci siano Activity attive, invece, la probabilità è molto più alta. Per questo motivo, nel caso in cui ci sia la necessità di eseguire un task complesso in background è preferibile utilizzare un IntentService al posto di un semplice thread in background. Così facendo, il sistema operativo è informato del fatto che l’applicazione sta eseguendo un task e quindi il corrispondente processo sarà terminato solo in casi estremi di necessità di memoria.Come già detto, la classe IntentService è una sottoclasse di Context; per questo moti-vo è possibile accedere facilmente a tutti i servizi messi a disposizione di Android. A differenza di un AsyncTask (in cui l’oggetto Context a disposizione è una Activity che può essere distrutta), nel caso di un IntentService c’è la sicurezza che il Context sia valido per tutta la durata del task da eseguire. Tuttavia, anche nel caso in cui si usi un AsyncTask (o altri metodi mostrati nei prossimi paragrafi), è possibile sfruttare l’Appli-cationContext al posto dell’Activity per avere accesso a un oggetto Context che rimarrà valido per tutto il tempo necessario. In molti casi, infatti, non ci sono differenze fra usare l’ApplicationContext o un IntentService: in entrambi i casi non è possibile fare riferimento a oggetti relativi all’interfaccia utente, ma è comunque possibile accedere alle SharedPreferences e a eventuali database.La soluzione vista, basata sulle classi IntentService e LocalBroadcastManager, permette di gestire tutte le problematiche in modo corretto. Tuttavia, ci sono vari aspetti da sottolineare che possono essere migliorati utilizzando librerie esterne alla piattaforma Android:

• il codice visto in questo paragrafo è abbastanza verboso; sia per la registrazione sia per inviare messaggi è necessario creare oggetti non legati al task da esegui-re (come, per esempio, Intent e IntentFilter);

• gli oggetti contenuti nei messaggi creati dal service devono essere inseriti all’interno di un Intent; per questo motivo devono essere tipi primitivi oppu-re implementare Parcelable o Serializable. In realtà questi oggetti saranno passati immediatamente al destinatario del messaggio e quindi non saranno mai serializzati;

• la classe IntentService deve essere definita nel manifest dell’applicazione, in caso contrario si ha un errore runtime;

• molte classi di Android devono essere utilizzate per l’implementazione; questo è vantaggioso per alcuni aspetti, ma è anche uno svantaggio per altri. Infatti il codice scritto non può essere utilizzato in altri contesti ed è più complicato da

41

Activity e task in background Capitolo 1

testare (questo argomento sarà affrontato più approfonditamente nel capitolo dedicato alla qualità del codice);

• nel caso in cui ci siano più chiamate di un IntentService, l’esecuzione non avver-rà mai in parallelo, ma sempre in modo sequenziale. Anche questo aspetto può essere sia positivo sia negativo a seconda dei contesti di utilizzo; comunque una soluzione in cui ci fosse la possibilità di scegliere il tipo di esecuzione sarebbe sicuramente più flessibile e quindi preferibile.

EventBus ed Executorutilizzando solo le classi standard Java, il modo più semplice per creare un thread in background è rappresentato dall’utilizzo della classe Thread; questo modo è immedia-to, ma non è certamente il migliore. Infatti, creando un nuovo thread ogni volta si ha uno spreco di risorse; sarebbe opportuno mantenere un pool di thread e riutilizzarli all’occorrenza. Per risolvere questa e altre problematiche, con il rilascio di Java 1.5 è stato introdotto il package java.util.concurrent che contiene varie classi e interfacce per gestire tutti gli aspetti di concorrenza. L’interfaccia che interessa in questo mo-mento è Executor; essa contiene un solo metodo execute che può essere utilizzato per eseguire un oggetto Runnable. In quale thread sarà eseguito dipende dall’implementa-zione di Executor scelta; nella classe Executors sono presenti alcuni metodi statici per creare i diversi tipi di Executor disponibili. I principali metodi sono i seguenti:

• newSingleThreadExecutor: thread pool contenente un solo thread. Tutti i Runnable sono eseguiti in sequenza; nel caso in cui il thread termini per un errore, viene creato in automatico un nuovo thread;

• newFixedThreadPool: thread pool di dimensione fissa (passata come parametro). nel caso in cui tutti i thread siano occupati e si vogliano eseguire ulteriori task, viene gestita in automatico una coda;

• newCachedThreadPool: thread pool in cui i vari thread sono tenuti in una cache e riutilizzati in base alle esigenze. usando questo metodo, non ci sarà né un numero minimo di thread da mantenere nella cache né un numero massimo di thread gestibili; un singolo thread sarà distrutto se non usato per 60 secondi. Creando manualmente un oggetto ThreadPoolExecutor è possibile configurare questi parametri in base alle esigenze.

nello sviluppo di una applicazione Android solitamente un Executor viene salvato in una variabile statica o in un singleton. In questo esempio è possibile prendere spunto dal codice sorgente della classe AsyncTask e creare un pool di thread di dimensione minima e massima proporzionale al numero di processori disponibili sul dispositivo:

42

Android | Programmazione avanzata

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();private static final int CORE_POOL_SIZE = CPU_COUNT + 1;private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

private static Executor EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 10, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

dopodiché è possibile utilizzarlo per eseguire un task in background passando un pa-rametro di tipo Runnable:

executor.execute(new Runnable() { @Override public void run() { Downloader.getInstance().download(); }});

Rispetto all’esempio precedente di utilizzo di un oggetto di tipo IntentService, in questo caso la chiamata per eseguire il task in background avviene direttamente; non è neces-sario passare da un Intent, ma è possibile invocare direttamente la classe interessata.Per avere i risultati nel thread principale invece di un LocalBroadcastManager è possibile utilizzare, in questo caso, un event bus. Il concetto di bus è usato in vari modi nello sviluppo di un software, per esempio in alcuni progetti (solitamente lato server) viene utilizzato un Enterprise Service Bus per far comunicare i vari componenti. Il concetto di bus è abbastanza semplice: gestisce una coda di messaggi in cui alcuni produttori inviano i messaggi sul bus e alcuni consumatori li elaborano. Il vantaggio dell’utilizzo di un bus è dato dal disaccoppiamento fra chi scrive e chi legge, infatti al momento della scrittura non è necessario sapere chi elaborerà il messaggio (potrebbe essere elabora-to anche da più di un consumatore) e, in alcuni casi complessi, quando sarà elaborato.Per lo sviluppo di una applicazione Android è possibile utilizzare due librerie che implementano un event bus: EventBus (https://github.com/greenrobot/EventBus), sviluppata da green robot, e Otto (https://github.com/square/otto), sviluppata da Square. EventBus ha alcune feature aggiuntive rispetto a otto, che è, per scelta pro-gettuale, molto semplice. utilizzando EventBus, infatti, è possibile specificare anche in quale thread sarà elaborato un messaggio, mentre usando otto l’elaborazione di un messaggio avviene sempre in modo sincrono nel thread in cui il messaggio è stato inviato al bus. Tuttavia, utilizzando otto è possibile scrivere una semplice sot-toclasse di Bus che permette di forzare l’elaborazione dei messaggi nel main thread di Android: ogni volta che un messaggio viene postato viene controllato il thread e, nel caso in cui non sia il main thread, viene utilizzato un oggetto di tipo Looper per eseguirlo sul main thread:

43

Activity e task in background Capitolo 1

public class MainThreadBus extends Bus { public static Bus bus = new MainThreadBus();

private final Handler handler = new Handler(Looper.getMainLooper());

public MainThreadBus() { super(ThreadEnforcer.ANY); }

@Override public void post(final Object event) { if (Looper.myLooper() == Looper.getMainLooper()) { super.post(event); } else { handler.post(new Runnable() { @Override public void run() { MainThreadBus.super.post(event); } }); } }}

Questa implementazione è molto utile nell’architettura introdotta in questo paragrafo: tramite un Executor un task viene eseguito in un thread in background che, durante l’esecuzione o al termine di essa, sfrutta il bus per trasferire i risultati sul main thread in modo da poter aggiornare l’interfaccia grafica. da un task in background è possibile inserire un messaggio nel bus invocando il metodo post:

public void download() { for (int i = 0; i < 10; i++) { progress = i; //… MainThreadBus.bus.post(i + 1); }}

È stato analizzato l’utilizzo del bus dal punto di vista del produttore dei messaggi; resta ancora da vedere come è possibile registrare gli oggetti che verranno invocati automaticamente per elaborare i messaggi. Il metodo da utilizzare è register, che può essere invocato in un metodo di callback dell’Activity o del Fragment per registrare un listener. Anche in questo caso valgono le stesse considerazioni fatte per la registrazio-ne su un LocalBroadcastReceiver:

• è possibile scegliere quale callback utilizzare in base alle esigenze;• cancellando la registrazione nella callback corrispondente (utilizzando, in

questo caso, il metodo unregister), si ottiene la gestione corretta del cambio di orientation.

44

Android | Programmazione avanzata

una volta che un oggetto è registrato sul bus, è necessario specificare quali metodi devono essere invocati dal bus e su quali eventi l’oggetto è in ascolto. Il tutto avviene molto semplicemente aggiungendo una annotation su un metodo:

• usando l’annotation Subscribe è possibile dichiarare che un metodo è un listener che sarà invocato dal bus;

• un metodo annotato con Subscribe sarà invocato dal bus per gli eventi che sono compatibili con il parametro definito nella signature del metodo (deve esserci obbligatoriamente un solo parametro).

Per esempio, il seguente metodo sarà invocato automaticamente per aggiornare l’in-terfaccia grafica a seguito dell’invio nel bus di un messaggio di tipo Integer eseguito dall’oggetto Downloader visto all’inizio di questo paragrafo:

@Subscribe public void updateProgress(Integer progressStep) { if (progressStep == 10) { goButton.setEnabled(true); progress.setVisibility(View.GONE); } else { goButton.setEnabled(false); progress.setVisibility(View.VISIBLE); progress.setProgress(progressStep); }}

un metodo annotato con Subscribe può avere un parametro di qualunque tipo; utiliz-zando otto, è presente la restrizione per cui il parametro non può essere corrispon-dente a una interfaccia Java ma deve essere una classe. un aspetto importante da sottolineare è che il matching avviene considerando anche le sottoclassi; per questo motivo, dichiarando un parametro di tipo Object, è possibile registrare un listener che sarà invocato per tutti i messaggi gestiti dal bus. In questo modo aggiungere un log per registrare tutti i messaggi è molto semplice; in altri casi è possibile sfruttare una classe base a comune di più oggetti per non avere una duplicazione del codice di un listener.In alcuni casi può essere utile fornire immediatamente agli oggetti che si registrano al bus un valore iniziale; per questo motivo otto mette a disposizione l’annotation Produ-ce. un metodo annotato in questo modo sarà richiamato automaticamente ogni volta che un oggetto effettua la registrazione, il valore ritornato dal metodo sarà passato al corrispondente metodo annotato con Subscribe. Anche in questo caso il matching viene effettuato in base al tipo dichiarato nella signature del metodo. Gli oggetti che producono valori da inserire nel bus devono essere registrati usando gli stessi metodi visti in precedenza. nell’esempio visto è possibile aggiungere il seguente metodo nella classe Downloader:

45

Activity e task in background Capitolo 1

@Produce public Integer getProgress() { return progress + 1;}

In questo modo viene risolto il problema che si presenta nel caso in cui il task in background termini quando l’Activity è in background. In questo caso, infatti, quando l’Activity effettua la registrazione sul bus nella callback onResume riceve immediata-mente un evento con l’ultimo valore emesso in modo da poter aggiornare corretta-mente l’interfaccia grafica.Sostituendo l’utilizzo di un LocalBroadcastManager con un Executor e un event bus, alcu-ni dei problemi visti nel paragrafo precedente sono risolti, in particolare:

• il codice da scrivere è più semplice;• i parametri dei metodi sono passati gestiti interamente in memoria, non è ne-

cessario che siano implementazioni di Parcelable o Serializable;• non è necessario aggiungere nessuna configurazione nel manifest;• non utilizzando classi del framework Android, il testing del codice è più semplice;• in base all’Executor utilizzato, è possibile decidere se far eseguire più chiamate

in parallelo o serializzare le varie esecuzioni.

ConclusioniGestire i task in background rispettando il ciclo di vita di una Activity non è un pro-blema banale da risolvere. In questo capitolo sono state esposte varie tecniche per gestire correttamente questa problematica.una soluzione alternativa per eseguire i task in background è rappresentata dal framework RxJava. Questo framework si sta diffondendo velocemente fra gli svi-luppatori Android, in quanto, fra le altre cose, permette di gestire task complessi combinandoli fra loro. Questo sarà l’argomento del prossimo capitolo, che si occu-perà di questo framework e di altri aspetti legati alla programmazione funzionale su piattaforma Android.