Università degli Studi dell’Aquila Facoltà di Ingegneria Tesi di Laurea in Ingegneria Informatica e Automatica Sviluppo di una applicazione su piattafotma Android con il supporto di un DBMS orientato ad oggetti Relatore: Laureando: Prof. Serafino Cicerone Marco Campoli Anno Accademico 2010-2011 Indice Introduzione 6 1 Dispositivi mobili ed Android 10 1.1 Perchè programmare per dispositivi mobili . . . . . . . . . . . . . . . 11 1.2 Sistemi per dispositivi mobili ed OHA 1.3 Android: Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.4 1.5 . . . . . . . . . . . . . . . . . 12 1.3.1 Storia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.3.2 Versione della piattaforma . . . . . . . . . . . . . . . . . . . . 18 1.3.3 Java e Dalvik Virtual Machine (DVM) . . . . . . . . . . . . . 20 1.3.4 Architettura di Android . . . . . . . . . . . . . . . . . . . . . 22 1.3.5 ADT. Android Development Tool . . . . . . . . . . . . . . . . 26 1.3.6 Tipologie di applicazioni . . . . . . . . . . . . . . . . . . . . . 27 1.3.7 Componenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 1.3.8 Stato di un processo . . . . . . . . . . . . . . . . . . . . . . . 29 File androidmanifest.xml e risorse . . . . . . . . . . . . . . . . . . . . 31 1.4.1 Le risorse, struttura di un progetto e classe R . . . . . . . . . 33 1.4.2 Tipi semplici di risorse . . . . . . . . . . . . . . . . . . . . . . 34 1.4.3 Risorse associate ai file . . . . . . . . . . . . . . . . . . . . . . 35 1.4.4 La classe R . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Activity, Intent e comunicazione tra Activity . . . . . . . . . . . . . . 36 1.5.1 Activity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 1.5.2 View e Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 2 Indice 1.6 1.7 1.5.3 Intent e comunicazione tra Activity . . . . . . . . . . . . . . . 44 1.5.4 Gestione degli eventi . . . . . . . . . . . . . . . . . . . . . . . 45 Dialog, Toast, Widget e Notification . . . . . . . . . . . . . . . . . . . 47 1.6.1 Dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 1.6.2 Toast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 1.6.3 Notification Service . . . . . . . . . . . . . . . . . . . . . . . . 48 1.6.4 Home-screen Widget . . . . . . . . . . . . . . . . . . . . . . . 49 Gestione dei dati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 2 Database ad oggetti e relazionali 2.1 54 Database Relazionali e RelationalDBMS . . . . . . . . . . . . . . . . 55 2.1.1 SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 2.1.2 SQLlite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 2.2 ODBMS ed RDBMS . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 2.3 DBMS ad oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 2.4 Db4o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 2.4.1 Licenza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 2.4.2 Object Container ed Operazioni basilari . . . . . . . . . . . . 67 2.4.3 Query . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 2.4.4 Transizioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 2.4.5 Proprietà ACID . . . . . . . . . . . . . . . . . . . . . . . . . . 72 2.4.6 Relazioni inverse . . . . . . . . . . . . . . . . . . . . . . . . . 73 2.4.7 Tipologie di relazioni ed Ereditarietà . . . . . . . . . . . . . . 73 2.4.8 Reference cache . . . . . . . . . . . . . . . . . . . . . . . . . . 75 2.4.9 Equivalence ed Equality . . . . . . . . . . . . . . . . . . . . . 75 2.4.10 Concetto di identità e trasparent persistence . . . . . . . . . . 77 2.4.11 Concetto di attivazione . . . . . . . . . . . . . . . . . . . . . . 78 2.4.12 Configurazione . . . . . . . . . . . . . . . . . . . . . . . . . . 80 2.4.13 Client-server mode . . . . . . . . . . . . . . . . . . . . . . . . 83 2.4.14 DRS. Data Replication System . . . . . . . . . . . . . . . . . 86 2.4.15 Db4o ed SQL (SQLite) . . . . . . . . . . . . . . . . . . . . . . 88 2.4.16 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 2.5 Perst . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 3 Indice 2.5.1 Salvataggio degli oggetti . . . . . . . . . . . . . . . . . . . . . 91 2.5.2 Relazioni tra oggetti . . . . . . . . . . . . . . . . . . . . . . . 92 2.5.3 Attivare gli oggetti . . . . . . . . . . . . . . . . . . . . . . . . 93 2.5.4 Cercare Oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . 93 2.5.5 Transizioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 2.5.6 Relational Database Wrappers . . . . . . . . . . . . . . . . . . 94 2.5.7 Repliation System 2.5.8 Perst vs Db4o . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 . . . . . . . . . . . . . . . . . . . . . . . . 95 3 Realizzare un’applicazione Android-Db4o 97 3.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 3.2 Il problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 3.3 Casi d’uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 3.3.1 3.4 3.5 Casi d’uso Capionario . . . . . . . . . . . . . . . . . . . . . . 99 Architettura logica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 3.4.1 MVC ed organizzazione dei package . . . . . . . . . . . . . . . 102 3.4.2 Pattern Singleton . . . . . . . . . . . . . . . . . . . . . . . . . 104 Modello di dominio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 4 Analisi dei casi d’uso e SSD 108 4.1 System Sequence Diagram . . . . . . . . . . . . . . . . . . . . . . . . 108 4.2 Il nostro caso d’uso: Aggiungere un Cliente . . . . . . . . . . . . . . . 109 4.3 4.2.1 Aggiungere un Indirizzo . . . . . . . . . . . . . . . . . . . . . 110 4.2.2 Aggiungere Recapiti . . . . . . . . . . . . . . . . . . . . . . . 114 4.2.3 Aggiungere un Cliente . . . . . . . . . . . . . . . . . . . . . . 119 Modificare ed eliminare un Cliente . . . . . . . . . . . . . . . . . . . 123 4.3.1 Modificare un Cliente . . . . . . . . . . . . . . . . . . . . . . . 123 4.3.2 Eliminare un Cliente . . . . . . . . . . . . . . . . . . . . . . . 125 5 Conclusioni 127 5.1 Il problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 5.2 La soluzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 5.3 considezioni personali . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 5.4 sviluppi futuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 4 Indice Bibliografia 132 A Programazione ad oggetti e Java 134 A.1 Programmazione ad oggetti . . . . . . . . . . . . . . . . . . . . . . . 134 A.2 Java: storia e caratteristiche . . . . . . . . . . . . . . . . . . . . . . . 135 B ORM ed Hibernate 138 5 Introduzione Con la nascita dei moderni dispositivi mobili o smartphone è possibile la creazione di applicazioni che si occupano di qualsiasi attività l’utente voglia eseguire. Con l’inserimento di sistemi di localizzazione geografica GPS, fotocamere, riproduttori mp3 ed altri strumenti, le capacità di questi device sono assimilabili a quelle di un personal computer di circa otto anni fa, dai costi, in alcuni casi molto inferiori. Le precedenti affermazioni fanno capire che il concetto di personal computer si sta spostando verso dispositivi sempre più piccoli, che possono accompagnare i suoi utilizzatori in ogni momento della giornata. Recenti statistiche affermano che il numero di smartphone venduti nel mondo supera nettamente quello dei PC. Caratteristica che accomuna ogni applicazione sviluppata per smartphone o calcolatore elettronico, è sicuramente la gestione di una grande quantità di dati. Ad esempio, potrebbe essere necessario salvare elementi relativi un cliente o un prodotto, per poterli recuperare in un secondo momento. Questa situazione è associata all’informatica fin dai suoi albori. Negli anni sono state realizzate soluzioni sempre più performanti, concluse con la creazione delle basi dati e DBMS (Data Base Management System), cioè sistemi di gestione dei database. Esistono varie tipologie di smartphone ed ognuno ha un proprio sistema integrato. La maggior parte di questi sono tecnologie proprietarie, cioè di dominio privato, facenti capo in generale ad una singola azienda. Tipici esempi sono Symbian prodotto da Nokia o Ios prodotto da Apple. In un mondo come l’informatica in cui si fanno spazio le teconologie open-source, 6 Capitolo 0 mancava certamente una piattaforma dedica a questi dispositivi. Con la l’avvento di Android (prodotto da Google) sono stati rilasciati gli strumenti per la creazione delle applicazioni e il sistema stesso, per cui molti programmatori si sono accostati a questo tipo di progettazione. Le ricerche effettuate sui vari devices fanno notare che la piattaforma Google è quella che nell’immediato futuro e molto probabilmente nei prossimi anni sarà predominante nel mercato. Ad avvalere questa ipotesi è stato recentemente registrato un numero di tre miliardi di applicazioni scaricate. Spesso un’applicazione deve far riferimento ad una grande quantità di dati. Per rendere persistenti questi valori si fa uso delle basi di dati. Nel 90% dei casi (che è appunto la fetta di mercato occupata) parlando di un database ci si riferisce ad un modello relazionale, cioè ad una struttura creata da tabelle, chiavi e relazioni. Per eliminare qualche problema tra il linguaggio utilizzato per la realizzazione di applicazioni in Android ed i sistemi relazionali, è possibile utilizzare una nuova tipologia di DBMS che va sotto il nome di OODBMS (Object Oriented DBMS). Esistono molti sistemi di gestione delle basi dati ad oggetti, peró si preferisce concentrarsi su elementi open-source adatti all’integrazione in Android. Il risultato della ricerca porta a due soli elementi che vanno sotto il nome di Db4o e Perst. La tesi è corredata da un’applicazione di riferimento dedicata alla gestione di un campionario di merci. Nella realtà esistono già molti apps che si occupano di questo problema, però non tutte sono scaricabili gratuitamente o fanno uso di un DBMS ad orientato agli oggetti. Programmi di questo genere molte volte sono specifici per la gestione dei soli prodotti; come si vedrà, BAgent oltre alle merci è in grado di gestire clienti, ordini, appuntamenti, tipologie per i prodotti e parametri opzionali aggiunti dinamicamente. L’applicazione è stata realizzata mediante il Pattern MVC, il quale definisce chi e come deve eseguire deteriminati compiti e il Pattern Singleton, usato per la gestione degli elementi appartenenti allo stesso. Come ampliamente consigliato dalla comunità di sviluppatori Android, BAgent è stato realizzato mediante l’uso dell’IDE Eclipse per il quale è stato fornito l’ADT 7 Capitolo 0 (Android Development Tool), plugin messo a disposizione da Google, ed OME (Object Manager Enterprise), per la visione degli elementi contenuti nel database direttamente all’interno dell’IDE. Per realizzare l’applicazione si è fatto uso di molti grafici. Per questi è stata utilizzata la versione di prova di Poseidon for UML e strumenti di ingegneria inversa. La struttura in capitoli del presente documento è racchiuso nella successiva sezione: Capitolo 1. In questo capitolo vengono forniti i motivi per i quali è consigliabile sviluppare per dispositivi mobili e di conseguenza, vengono introdotti i sistemi di maggior successo in questo ambito. In un secondo momento si introduce Android, il sistema, gli elementi fondamentali e i motivi per cui l’applicazione BAgent è stata realizzata con esso. L’ultimo paragrafo è dedicato ai vari metodi di gestione dei dati per questa piattaforma, con il quale si introduce il capitolo successivo. Capitolo 2. Dedicato allo studio dei DBMS ed in particolare degli ODBMS. Inizialmente vengono descritti gli RDBMS, SQLite e sistemi di mapping (ORM ). La seconda parte è invece concentrata sullo studio dei database orientati ad oggetti, in particolare su Db4o, Perst e sui confronti tra i vari sistemi di gestione dei dati. Questo capitolo descrive anche i motivi per cui si è utilizzato Db4o per la realizzazione dell’applicazione. Capitolo 3. Capitolo dedicato alla descrizione dell’applicazione di riferimento. In esso sono racchiuse i concetti fondamentali dell’applicazione e la sua struttura. BAgent è stato descritto sommariamente attraverso un modello realizzato con strumenti di ingegneria inversa di Poseidon for UML. Nella seconda parte vengono descritti i casi d’uso, cioè tutte le funzioni che l’app offre al suo utilizzatore, in questo caso chiamato “Attore”. I casi d’uso sono divisi in quattro macro categorie nelle quali l’attore che interagisce con l’applicazione è sempre lo stesso, cioè è l’utente che la utilizza. Alla fine del capitolo è stata dichiarata la struttura in package del progetto e le implementazioni dei Patterns Singleton ed MVC. Capitolo 4. In questo capitolo si realizzano gli SSD o diagrammi di sequenza, 8 Capitolo 0 i quali descrivono le interazioni tra gli elementi caratteristici di BAgent. Avendo sviluppato l’applicazione secondo il pattern MVC, questi grafici evidenziano le relazioni che avvengono tra i vari elementi che lo compongono. A partire da questi elementi viene descritto anche il codice relativo al caso d’uso Aggiungi Cliente, nel quale sono riportati i metodi per l’accesso e il recupero dati relativi ad indirizzi, numeri di telefono, fax ed email direttamente dalla rubrica interna al dispositivo. Nella parte finale si descrive il codice per recuperare un oggetto Cliente all’interno del database ed effettuare operazioni di modifica o eliminazione su di esso. Capitolo 5. Capitolo dedicato alle conclusioni tratte dallo studio di Android e degli ODBMS. In esso sono raccolte riflessioni al problema interposto nella realizzazione della tesi e alle sue soluzioni. L’applicazione di riferimento BAgent potrebbe inoltre essere ulteriormente approfondita realizzandola in modo diverso per tablet, integrando nuove funzioni e rendendola disponibile per un numero maggiore di versioni della piattaforma. 9 Capitolo 1 Dispositivi mobili ed Android In questi ultimi anni si sta svolgendo una rivoluzione legata all’uso dei dispositivi mobili 1 . I moderni dispositivi includono fotocamere, media players, sistemi GPS e touchscreen. Queste caratteristiche aumentano le possibilità di utilizzo da parte dell’utente finale e quindi la varietà di applicazioni che un programmatore può creare. Fin dai primi anni in cui è cresciuto il mercato dei dispositivi mobili, essi erano sempre caratterizzati dall’avere un sistema proprietario. Ciascuna di queste piattaforme aveva proprie caratteristiche, storia e linguaggio. Con l’avvento delle tecnologie esposte in precedenza si è introdotta una barriera per i programmatori che si distaccano da questo tipo di filosofia, poichè prediligono le tecnologie open-source 2 . 1 Con la notazione “dispositivi mobili” (o smartphone) si intende un qualsiasi strumento elettronico capace di ricevere chiamate, caratterizzato dall’essere mobile. Come si vedrà questa è solo una delle tante caratteristiche disponibili. 2 In un epoca segnata dall’uso di teconologie open source è sempre mancata una piattaforma dedica ai dispositivi mobili. Come si vedrà, Android è caratterizzato dal fatto di essere open source, quindi consultabile e migliorabile da chiunque voglia farlo. La licenza scelta è la Open Source Apache Licence 2.0. 10 Capitolo 1 1.1 Perchè programmare per dispositivi mobili Le informazioni che prima erano accessibili solo attraverso un personal computer, ora possono essere recuperate attraverso dispositivi 3 che hanno la caratteristica di essere mobili e di dimensioni sempre più ridotte. In questo modo si può estendere il concetto di personal computer per dispositivi sempre più piccoli e che ci possono accompagnare in qualsiasi momento. Molto importanti sono le seguenti note: • Il mercato dei dispositivi mobili ha superato quello dei personal computer 4 [1]. • Il 2009 è stato l’anno in cui il numero di accessi ad internet fatto con un dispositivo mobile ha superato quello dei personal computer 5 . • Ci sono molti fattori cui tener conto quando si scrivono applicazioni software per smartphone. • I dispositivi sono limitati da un piccolo schermo, una ridotta memoria e processori a bassa potenza 6 . 3 Rispetto un destkop o un notebook computer, i dispositivi mobili hanno: processori lenti, RAM limitata, memoria limitata, batteria limitata, piccoli schermi a bassa risoluzione ed altri problemi. Ogni sviluppatore dovrebbe tener conto di essi, anche se per ogni nuova generazione di telefoni diminuisce il gap prestazionale. Si stima che la potenza di calcolo di un qualsiasi smartphone sia paragonabile a quella di un personal computer di circa otto anni fa. Queste righe risalgono ad un articolo scritto nel 2010 e al giorno d’oggi si può affermare che questo gap è stato ridotto ulteriormente con l’avvento ad esempio di dispositivi dual-core. 4 Il boom del mercato degli smartphone è avvenuto tra il 2004 e 2005 dove furono raddoppiate le vendite. L’ultimo dato fornito afferma che sono 21 milioni gli smartphone venduti nel mondo nell’ultimo trimeste del 2011 (a differenza dei normali telefoni che hanno raggionto i 20 milioni di prodotti venduti). 5 Con 24 miliardi di device dotati di connessione wireless e 12 miliardi di device connessi alla rete mobile attesi per il 2020 (per un mercato stimato di 1,2 triliardi di dollari) stiamo decisamente entrando in una nuova era. 6 Questa caratteristica è tipica dei dispositivi rilasciati negli scorsi anni. Anche se è buona norma assumere che essi siano a risorse limitate, va anche assunto il fatto che come avviene per i personal computer, anche per gli smartphone o tablet le prestazioni aumentano con il rilascio di nuovi dispositivi. 11 Capitolo 1 • Si deve sempre ottimizzare il codice per renderlo veloce e responsabile. • Le applicazioni sono pensate per essere eseguite in piccoli schermi. Si dovrebbero creare applicazioni in modo da essere intuitive all’utente finale e che siano ben visibili su schermi a diversa grandezza. • Quando si sviluppano applicazioni che fanno uso della rete internet è buona norma assumere che la connessione potrebbe essere lenta e molto costosa. Alla base di quanto detto, la nuova frontiera per i programmatori è data dallo sviluppo di applicazioni per telefoni di nuova generazione. Inoltre, è buona norma tener conto di quali costi ogni azione coinvolge; in qualche caso come ad esempio l’uso di GPS o il trasferimento dati sono richiesti costi aggiuntivi a seconda dell’operatore e della tariffa utilizzata; l’utente ha sempre la possibilità di disabilitare queste funzioni e lo sviluppatore è tenuto a rispettare queste scelte. 1.2 Sistemi per dispositivi mobili ed OHA Il mercato degli smartphone può essere declinato attraverso tre grandi società che vanno sotto il nome di Google, Nokia ed Apple. Google è responsabile del progetto Android [5] [6] [7], mentre le altre due sono responsabili di Symbian 7 ed iOS 8 . I produttori dei tre sistemi sono in continua disputa legale e letteraria 9 . 7 Prodotto della Symbian Foundation. La sua nascita risale al giugno del 1998 con la creazione di Symbian Limited nata dalla cooperazione di diverse compagnie telefoniche. L’ultima versione disponibile del sistema operativo è la 5.0. Il 4 febbraio 2010 è diventato un sistema operativo libero, mentre Il 5 Aprile 2011 Nokia annuncia un cambiamento nei criteri necessari per contribuire al progetto trasformandolo in un sistema dove solo le aziende possono collaborare al suo sviluppo. 8 Sistema operativo sviluppato da Apple per iPhone, iPod ed iPad. Il sistema operativo è stato presentato il nove gennaio 2007 al Macworld Conference Expo e la versione 1.0, ancora priva di nome, è entrata in commercio con il primo iPhone il 29 giungno dello stesso anno. L’ultima versione è stata rilasciata nel 2011 ed è la quinta. 9 L’ultima disputa ripresa dai media risale a qualche mese fa. In una conferenza esponenti importanti della Nokia hanno affermato che Google ha creato Android grazie ad Apple (in qualche modo avrebbe copiato il sistema iOS). 12 Capitolo 1 Come visibile nel seguente grafico a torta (fornito da Millennial Media nell’agosto 2011), si può affermare che Google ed Apple sono i maggiori produttori di dispositivi mobili. Figura 1.1: Grafico delle vendite dei vari produttori di dispositivi mobili. Come si vede bene dall’immagine, Android è il sistema utilizzato sul 54% dei moderni telefoni, a fronte del 28% dei dispositivi iOS. Molto distante, con solo il 13%, si colloca RIM (casa produttrice dei BlackBerry) [2] [3]. Sicuramente Google ed Apple si contengono una grossa fetta di mercato. Questo può essere dovuto dalla natura open-source di android e dalle innumerevoli caratteristiche ed accessori rilasciati dalla Apple per i suoi prodotti 10 . Per quanto affermato bisogna specificare che andorid non è la risposta Google all’IPhone. L’IPhone è un sistema hardware e software proprietario facente capo ad una singola società (Apple). Android è uno stack open-source prodotto e supportato dalla OHA. 10 Android non è da meno ad iOS. Sono recenti le notizie della brevettazione da parte di Apple di un sistema che consente di effettuare qualsiasi operazione durante una chiamata. È riscontrabile che nei dispositivi Android questo avveniva già da molto tempo, ma in futuro dovrà essere eliminata a causa del brevetto. 13 Capitolo 1 La Open Handset Alliance (OHA) è un insieme di circa cinquanta società, dove oltre a Google troviamo sviluppatori software, costruttori di componenti (Intel e Texas Instruments) e MOBILE CARRIERS 11 . Di questa società fanno parte anche le più note case costruttrici di dispositivi tra le quali si citano Motorola, T-Mobile, Samsung, Sony-Ericsson e Toshiba. 1.3 Android: Introduzione Android è un vero e proprio stack di strumenti e librerie caratterizzato da un sistema costruito sul kernel open-source Linux (nella versione 2.6), da librerie ed API scritte in C. Con queste caratteristiche ogni applicazione può avere accesso all’hardware mediante specifiche API. Android dispone di una vasta comunità di sviluppatori, i quali rilasciano nuove applicazioni 12 (apps) che estendono le funzionalità del telefono. Per motivi di si- curezza informatica, le apps possono essere scaricate e utilizzate mediante un servizio di distribuzione fidato, quindi un sito internet che fornisca sia l’applicazione stessa che le relative certificazioni 13 . A differenza di altri sistemi, il linguaggio utilizzato da Android è Java 14 . In questo modo nello sviluppo di applicazioni si ereditano tutti i pregi introdotti dalla Sun, le sue API, ed inoltre ne vengono aggiunte ulteriori atte allo sviluppo di questo tipo di applicazioni. 11 I mobile carriers sono letteralmente gli operatori di telefonia mobili. Di questo gruppo fa parte ad esempio la Vodafone. 12 Attualmente sono disponibili nel market oltre 500000 applicazioni, dove la maggior parte di esse è scaricabile senza costi aggiuntivi. A dimostrazione del successo del market è recenetemente stato raggiunto il numero di 10miliardi di app scaricate. Per festeggiare l’avvenimento Google ha fornito il download di tutte le applicazioni a pagamento alla modica cifra di 0.50 centesimi di euro. 13 Il sito in questione prende il nome di “AndroidMarket”. Per venire incontro alle necessità degli sviluppatori è stata prevista la possibilità di disattivare il controllo dei certificati a partire dalla versione 2.2 della piattaforma. 14 Java è un linguaggio di programmazione orientato agli oggetti descritto dalle specifiche rilasciate da Sun nel 1995. 14 Capitolo 1 La scelta di Java [16] ha però un risvolto in contrasto con quella che è la natura open di Android. I dispositivi che intendono adottare la Virtual Machine associata all’ambiente J2ME (JVM o KVM) devono pagare una royality. Per rispondere a questa esigenza Android non esegue bytecode Java, per cui non ha bisogno di una JVM. Google ha adottato una propria VM che prende il nome di Dalvik 15 , la quale trasforma il codice scritto in Dalvik dex-code (Dalvik Executable). Le applicazioni sono sviluppate all’interno di un framework, ossia di una struttura dati specifica. La struttura del framework è molto chiara se si utilizza l’ambiente di sviluppo (SDK) con IDE Eclipse 16 . Il Software Development Kit include tra le altre cose, gli strumenti di sviluppo, librerie ed un emulatore. L’SDK è installabile su qualsiasi computer X86 e su uno dei sistemi operativi di maggior uso, quali Linux, Windows o Mac. Incluso nel Kit si trovano: API: che garantiscono l’accesso allo stack Android da parte degli sviluppatori. Esse sono le stesse con cui sono state create le applicazioni native. Tools di sviluppo: strumenti rilasciati per compilare e fare il debug delle applicazioni. Emulatore: l’emulatore è un dispositivo completamente interattivo per testare e vedere le apps direttamente dal PC. Esso è eseguito nell’AVD (Android Virtual Device), il quale simula il dispositivo con le sue configurazioni hardware all’interno di un personal computer. Documentazione: informazioni dettagliate riguardanti la versione correntemente installata nel PC. 15 Il nome Dialvik deriva da una località islandese. La scelta di utilizzare una virtual machine diversa da quella di Java deriva dal fatto che si deve programmare per un terminale mobile che risponde necessariamente ad eventi (touch schermo, azioni da tastiera). Altri sostengono invece che questa scelta è dovuta solo alla presenza di royality. 16 Eclipse è L’IDE ufficialmente supportata per lo sviluppo di applicazioni Android. Per esso è stato fornito un plug-in che racchiude elementi per la creazione dei file .xml e Java. Da sottolineare che Eclipse non è indispensabile per la creazione di apps. 15 Capitolo 1 Esempi: collezione di esempi atti dimostrare le capacità di Android. Senza alcun dubbio è nell’interesse dei creatori del sistema fornire strumenti adeguati per la creazione di applicazioni. Infatti, il successo di una piattaforma può essere diretta conseguenza del numero di estensioni disponibili per l’ambiente stesso. Un motto molto ricorrente nei libri e negli articoli dedicati ad Android il seguente: tutte le applicazioni sono uguali. Questo vuol dire che tutte le applicazioni, comprese quelle native, sono scritte con le medesime API ed eseguite allo stesso tempo. Ogni app. scritta in Android ha due caratteristiche predominanti: • È caratterizzata da una parte dinamica scritta in Java ed una parte statica XML che ne definisce il layout. • È eseguita in un proprio processo, in una istanza della Dalvik. 1.3.1 Storia Questo stack fu inizialmente sviluppato da Android Inc. 17 e successivamente ac- quisito da Google. I cofondatori di Android Inc., Andy Rubin, Rich Miner, Nick Sears (vicepresidente di T-Mobile) e Chris White hanno fatto parte della realizzazione della piattaforma in Google. Nel 2007 è stata rilasciata la prima versione del Software Development Kit (SDK) che ha consentito agli sviluppatori di realizzare le prime applicazioni sperimentali. Il primo dispositivo Android, il T-Mobile G1, fu rilasciato nel lontano ottobre 2008 e alla fine del 2009 più di 20 smartphone furono lanciati per il mercato di circa 26 nazioni. Sempre alla fine del 2008 Google ha dato la possibilità agli sviluppatori di alcuni paesi (tra cui non c’era l’Italia) di acquistare un telefono, il Dev Phone 1, per sperimentare l’uso di applicazioni senza vincoli di operatori. Subito dopo l’uscita della 1.0, si è iniziato a lavorare per la versione 1.1 (rilasciata nel dicembre 2008). Una delle limitazioni di essa era sicuramente quella che obbligava i dispositivi ad usare una tastiera fisica. Con il rilascio della 1.5 (maggio 2009) fu introdotta la tastiera virtuale, in modo da eliminare il precedente vincolo 17 Android Inc. è stata fondata a Palo Alto California nell’ottobre 2003. Essa ha fornito le basi del sistema Android. 16 Capitolo 1 18 . Questa versione è stata chiamata con un suo secondo nome “Cupcake” ed ha identificativo API rappresentato dal valore 3 19 . Il 16 settembre 2009 è stata rilasciata la versione 1.6 dell’SDK (chiamata anche Donut) con diverse importanti novità, sia a livello utente, sia a livello di API (livello assegnato a 4). In essa sono state implementate nuove funzioni e tecnologie come il supporto alle reti CDMA 20 , diverse risoluzioni di schermo e un sistema di ricerca globale (interno e su internet). I primi cellulari con Android 1.6 nativo sono stati lanciati sul mercato globale poco dopo. Nell’ottobre 2009, dopo poche settimane dal rilascio della versione 1.6, è venuto il turno della distribuzione 2.0. Essa introduce la possibilità d’inviare dati tramite Bluetooth, nuove API (identificate con il numero 7) e correzione di errori. Poco dopo il rilascio della 2.0, il 12 gennaio 2010 è stato rilasciato l’Android SDK 2.1 (Eclair). Il 20 maggio 2010 al Google I/O conference è stato presentato l’Android SDK 2.2 (chiamato anche Froyo). Sono stati inseriti importanti aggiornamenti in questa versione: nuovo kernel linux 2.6.32, un nuovo compilatore JIT 21 , Tethering Wi-fi per utilizzare il terminale come Hotspot Wireless 18 23 22 , oltre a nuove API (assegnate a Atre importanti innovazioni di questa versione sono senzaltro la possibilità di creare widget e folders. 19 Questo identificatore chiamato “API LEVEL” dichiara al sistema il livello di compatibilità dell’applicazione. In questo caso come nelle successive distribuzioni è stata garantita la retro compatibilità con le vecchie versioni. 20 Il Code Division Multiple Access (accesso multiplo a divisione di codice nota anche con l’acronimo CDMA) è il protocollo di accesso multiplo a canale condiviso più diffuso nelle retiwireless. 21 Un compilatore just-in-time o JIT permette un tipo di compilazione con la quale è possibile aumentare le performance dei sistemi, traducendo il bytecode nel codice macchina nativo in fase di run-time. L’obiettivo finale è di combinare i vantaggi della compilazione del bytecode a quelli della compilazione nativa aumentando le prestazioni. 22 Il tethering consiste nell’uso di un telefono cellulare come modem per offrire accesso alla rete ad altri dispositivi che ne sono sprovvisti. La connessione tra i due dispositivi può avvenire via Bluetooth, Wi-Fi o USB. 23 Con il termine Hotspot ci si riferisse ad un’area dove è possibile accedere su internet in modalità senza fili attraverso l’uso di un Router collegato a un provider; attualmente lo standard più diffuso in questo ambito è il Wi-Fi. 17 Capitolo 1 livello 8). In questa relase è stata anche aggiunta la possibilità di installare le apps sulla SD Card, mentre, il 7 dicembre 2010 è stata rilasciata la versione 2.3 cui è stata assegnata la versione delle API a livello 9. Nel gennaio del 2011 viene presentarto Honeycomb (3.0) dedicato ai soli tablet. La versione definitiva dell’SDK 3.0 è stata invece ufficializzata solo il 23 febbraio 2011, mentre l’undici maggio 2011 è stata rilasciata la distribuzione SDK 3.1. Android 3.0 aggiorna il livello API al valore 13. Nell’ottobre 2011 è stata presentata la versione 4.0 (Ice Cream Sandwich) destinata sia per smartphone che tablet, abbandonando la precedente situazione dove smartphone e tablet utilizzavano sistemi operativi diversi. La relase aggiunge un numero di features per gli utenti e i programmatori, alla quale si associa la versione API con identificativo 14. 1.3.2 Versione della piattaforma Gli sviluppatori Android forniscono mensilmente la stima dei dispositivi che hanno avuto accesso all’Android Market (figura 1.2) e il numero di applicazioni disponibili per una particolare versione (figura 1.3), in periodi uguali di una settimana [7]. Figura 1.2: Diagramma a torta degli accessi al market relativi ad ogni versione della piattaforma (Febbraio 2012). 18 Capitolo 1 Figura 1.3: Diagramma delle applicazioni nel market disponibili per ogni versione della piattaforma (Febbraio 2012). La versione più diffusa risulta essere Froyo con il 27.8% di accessi. In costante ascesa è invece Gingerbread nelle versioni 2.3.3 e successive (58.1%). Eclair è al 7.6%, mentre le versioni 1.x raggiungono in totale il 2% circa. Bisogna sottolineare che Honeycomb fa fatica ad imporsi, segno del successo non proprio esaltante dei tablet Android. Questa è una delle maggiori motivazioni che ha indotto gli sviluppatori, pochi mesi dopo il rilascio della versione 3.0, ad introdurre Ice Cream Sandwich, la quale è utilizzabile sia per smartphone che tablet (anche questa statistica può considerarsi non pervenuta, poichè al momento è disponibile solo per il dispositivo Nexus One. Notizie confortanti arrivano dai produttori di smartphone, i quali dichiarano il rilascio di aggiornamenti alla versione 4.0 a partire dalla prossima primavera). Facendo riferimento alla precedente tabella ed hai dati rilasciati nei mesi scorsi 24 , si può affermare che gli sviluppatori possono contare su Froyo come piattaforma 24 Luglio 2010: le versioni 2.x occupano il 58,8% del diagramma, mentre le 1.x il restante 41,2%. Settembre 2010: La crescita dei dispositivi che si aggiornano all’ultima versione è rassicurante. La versione più diffusa è Eclair (2.1) con oltre il 41% del mercato, mentre al secondo posto troviamo l’ultimissima FroYo (2.2), che in pochi mesi recupera terreno a discapito di altre meno recenti. Si registra che il 29% degli utenti utilizza ancora versioni 1.x. Dicembre 2010: in questo mese finalmente avviene il sorpasso di Froyo (2.2) su Eclair 19 Capitolo 1 e fare uso senza troppi problemi delle API di sistema di livello 8. Come si vede dal diagramma numero 1.2, uno dei problemi caratteristici in Android è la frammentazione delle sue distribuzioni. Anche se esistono versioni predominanti, ci sono moltissimi utenti che fanno ancora riferimento a quelle vecchie. Questa situazione può essere data dal fatto che i produttori di smartphone non hanno rilasciato una nuova versione della piattaforma per tutti i dispositivi (motivo dovuto ai costi che ogni produttore dovrebbe investire per l’aggiornamento del sistema). Questo problema si ripresenta quando un utente vuole scaricare una determinata applicazione, a seconda della versione del proprio smartphone essa potrebbe non essere disponibile. Solo recentemente si è raggiunta una soluzione: gli sviluppatori possono inserire più volte la stessa applicazione nel market, in riferimento alle diverse versioni del sistema. 1.3.3 Java e Dalvik Virtual Machine (DVM) Android sfrutta il linguaggio di programmazione Java nella versione 5 per la creazione delle applicazioni. Sono implementate la quasi totalità delle API, ma sono state escluse le Abstract Window Toolkit (AWT 25 ) e le Swing 26 . La scelta di utilizzare (2.1), con percentuali rispettivamente del 43,4% e del 39,6% (l’80% del totale). Febbraio 2011: Android 2.2 Froyo rappresenta il 57,6% del mercato, mentre Eclair scende al 31.4%. Android 1.6 scende al 6,3% e Android 1.5 al 3,9%. Si nota anche la presenza di Android 2.3 Gingerbread, attualmente presente solo sul Nexus S (circa 1%). Ottobre 2011: Android 2.2 Froyo rimane la versione più diffusa con una percentuale del 45.3%, mentre Gingerbread rappresenta il 38.2%. Al terzo posto troviamo Android 2.1 Eclair con una percentuale ancora alta, 11.7%; seguono infine Android 1.6 Donut con l’1.4%, Android 1.5 Cupcake con l’1.1% e rimane ancora poco diffuso, Honeycomb, le cui versioni risultano sotto il punto percentuale. Gennaio 2012: mese in cui viene riportata per la prima volta la versione 4.0 della piattaforma. 25 La Abstract Window Toolkit (AWT) è la libreria Java contenente le classi e le interfacce fondamentali per la creazione di elementi grafici. Essa è stata inserita nelle API standard di Java per lo sviluppo di applicazioni GUI. 26 Swing è un framework per Java orientato allo sviluppo di interfacce grafiche. Parte delle classi del framework Swing sono implementazioni di widget, come caselle 20 Capitolo 1 Java forse è dovuta al fatto che un’applicazione compilata può essere eseguita senza alcuna modifica in sistemi operativi diversi, a patto che per questi esista una specifica JVM in grado di tradurre le istruzioni Bytecode in codice nativo. Come già visto nell’introduzione, Android ha adottato una propria VM che prende il nome di Dalvik. Si tratta di una VM ottimizzata per l’esecuzione di applicazioni in dispositivi a risorse limitate, la quale esegue codice contenuto all’interno di file di estensione .dex, ottenuto a sua volta in fase di building, a partire da file .class di bytecode Java. Ufficialmente questa scelta è stata dettata dalla necessità di risparmiare memoria per la memorizzazione e l’esecuzione delle applicazioni. Da un’applicazione Java descritta da codice con estensione .jar, si ha circa il 50% in più di memoria della stessa applicazione con estensione .dex. Questa diminuzione avviene in fase di trasformazione dal bytecode Java al bytecode per la DVM, durante il quale i diversi file .dex sono in grado di condividere informazioni che altrimenti verrebbero ripetute più volte. Un altro aspetto molto importante della DVM riguarda il meccanismo di generazione del codice che viene detto register based, a differenza di quello della JVM detto invece stack based. Attraverso questo meccanismo i progettisti della DVM si aspettano, a parità di codice Java, di ridurre del 30% il numero di operazioni da eseguire. Per capire come questo possa avvenire si propone un semplice esempio. Supponiamo di voler valutare la seguente operazione: c= a+b; Se con L indichiamo una operazione di Load e con S indichiamo quella di Store, la precedente operazione si può tradurre nel seguente modo: push b; //LS push a; //LS add; //LLS store c; //LS Se volessimo ora eseguire la stessa operazione con un meccanismo register based otterremo: add a,b,c; //LLS Questa ultima istruzione mostra il caricamento degli operandi a e b in zone diverse di testo, pulsanti, pannelli e tabelle. 21 Capitolo 1 di un registro e la memorizzazione del risultato in c. In questo caso si ottiene un minor tempo di esecuzione delle istruzioni, al prezzo di un maggior sforzo in fase di compilazione o trasformazione. 1.3.4 Architettura di Android Android è un’architettura che comprende tutto lo stack degli strumenti per la creazione di applicazioni per dispositivi mobili, dove i layer inferiori forniscono strumenti a quelli superiori. In questa struttura si nota la presenza di un sistema operativo, un insieme di librerie native, ed una implementazione della VM. Lo stack è composto dagli elementi in figura 1.4, i quali sono trattati in dettaglio nelle prossime righe. 1. Linux Kernel: il layer più basso del dispositivo nella versione 2.6 di linux. La necessità è quella di disporre di un vero e proprio sistema operativo che fornisce strumenti di basso livello, attraverso la definizione di diversi driver. In particolare, si possono notare la presenza di driver per la gestione delle periferiche multimediali, del display, della connessione Wi-Fi e dell’alimentazione. 2. Librerie: layer superiore al kernel che include varie librerie, le quali fanno riferimento a un insieme di progetti Open Source C/C++. Tra esse si citano: • Surface Manager (SM): componente fondamentale che ha la responsabilità di gestire le View, ovvero ciò di cui l’interfaccia grafica è composta. La SM ha il compito di coordinare le diverse finestre che le applicazioni visualizzano sullo schermo. • Open GL ES: libreria utilizzata per la grafica 3D, la quale permette l’accesso alle funzionalità di un eventuale acceleratore grafico hardware. Si tratta di una versione OpenGL specializzata per dispositivi mobili. • Scalable Graphics Library (SGL): libreria C++ utilizzata per la grafica 2D. • Media Framework: la maggior parte delle applicazioni Android sono caratterizzate da un elevato contenuto di componenti multimediali. Vi è 22 Capitolo 1 Figura 1.4: Stack Android. 23 Capitolo 1 la necessità di un componente in grado di gestire i diversi CODEC 27 per i vari formati audio e video. Questo componente è basato sulla libreria open source OpenCore ed è fornita da Packet Video (uno dei membri fondatori dell’OHA). • FreeType: motore di piccole dimensioni ed efficiente, utilizzato per i Free Font Type 28 . Attraverso FreeType le aplicazioni sono in grado di visualizzare immagini di alta qualità. • SQLite: libreria che implementa un DBMS relazionale caratterizzato dall’essere molto compatto. • WebKit: si tratta di un browser engine basato sulle tecnologie HTML 30 27 , CSS 31 29 (e non un semplice browser) , JavaScript 32 e DOM 33 . Un codec è un programma o un dispositivo che si occupa di codificare e/o decodificare digitalmente un segnale analogico (tipicamente audio o video), affinchè possa essere salvato su un supporto di memorizzazione o richiamato per la sua lettura o riproduzione. 28 Insieme di caratteri tipografici caratterizzati e accomunati da un certo stile grafico. 29 Componente software che interpreta delle informazioni in ingresso codificate secondo uno specifico formato e le elabora creandone una rappresentazione grafica. Nel caso di un browser vengono interpretati gli stili associati ai documenti scaticati e li rappresenta sul monitor. 30 In informatica l’HTML (HyperText Markup Language o linguaggio di descrizione per ipertesti) è il linguaggio di markup (che definisce modalità di impaginamento formattazione e visualizzazione dati) solitamente usato per i documenti ipertestuali disponibili nel World Wide Web. In tali documenti, un tratto di testo può essere contrassegnato inserendo dei tag, che ne descrivono la funzione, il colore o altre caratteristiche. 31 Il CSS (Cascading Style Sheets o Fogli di stile) è un linguaggio usato per definire la formattazione di documenti HTML ed XML. I fogli di stile permettono di separare i contenuti dalla formattazione e permettere quindi, una programmazione più chiara e facile da utilizzare. 32 JavaScript è un un linguaggio object based comunemente usato per l’esecuzione di controlli nei siti web lato client. Altra caratteristica fondamentale è che esso è in grado di rispondere ad eventi; al contrario di quanto si possa pensare, Javascript non è un parente di Java. 33 Il DOM (Document Object Model, letteralmente modello a oggetti del documento) è una forma di rappresentazione dei documenti strutturati come modello 24 Capitolo 1 • SSL: libreria usata per la gestione dei Secure Soket Layer atta ad osservare gli aspetti legati alla sicurezza nello scambio di comunicazioni. • Libc: si tratta di un’implementazione della libreria standard C ottimizzata per dispositivi basati su linux embedded come Android. 3. Android run time: include librerie core e la DVM. Questo è il motore delle applicazioni e forma le basi dell’application frameworks. • Core Library: classi relative all’ambiente nella quale l’applicazione viene eseguita; le core library sono rappresentate da pacchetti di formato .dex. 4. Application Framework: tutte le librerie viste fino a questo momento vengono utilizzate da componenti di più alto livello che compongono l’Application Frameworks. Questo livello fornisce le classi usate per creare le applicazioni. Tutte le apps Android utilizzano lo stesso AF (da qui è possibile trovare un’ulteriore conferma del motto ricorrente: All Applications Are Equals). In questo layer possiamo trovare: • Activity Manager: responsabilità di questo componente è l’organizzazione delle varie schermate di un’applicazione a seconda dell’ordine di visualizzazione delle stesse sullo schermo. Ogni schermata è rappresentata da un’Activity. • Package Manager: responsabilità del package manager è quella di gestire il ciclo di vita delle applicazioni. • Window Manager: componente che permette di gestire le finestre delle diverse applicazioni sullo schermo del dispositivo. • Telephony Manager: permette l’interazione con le funzionalità caratteristiche di un telefono, come la possibilità di iniziare una chiamata o di verificare lo stato della stessa. orientato agli oggetti. DOM è lo standard ufficiale del W3C per la rappresentazione di documenti strutturati, in maniera da essere neutrali sia per la lingua che per la piattaforma. Sfortunatamente negli scorsi anni questa struttura non è mai stata presa come riferimento, rendendo molto difficile la vita dei programmatori web. 25 Capitolo 1 • Content Provider (CP): componente fondamentale nella realizzazione delle applicazioni Android, poichè ha la responsabilità di gestire la condivisione di informazioni tra vari processi. Il funzionamento è simile a quello di un repository, in cui diverse applicazioni interagiscono, inserendo o leggendo informazioni. • Resource Manager: ha la responsabilità di gestire un insieme di file di tipo diverso, tra cui immagini, file di configurazione o di ottimizzazione delle risorse. • View System: compito del View System è la gestione degli eventi associati alle applicazioni. • Location Manager: API che prendono il nome di Location Based Application (LBA). Queste permettono di gestire le attività con il sistema di Geo Referenziazione (GPS). • Notification Manager: il Notification Manager mette a disposizione un insieme di strumenti per inviare particolari notifiche al dispositivo. Tra gli avvisi è possibile citare la vibrazione o visualizzazione di un’icona. 1.3.5 ADT. Android Development Tool L’SDK include molti tool ed utilities per aiutare i programmatori a creare, testare ed effettuare debug. L’Android Development Tool contiente molti di questi elementi che fanno uso dell’IDE Eclipse. Tra questi si possono citare: SDK e Virtual Device Manager: usato per creare e gestire l’Android Virtual Device, cioè un’istanza dell’emulatore in grado di simulare specifiche hardware ed API disponibili per diversi dispositivi. Questa soluzione permette agli sviluppatori di testare il software per diversi schermi e risoluzioni. Emulatore Android: utilizzato dagli sviluppatori per testare ed effettuare debug delle applicazioni. Con l’emulatore è possibile simulare SMS, chiamate e tempi di latenza. Dialvik Debug Monitoring Service (DDMS): usato per monitorizzare e controllare la DVM quando di effettua il debug dell’applicazione. 26 Capitolo 1 1.3.6 Tipologie di applicazioni Le applicazioni sviluppabili in ambiente Android possono essere raggruppate nelle seguenti quattro categorie: Foregound: un’applicazione è detta in foreground quando interagisce con l’utente e viene sospesa quando non è visibile. Con elementi di questo tipo bisogna considerare il ciclo di vita delle Activity (introdotto nei prossimi paragrafi). Background: applicazione con limitata interazione con l’utente che si trova per la maggior parte del tempo nascosta, in modo da ascoltare le azioni causate dall’hardware, dal sistema o da altre applicazioni. Esse si trovano in foreground solo nel caso in cui debbano essere configurate 34 dall’utente. Intermittent: alcune applicazioni sono interattive ma fanno la maggior parte del lavoro in background. Queste sono generalmente una unione delle Activity con i Background Services. Estensioni della piattaforma che gestiscono chat ed email sono tipici esempi. App Widget: alcuni elementi sono rappresentati solo da una schermata nella home del dispositivo mobile. Questi vanno appunto sotto il nome di widget. Alcune Applicazioni possono essere molto complesse, quindi molto difficili da far rientrare in una delle precedenti categorie (in alcuni casi è possibile racchiudere le precedenti in un unico elemento). 1.3.7 Componenti Le estensioni Android devono garantire un certo livello di interattività con gli utenti. Questo deve avvenire in modo altamente intuitivo e senza alcuno spreco di risorse. I progettisti hanno pensato di fornire alcuni componenti e un meccanismo di comunicazione tra essi, in modo da permettere un’ottimale sfruttamento delle delle risorse ed estensibilità della piattaforma. 34 È buna norma implementare la funzione di configurazione all’interno delle applicazioni di tipo Background. Questa procedura rende possibile il settaggio di opzioni. 27 Capitolo 1 I componenti costruiscono i blocchi principali delle applicazioni e possono essere racchiusi in: Activity: ogni schermata è un estensione della classe Activity. Le Activity usano le Views per avere un’interfaccia grafica con l’utente, le quali rispondono alle azioni degli stessi. Siccome un’applicazione può essere composta da più schermate, la piattaforma organizza le attività secondo una struttura a stack, dove l’attività in cima è quella attiva in un particolare momento. La visualizzazione di una nuova schermata porterà questa in cima allo stack, ponendo in pausa la precedente. Quando una Activity termina il proprio lavoro fa in modo di ritornare le eventuali informazioni raccolte. Il sistema rende tutte queste operazioni trasparenti all’utente che usa il dispositivo, mentre lo sviluppatore deve gestire gli stati di un’attività attraverso opportuni metodi di callback 35 . Service: i componenti Services operano in background facendo l’upload delle risorse e delle Activity visibili. Essi vengono spesso citati con la notazione: “invisible workers of your application”. Content provider: utilizzati per gestire le repository di informazioni. Android include alcuni Content Provider nativi che si riferiscono ad informazioni ad esempio circa i contatti telefonici, chiamate, eccetera. Intent e intent filter: quando un’applicazione ha la necessità di eseguire una particolare operazione non fa altro che creare creare un Intent, richiedendo l’utilizzo di una qualunque risorsa o componente in grado di poterla esaudire. Un Intent deve essere caratterizzato da informazioni relative all’acquisizione dei 35 Con questo termine si intende una funzione che viene richiamata da un’altra funzione o dal sistema operativo; questi metodi sono in grado di gestire determinati eventi. 28 Capitolo 1 dati e da un meccanismo per identificare il tipo. Questi dati sono rappresentati mediante un oggetto di tipo String e da una URI 36 . A completamento di quanto scritto, serve un meccanismo che permetta ad un applicazione di dichiarare l’insieme degli Intent che gli stessi sono in grado di gestire. Questo viene realizzato attraverso gli Intent filter. Broadcast receivers: La gestione di eventi esterni può essere realizzata attraverso la definizione di Broadcast receiver. L’arrivo di un evento esterno implica l’attivazione di un Intent receiver ma non necessariamente l’esecuzione di una Activity e nemmeno la notifica all’utente. Notification: le notifiche forniscono un input all’utente che non perdere il focus dell’Activity corrente. Questa tecnica fornisce segnali da parte di un Service oppure un Broadcast receiver. 1.3.8 Stato di un processo Come introdotto nei precedenti paragrafi, ogni applicazione fa riferimento ad un proprio processo in un istanza della Dalvik Virtual Machine. Se i processi in esecuzione all’interno del dispositivo esauriscono la memoria, ne vengono eliminati alcuni al fine di favorire l’esecuzione di quelli visibili. Il sistema sceglie quale processo terminare in base a determinate priorità. A tale proposito, le tipologie sono state classificate in: • Active process. • Visible process. • Service process. • Background process. 36 URI: Uniform Resource Identifier. Permette di specificare molti tipi di dato secondo la notazione “scheme://host:port/path”, dove si definisce authority l’insieme dell’host e della porta. Insieme all’URI è molto importante il concetto di mime-type dei dati associati, il quale è caratterizzato da una coppia chiave-valore. 29 Capitolo 1 • Empty process. dove la figura 1.5 mostra l’albero delle priorità. Figura 1.5: Albero delle priorità. Active process: componenti di applicazioni che interagiscono con l’utente. Di norma all’interno del dispositivo ci sono pochi processi di questo tipo. Essi hanno priorità impostata al valore Critico, quindi verranno terminati solo nel caso in cui non fossero disponibili le risorse per la loro esecuzione. Visible process: processi visibili ma inattivi. Un processo si trova in questo stato quando una Activity è parzialmente visibile all’utente ma allo stesso tempo oscurata. La priorità assegnata a questi processi prende il nome di High. Started Service Process: processi che non hanno interfaccia grafica ma con priorità paragonabile a quella delle Activity allo stato di RUNNING. Si provvede alla loro eliminazione solo nel caso di reale necessità, ovvero per non precludere l’esecuzione dei processi elencati in precedenza. Anche questi processi hanno priorità impostata ad High. 30 Capitolo 1 Background Process: in genere esiste un gran numero di processi in background che vengono terminati usando il pattern LSFK. La priorità è impostata a Low. Empty Process: si tratta di processi legati a nessun componente predefinito della piattaforma e quindi tra i primi candidati all’eliminazione. Si parla di empty process perchè Android di solito mantiene in memoria applicazioni anche dopo aver terminano il loro ciclo di vita. 1.4 File androidmanifest.xml e risorse Ogni applicazione Android include il file AndoridManifest.xml salvato nella root del progetto. Questo è utilizzato per definire la struttura dei meta-data dell’applicazione, i suoi componenti e le sue richieste sotto forma di file XML. Il file include i nodi per componenti quali Activity, Services, Content Provider, Broadcast Receiver (trattati nel seguito), un’icona da visualizzare nel menu, una label per il nome dell’applicazione e temi. È inoltre necessario definire permessi che determinano come l’applicazione può interagire con l’utente e come essa possa interagire con altre applicazioni. L’Android Manifest ha come elemento di root il tag manifest. In questo tag vendono definiti gli attributi versioncode, utilizzato per definire la versione corrente dell’applicazione, e l’attributo versionname, che specifica la versione visibile agli utenti. <m a n i f e s t xmlns : a n d r o i d : // schemas . a n d r o i d . com/ apk / r e s / a n d r o i d package= ”com . my domain . my app” a n d r o i d : v e r s i o n C o d e= ”1” a n d r o i d : versionName= ” 0 . 9 Beta”> [ ] </m a n i f e s t > Il tag manifest può contenere molti nodi a seconda delle richieste dell’aplicazione. Il primo di essi è sicuramente uses-sdk che è in grado di definire la versione minima 31 Capitolo 1 37 , massima 38 ed il target SDK che può essere utilizzato. <uses −sdk a n d r o i d : minSdkVersion= ”4” a n d r o i d : t a r g e t S d k V e r s i o n= ”5”> </u se s −sdk> Subito dopo il tag manifest è possibile trovare il nodo application utilizzato per specificare i meta-data delle applicazioni. <a p p l i c a t i o n a n d r o i d : i c o n= ” @drawable / i c o n ” a n d r o i d : theme= ” @ s t y l e /my theme” a n d r o i d : name= ” MyApplication ” a n d r o i d : debuggable= ” t r u e ”> [ ] </ a p p l i c a t i o n > Il precedente elemento può contenere a sua volta i tag Activity, Services, Content Provider e Broadcast Receiver che specificano i componenti dell’applicazione. All’interno del nodo application è possibile trovare uno o più nodi activity, richiesti per il corretto funzionamento di ogni Activity visualizzata sullo schermo. Usando l’attributo android:name si specifica il nome della classe Activity. Ogni nodo di questo tipo può contenere intent-filter 39 , il quale specifica quale Intent può eseguire l’Activity. 37 Il parametro segnala la versione minima delle API in cui l’applicazione non genera errori. Nel caso in cui si omette l’attributo minSDKVersion, l’applicazione fallirà l’esecuzione nel momento in cui si cerca di accedere ad API non disponibili per quel dispositivo. La scelta multipla delle proprietà introdotte non è consentita, poichè, si suppone che un dispositivo compatibile con una determinata versione sia in grado di eseguire applicazioni sviluppate per le versioni precedenti. 38 Inserendo l’attributo maxVersion non sarà possibile installare l’applicazione in dispositivi con livello API maggiore. È buona norma non inserire questo attributo se non si riscontrano problemi con nuove versioni delle API. 39 Pensando ad un intent come la volontà di un particolare componente di eseguire una determinata azione su un determinato insieme di dati, un intet-filter è invece la dichiarazione da parte di un componente di essere in grado di soddisfare un intent. 32 Capitolo 1 Il tag service è utilizzato per segnalare la presenza di una classe Service utilizzata nell’applicazione. Anche questo nodo può contenere tag figli di tipo intent-filter. Come nei casi precedenti, i nodi provider e receiver vengono utilizzati per segnalare content provider e broadcast receiver rispettivamente. Altro nodo di notevole importanza è uses-permission, il quale permette di dichiarare i permessi che l’applicazione dovrà ricevere dall’utente per consentire il corretto funzionamento della stessa. Se un’applicazione richiede specifici permessi da parte dell’utente, essi saranno esposti prima dell’installazione della stessa. Nel caso in cui l’utente non accetta l’uso di questi, l’applicazione non sarà installata. Come si può intuire, una corretta gestione del file manifest potrebbe essere molto difficile. Fortunatamente gli sviluppatori hanno hanno messo a disposizione un esenzione per l’IDE Eclipse in grado di gestire in modo visuale quest’ultimo. 1.4.1 Le risorse, struttura di un progetto e classe R Potrebbe essere molto produttivo estraniare le risorse di un progetto e renderle disponibili attraverso delle costanti. Android supporta questo sistema per molti elementi che vanno da un semplice valore come una stringa o un colore, per finire con immagini (Drawable), animazioni e temi. Nel momento in cui si rende necessario l’utilizzo delle risorse, l’applicazione sa dove si trova il corretto valore da visualizzare, quindi questo meccanismo è del tutto trasparente all’utente e come si vedrà può essere trasparente anche verso un progettista. Le risorse possono essere di due tipologie: quelle che vanno sotto la cartella res e quelle nella cartella di nome assets. Le prime sono compilate in un formato conveniente e sono accessibili attraverso costanti generate dalla classe R. Nella cartella res sono disponibili di default le sotto-cartelle values, drawableldpi, drawable-mdpi, drawable-hdpi, utilizzate per la gestione di elementi drawable per diverse tipologie di schermi. Di questa cartella fanno parte anche le risorse layout e non compilate (raw ) per le quali viene comunque generato in identificativo nella classe R. La seconda cartella va sotto il nome di assets e contiene risorse che mantengono il loro stato di origine, cioè non vengono compilate. Queste tipo di risorse possono 33 Capitolo 1 essere gestite solamente attraverso la classe AssetManager. 1.4.2 Tipi semplici di risorse I tipi semplici includono string, colors, dimensions, integer-array e string-array. Tutti questi tipi sono salvati come file XML ed inclusi della cartella res/values. String: specificate dal tag string ed associate ad una chiave univoca nell’applicazione. Integer e String Array: queste risorse contengono rispettivamente array di interi o stringhe. Elementi di questo tipo possono essere creati mediante l’uso di tag item, dichiarati a loro volta all’interno di string-array o integer-array. Color: usando il tag color si specificano risorse di tipo colore. I colori vengono specificati usando il simbolo # seguito da uno o due numeri esadecimali per i rispettivi valori di rosso, verde e blù. È inoltre possibile l’inserimento di un’ulteriore coppia esadecimale, la quale indica i parametri alpha di trasparenza. Drawable: definite nella cartella res/drawable, alle quali rientrano le immagini e Drawable composti come ad esempio LevelListDrawable e StateListDrawable. Da segnalare che non tutte le specializzazioni della classe Drawable possono essere definite in modo dichiarativo attraverso documenti XML. Stili e temi: uno stile definisce un insieme di attributi che possono essere applicati ad un particolare componente, View o UI. Per gli stili si ha un tag di tipo style in cui definiamo un attributo di tipo name, contenuti da uno o più tag item. Mentre uno stile si applica a un componente, un tema è un insieme di stili che si impostano a livello di applicazione o di singola attività. È possibile ereditare le proprietà di un tema già definito attraverso l’attributo parent, contenente il nome del tema specifico. Come nel precedente caso si può dichiarare un insieme di sotto nodi item, ognuno con lo specifico attributo name. 34 Capitolo 1 Dimension: il tag dimen seguito da un identificatore di scala definisce risorse di tipo dimension. Questi parametri possono essere utili per creare costanti di altezza e larghezza espressi in px, mm, dp, sp. Animations: Android supporta due tipi di animations, le Tweened animations, usate per ruotare e muovere le View, oppure le frame-by-frame animations, le quali visualizzano una sequenza di immagini Drawable. Layouts: risorse utilizzate per disegnare le user interface o elementi customizzati attraverso documenti XML. Le activity vengono messe in relazione con i layout attraverso l’uso del metodo setContentView e sono salvati in un file separato all’interno della cartella /res/layout. 1.4.3 Risorse associate ai file Le risorse associate ai file non si traducono in oggetti particolari, ma possono essere utilizzate in base alle loro specifiche caratteristiche. In questo caso si parla di determinati file binari o documenti XML che possono racchiudersi nei seguenti punti. Assets: si tratta di file che non corrispondono a vere e proprie risorse, in quanto per esse, non vengono generate costanti per la classe R e non viene eseguita alcuna ottimizzazione. L’unico modo per accedere a queste informazioni da parte dell’applicazione è quello di utilizzare la classe AssetsManager. Risorse XML: una risorsa XML può essere interpretata dal sistema in modi diversi. Il più semplice prevede di gestire il file come semplice documento contenuto nella cartella /res/xml. Risorse raw: si tratta di file contenuti all’interno della cartella /res/raw ai quali sono associati delle costanti della classe R, ma non vengono ottimizzate. Esempi di questo tipo possono essere file video, audio o comunque file cui si ha la necessità di accedere secondo una modalità di stream. 35 Capitolo 1 1.4.4 La classe R Le risorse possono essere usate direttamente all’interno del del codice dell’applicazione usando la classe statica R. Questa classe contiene sottoclassi per ogni tipo di risorsa definita nell’applicazione. Ogni sottoclasse associa una risorsa ad una variabile attraverso un identificatore che corrisponde ad una locazione nella tabella delle risorse. Tra le classi interne contenute in R si trova la classe layout che contiene a sua volta una costante intera di nome main. In questo modo, per accedere alla risorsa main si deve usare il costrutto R.layout.main. 1.5 Activity, Intent e comunicazione tra Activity Nei seguenti paragrafi si entrerà più in dettaglio nello studio degli elementi fondamentali in Android e cioè, le Activity e gli Intent. Come si vedrà, le Activity forniscono il core delle applicazioni, mentre gli Intent sono utilizzati per far comunicare le precedenti. 1.5.1 Activity Ogni schermata da visualizzare sul dispositivo estende la classe Activity. Per utilizzarle è necessario segnalarle nel file AndroidManifest, inserendo tag application, contenuti a loro volta nei nodi activity. Tipicamente esiste una schermata principale rappresentata da una Activity di nome main. Questa schermata è di solito supportata da una o più Activity che aggiungono informazioni ed interazioni alla prima. Ciclo di vita di una Activity e metodi di callback Le Activity sono rappresentate mediante una struttura a stack basata sul principio last-in-first-out 40 dove l’oggetto in cima alla pila è quello visibile. Attraverso questo 40 Il termine LIFO è l’acronimo di Last In First Out (Ultimo ad entrare, primo ad uscire) ed esprime il concetto relativo al modo di immagazzinare dati, in cui l’ultimo valore introdotto è il primo ad uscire. 36 Capitolo 1 stack è possibile determinare lo stato di un’Activity. Quando una nuova Activity viene avviata questa vine portata in cima alla pila e quella precedente scala di una posizione. Questo ciclo è illustrato nella seguente tabella. Figura 1.6: Activity che si muove nello stack dell’applicazione. All’interno dello stack ogni attività si può trovare in uno dei seguenti stati: Active: quando una Activity è in cima allo stack ed è in grado di ricevere gli input da parte degli utenti, viene posta nello atato Active. Android cerca di mantenere in vita queste, fermando se necessario quelle a priorità minore. Paused: caso in cui una Activity è visibile ma non ha il focus. Questo stato si ha quando Activity superiori hanno un grado di trasparenza, oppure non occupano tutto lo spazio a disposizione. Stopped: una Activity non visibile si trova in questo stato; essa rimane in memoria mantenendo tutte le informazioni, ma è la prima ad essere eliminata in caso di bisogno. Inactive: dopo che una Activity è stata eliminata e prima che essa sia nuovamente lanciata, si trova in questo stato. 37 Capitolo 1 I metodi di callback Per ognuno dei punti espressi nel paragrafo precedente esiste un metodo definito di callback, il quale permette l’esecuzione di diverse operazioni. Queste relazioni sono rappresentate nella figura 1.7. Il primo metodo nella tabella ha la seguente forma: protected void onCreate(Bundle savedInstanceState) Si tratta dell’operazione invocata in corrispondenza della creazione di una Activity, dove la prima istruzione da eseguire sarà sempre l’invocazione dell’analogo metodo della classe padre (super.onCreate()). Questa caratteristica è tipica di tutti i metodi di callback e per non appesantire la lettura si ometteranno ulteriori note a riguardo. Il parametro di tipo Bundle fornisce un riferimento allo stato che l’Activity aveva prima di essere eliminata dal sistema. Questo parametro può essere salvato all’interno del metodo onSaveInstanceState() ed essere ripreso nel precedente. Se il metodo onCreate termina con successo, la Activity viene preparata per la visualizzazione e il sistema invoca il metodo definito: protected void onStart() di ovvio significato. La successiva funzione dipende dal fatto che la Activity abbia ottenuto o meno il focus, quindi sia quella in cima allo stack. Se ciò è verificato viene invocato: protected void onResume() e se questo termina con successo, la Activity è nello stato RUNNING e può interagire con l’utente. Una Activity da rimuovere dalla posizione iniziale dello stack viene messa nello stato PAUSED e conseguentemente vengono invocati i metodi: protected void onPause() e protected void onStop() dove il secondo è eseguito solo nel caso in cui il primo è terminato con successo. Successivamente si può ripristinare l’attività precedente con: protected void onRestart() La logica del metodo onRestart() è molto simile a quella di onCreate(); rispetto questo metodo, onRestart() deve preoccuparsi di un eventuale ripristino dello stato. Dopo l’esecuzione di onRestart() vengono invocati rispettivamente onStart() ed onResume() secondo le stesse modalità viste in precedenza. 38 Capitolo 1 Figura 1.7: Ciclo di vita delle Activity e relativi metodi di callback. 39 Capitolo 1 Il sistema elimina le Activity portandole dallo stato PAUSED in cui l’aveva lascita a quello di STOPPED invocando il metodo: protected void onStop() e poi nello stato INACTIVE con: protected void onDestroid() Ciclo di vita di una Activity e metodi di Callback Il ciclo di vita di una Activity è racchiuso dall’invocazione dei metodi onCreate() ed onDestroy(). In questa situazione la Activity è visibile, mentre tra i metodi onStart() ed onStop() è possibile anche l’interazione con l’utente. Il ciclo attivo invece, è compreso tra onResume() ed il metodo onPause(). Una Activity in questo stato è in foreground e riceve input da parte degli utenti. Immediatamente prima del metodo onPause() c’è una chiamata ad onSaveInstanceState() che salva lo stato di una Activity in un oggetto di tipo Boundle, il quale sarà passato come visto prima al metodo onCreate(). 1.5.2 View e Layout Con l’aumentare della risoluzione degli schermi, della loro grandezza e della potenza dei processori, sono aumentate le caratteristiche che uno smartphone può fornire dal punto di vista del layout. Gli sviluppatori android hanno definito delle classi View 41 , dediche alla visualizzazioni e alle interazioni con gli utenti. Una delle caratteristiche principali delle View è sicuramente legata al fatto che esse possono essere descritte sia all’interno di documenti XML (all’interno della cartella /res/layout), o tramite codice, utilizzando le opportune API. Il metodo più semplice per creare le View è sicuramente la creazione di tag. All’interno di questi sono inseriti molti attributi come andorid:id, android:layout height ed android:layout width, i quali indicano rispettivamente un identificatore, l’altezza e la larghezza. Gli ultimi due attributi descritti sono obbligatori e possono con41 Le View non sono l’unico strumento per fornire un layout. Esiste infatti la classe ViewGroup, estenzione della classe View, la quale è disegnata per contenere più View contemporaneamente. 40 Capitolo 1 tenere costanti statiche come FILL PARENT 42 o WRAP CONTENT 43 , mentre si possono usare altri parametri per definire padding, margini ed elementi specifici. Android mette a disposizione molte generalizzazioni della classe View. Nelle prossime righe descriveremo quelle più importanti. LinearLayout: componente base che permette di disporre le View in esso contenute su una singola riga o colonna, a seconda della proprietà orientation 44 . RelativeLayout: in questo caso è possibile specificare la posizione assoluta delle View in esso contenute; sono molto importanti gli attributi descritti dalla classe RelativeLayout.LayoutParams 45 ed è necessario evitare ciclicità di essi, all’interno del contenitore. TableLayout: le TableLayout permettono di organizzare le View secondo una struttura formata da righe e colonne. All’interno dei nodi TableRow è possibile la definizione di attributi come layout column ed layout span. FrameLayout: permette il controllo sulla visualizzazione delle View, fornendo strumenti per nasconderle o visualizzarle. ListView: permettono di visualizzare informazioni attraverso una lista. Questa non fa altro che implementare righe attraverso il metodo setAdapter( ListAdapter adapter ), dove ListAdapter è una collezione di Adapter 42 46 . L’attributo FILL PARENT indica che la View occupa tutto lo spazio disponibile all’interno del contenitore. 43 L’attributo WRAP CONTENT indica che la View occupa solo lo spazio necessario alla visualizzazione. 44 Ogni dispositivo è sensibile ai cambiamenti dell’orientamento. Per View come LinearLayout o RelativeLayout è possibile utilizzare l’attributo andorid:layout orientation, il quale specifica la tipologia di orientazione (portaint o landscape). 45 Molto importanti sono gli attributi layout toRightOf, layout toLeftOf, layout alignParentRight, layout alignParentLeft, layout alignBaseline. 46 Gli Adapter permettono di associare dati a determinate View. A seconda della View associata all’Adapter, esso verrà definito in diverso modo (ad esempio un Adapter associato ad una ListView è definito ListAdapter). Essi possono essere assimilabili a collezioni di elementi come le liste, però, al contrario di queste fanno 41 Capitolo 1 GridView: molto simile ad una ListView, però in questo caso si può specificare il numero di colonne attraverso il quale visualizzare gli elementi. ExpandableListView: molto utile nel caso in cui si vogliono visualizzare dati che si differenziano tra loro da una sola caratteristica. Con le ExpandableListView viene utilizzata la classe ExpandableListAdapter per evidenziare gli attributi diversi degli elementi che andranno a comporre la lista. ScrollView: si tratta di una specializzazione della classe FrameLayout che consente l’inserimento di una sola View figlia (questa però potrà contenere un numero indefinito di figli), effettuando lo scroll del suo contenuto. Da notare che una ListView non effettua lo scroll se è all’interno di questo tag. In questo caso è necessario inserire codici che aggirino l’ostacolo. Spinner: elemento di selezione univoco, a partire da un elenco scelto dallo sviluppatore. Gallery: permette la visualizzazione di immagini sullo schermo con scrolling orizzontale. Queste devono essere passate alla View mediante un oggetto di tipo ImageSpinnerAdapter. Layout Custom: Android permette la realizzare Layout customizzati attraverso opportune specializzazioni della classe ViewGroup o dei layout descritti in precedenza. Come per i Layout, è anche possibile la creazione di attributi custom. Widget toolbox Esse sono utilizzate per creare semplici interfacce che vanno sotto il nome di Widget. La seguente lista racchiude la maggior parte di essi. TextView: utilizzate per inserire elementi di sola lettura nella pagina. Supporta testi multi linea e formattazione di stringhe. La gestione del testo avviene rifermiento sempre ad un layout che dovrà contenere i dati passati mediante un Array. 42 Capitolo 1 attraverso overload del metodo setText(), mentre per recuperarlo, si utilizza il metodo getText(). EditText: elemento che permette l’editazione di un testo. Button: pulsante standard utilizzato per inviare input al programma. AutoCompleteTextView e MultiAutoCompleteTextView: entrambe permettono di suggerire parole all’interno di una EditText. Mentre il primo permette il suggerimento di singole parole, il secondo è dedico a parole all’interno di frasi. In entrambi i casi è necessario passare all’oggetto le parole su cui effettuare le ricerche, ad esempio recuperando dati all’interno di un database. CheckBox: pulsante rappresentato dagli stati checked e unchecked. RadioButton: anche questo componente è caratterizzato dagli stati checked ed unchecked. A differenza del precedente non si può deselezionare l’elemento scelto. È possibile raggruppare più elementi all’interno di una RadioGroup, dove uno ed un solo un elemento viene selezionato in ogni istante. ToggleButton: pulsante che implementa la funzione on/off nel quale è possibile visualizzare label diverse a seconda dello stato. CheckedTextView: versione checkable di una TextView. L’utilizzo prevalente di questa classe avviene nella gestione delle View all’interno delle liste. I possibili valori della modalità sono descritti dalle costanti CHOICE MODE NONE (tipologia di selezione default), CHOICE MODE SINGLE (permette di selezionare un solo valore) ed CHOICE MODE MULTIPLE (per la selezione di un insieme di valori). ImageView: le classi di questo tipo permettono di reperire un immagine. AnalogClock e DigitalClock per la visualizzazione di un orologio analogico o digitale. 43 Capitolo 1 Tutte le precedenti classi derivano dalla classe View. Anche se questa situazione può essere facilmente intuibile, molto più anomalo è che la classe TextView viene ereditata da tutte le altre, le quali estendono i suoi metodi. 1.5.3 Intent e comunicazione tra Activity Come annunciato, gli Intent sono utilizzati in Android per la comunicazione all’interno di una o più applicazioni o come messaggi di sistema. In generale gli Intent possono essere usati per: • Dichiarare come le Activity o i Service possono essere azionati per eseguire una determinata azione. • Registrare l’avvenimento di un determinato evento. • Far partire esplicitamente un particolare Service o Activity. • Inviare messaggi al sistema Il tipico scenario prevede un componente con la necessità di eseguire una particolare azione su un determinato insieme di dati. In diversi casi, il componente non sa quale sarà l’oggetto in grado di soddisfare la sua richiesta, perchè questo dipenderà dall’insieme degli elementi installati nel dispositivo. Ciascuno di questi avrà descritte le proprie competenze attraverso degli Intent Filter all’interno del file AndoridManifest.xml. È compito del dispositivo decidere in base ad una serie di regole, quale componente attivare 47 in corrispondenza di un particolare Intent. In questo caso si parla di Intent implicito. Un Intent può essere esplicito quando il nome del componente che risponderà è già noto in fase di creazione dello stesso. La creazione di Intent espliciti può essere fatta in due modi, a seconda che la Activity da lanciare debba ritornare o meno dati. Supponendo il caso in cui l’attività portata in cima allo stack non deve ritornare alcuna notifica a quella di partenza, si utilizza il metodo public void startActivity(Intent intent) dove l’Intent può specificare la classe Activity da aprire, può includere un’azione 47 Il processo seguito dal dispositivo, per la determinazione del componente da attivare a seguito del lancio di una Intent, si chiama Intent Resolution. 44 Capitolo 1 da eseguire o un insieme di dati, espressi mediante URI 48 o dati semplici. Dopo l’invocazione del precedente metodo è creata una nuova Activity che diviene attiva e visibile, quindi è mossa al primo posto dello stack. Invocando il metodo finish, oppure premendo il tasto back del dispositivo, essa è chiusa e rimossa dallo stack. Esiste un secondo metodo usato quando è necessario per far ritornare dati. In questo caso si parla di attività padre ed attività figlia, dove viene invocato il metodo: startActivityForResult(Intent intent, int requestCode) nel quale, l’Intent è quello da lanciare, mentre requestCode è un intero identificativo dell’attività chiamata. Per reperire i dati nell’attività padre si presuppone un meccanismo di recupero. Esso avviene attraverso l’invocazione di protected void onActivityResult(int requestCode, int resultCode, Intent data) dove il requestCode è un intero per distinguere quale attività sta ritornando, mentre resultCode specifica l’esito della chiamata. Se il parametro è impostato a Activity.RESULT OK si indica un esito positivo dell’invocazione, mentre con Activity.RESULT CANCELED si indica che l’operazione non è andata a buon fine, oppure è stata annullata. Da sottolineare che l’Intent anche in questo caso può ritornare una URI nel modo definito in precedenza o una collezione di informazioni extra (mediante la chiamata dei metodi .putExtra(name, value)). 1.5.4 Gestione degli eventi La gestione degli eventi è uno dei concetti fondamentali riguardanti l’utilizzo dei componenti di un’interfaccia grafica. Al verificarsi un evento possono eseguirsi determinati codici, quindi, occorrono dei listener in grado di verificare determinati avvenimenti. 48 Per esempio, si può inviare mediante URI, un numero di telefono che permette di iniziare una chiamata. In questo caso si utilizza il parametro ACTION DIAL. Altri tipi di azioni native possono essere: ACTION CALL (inizia immediatamente una chiamata, usando il numero contenuto nella URI), ACTION INSERT (apre una Activity per inserire un nuovo elemento nel cursore specificato nella URI dell’intent), ACTION WEB SEARCH (apre una pagina web di ricerca basandola sul testo inserito nella URI dell’intent). 45 Capitolo 1 Esiste una regola associata al nome di un evento che può essere sintetizzata come: se Evento è il nome dell’evento l’interfaccia ad esso associata sarà View. OnEventoListener, la quale definisce metodi di callback. Inoltre, la registrazione dell’ascoltatore avviene attraverso la definizione di metodi del tipo setOnEventoListener. Gli eventi di principale importanza sono racchiusi nelle seguenti righe: OnClickListener: (click) evento di selezione. Dalla versione 1.6 dell’SDK è possibile specificare il metodo da eseguire dopo il click, mediante un attributo, all’interno del documento XML. Questo attributo prende il nome di android:onClick. OnLongClickListener: (long click) evento di selezione prolungata di un componente. OnFocusChangeListener: (focus change) acquisizione o perdita del focus da parte di un componente. OnKeyListener: (key) evento di selezione di un tasto. OnTouchListener: (touch) evento di touch. Alcuni metodi della classe View vengono invocati automaticamente su tali metodi, dei quali è possibile farne l’overriding e tutti sono caratterizzati da un parametro identificatore KeyEvent del tasto premuto. Tra essi si citano: • onKeyDown • onKeyUp • onKeyLongPress • onKeyPreIme • onKeyMultiple • onKeyShortcut • onKeyUp 46 Capitolo 1 1.6 Dialog, Toast, Widget e Notification A partire dalle versioni 2.0 della piattaforma sono state inserite importanti novità. Di questa categoria fanno parte gli home-screen Widget (da non confondere con i widget relativi alle classi View) ed i notification services. Oltre i precedenti elementi citati, si tratteranno in questa sezione i Dialog ed i Toast, i quali sono tutti utilizzati per fornire informazioni all’utente in modo semplice e veloce. 1.6.1 Dialog Le finestre di dialogo permettono la visualizzazione e l’inserimento di piccole quantità di informazioni. Ogni Dialog è caratterizzata da una Activity da cui acquisisce le informazioni ed è rappresentata mediante i seguenti metodi: showDialog(int id): per la visualizzazione su schermo, dove id è l’identificativo della finestra. onCreateDialog(int id): utilizzato per la creazione della finestra ed eseguito solo durante la prima richiesta di visualizzazione. onPrepareDialog(int id, Dialog dialog): permette di preparare la finestra ad una successiva visualizzazione. DismissDialog(): un dialog che ha terminato il proprio compito può essere nascosto attraverso questo metodo. Utilizzandolo non vengono effettuate ulteriori chiamate ad onCreateDialog(int id) per le successive visualizzazioni. RemoveDialog(): metodo che rimuove la finestra di dialogo. A differenza del precedente verranno effettuate le chiamate onCreateDialog(int id) per le successive visualizzazioni. In Android è possibile creare le seguenti finestre di dialogo: Alert: permettono di segnalare informazioni come errori, warning ecc. Una finestra di tipo Alert può contenere informazioni circa un titolo ed un messaggio. 47 Capitolo 1 Progress: utilizzate quando si vuole caricare una notevole quantità di dati, oppure eseguire un’operazione molto lunga. Queste finestre forniscono all’utente l’idea del caricamento dati. Date e Time Picker: finestre di dialogo relative ad informazioni su date ed orari. Custom Dialog: è possibile utilizzare specializzazioni delle AlertDialog o delle ProgressDialog per le proprie interfacce. 1.6.2 Toast Un toast è il meccanismo con cui Android mostra dei messaggi temporanei sullo schermo, in cui non si prevede nessun tipo di interazione con l’utente (se non la visualizzazione stessa). Per la creazione di questi elementi è necessario dichiarare una stringa da visualizzare ed una durata. La durata può assumere i valori descritti dalle costanti LENGTH SHORT, LENGTH LONG, abedue appartenenti alla classe Toast, oppure un qualsiasi lasso di tempo espresso in millisecondi. Per default i Toast vengono visualizzati in basso nello schermo del dispositivo. Questa ed altre caratteristiche possono essere customizzate. Essendo questo elemento molto semplice da creare e facile da comprendere, essi possono essere una valida alternativa alle finestre di dialogo. I produttori di dispositivi mobili che girano su Android, li prediligono nel momento in cui si ha bisogno di fornire informazioni all’utente. 1.6.3 Notification Service Questo componente fornisce un meccanismo alternativo per la notificare la presenza di particolari informazioni. La notifica di questi messaggi avviene nella parte alta del display detta status bar e non interrompono l’eventuale attività dell’utente, il quale per visualizzarla dovrà trascinare la barra di riferimento verso il basso. Le informazioni contenute in una notifica vengono incapsulate all’interno di un oggetto Notification. Ciascuna Notification contiene: • un icona. 48 Capitolo 1 • Un breve messaggio da visualizzare ed un layout corrispondente. • Un Intent da lanciare nel caso di selezione. Oltre a le informazioni obbligatorie è possibile associare un particolare un suono una vibrazione 1.6.4 50 , l’attivazione dei LED 51 49 , . Home-screen Widget I Widget e più specificatamente le AppWidgets sono componenti visuali rappresentati nella home del dispositivo. Un Widget è creato per la visualizzazione di elementi come appuntamenti, messaggi inviati o informazioni riguardanti il meteo. Spesso i progettisti di applicazioni forniscono diverse versioni dei Widget, che visualizzano le stesse informazioni, in modi diversi e con diversa grandezza 52 . Questi elementi sono implementati come IntentReceivers ed utilizano RemoteViews 53 per visualizzarne il contenuto. Per la loro creazione è necessario inserire tre componenti: 1. Un layout che definisce la UI del Widget. 2. Un file XML che definisce i metadata associati. 49 Per aggiungere un suono alla notifica è necessario modificare un attributo pubblico, nel caso si voglia utilizzare un suono di default, oppure creare un riferimento ad un media da riprodurre mediante URI per la riproduzione di un suono specifico. 50 Anche per la vibrazione bisogna modificare un attributo pubblico, dove il corrispondente valore è un Array di long i quali rappresentano, in millisecondi, il dalay relativo alla prima vibrazione, la sua durata, ed eventuali coppie di informazioni dello stesso tipo, relativamente a delay e durata di vibrazioni successive alla prima. 51 In questo caso è possibile scegliere il colore del LED, la durata relativa all’accenzione e allo spegnimento. 52 La home-screen di un dispositivo Android è divisa da una griglia 4x4, dove ognuna di esse ha grandezza prefissata di 74x74 dp (device-indipendent pixels). Per scegliere il numero dei bolcchi che il widget occuperà è necessario effettuare il seguente conto: Mininum size dp= (Cell-coun * 74dp) 2dp. Nel caso il numero non corrisponda al precedente conto vengono utilizzate un numero di celle che più si avvicina ad esso. 53 RemoteView: classe che descrivere una gerarchia di view che possono essere visualizzare in un processo separato. La gerarchia è dichiarata attraverso un file XML e questa classe racchiude operazioni base per modificare il contenuto. 49 Capitolo 1 3. Un Intent Receiver che definisce i controlli. 4. Un periodo minimo per l’aggiornamento espresso in millisecondi, oppure un’attività di configurazione. 5. Uno specifico tag all’interno del file AndroidManifest.xml. Anche se un Widget è definito attraverso un file XML creato ad-hoc, non è possibile inserire tutte le View messe a disposizione in Android. Attualmente i Layout utilizzabili sono limitati a: • FrameLayout • LinearLayout • RelativeLayout Le View utilizzabili sono invece: • AnalogClock • Button • Chronometer • ImageButton • ImageView • ProgressBar • TextView 1.7 Gestione dei dati Il salvataggio ed il caricamento dei dati è una caratteristica essenziale per molte applicazioni. Le tecniche utilizzate in android per questo tipo di operazioni si dividono in: 50 Capitolo 1 Shared Preferneces: permettono il salvataggio di elementi primitivi (String, Long, Boolean, Float ed Integer ) mediante una coppia key/value che prende il nome di preferenza. Per creare una Shared Preference è necessario chiamare il metodo getSharedPreferences passando il nome della stessa, mentre per modificarla è inoltre necessario effettuare chiamate ai metodi edit() e commit(). Una volta aperta la preferenza è possibile operare con i dati in essa contenuti attraverso i metodi get() 54 e put() 55 . Application State: ogni Activity include come visto in precedenza determinati eventi atti al salvataggio dello stato corrente. In questo caso si utilizzano oggetti Bundle in cui possono essere passati parametri primitivi rappresentati da una coppia key/value. Files: permettono il salvataggio di dati su file. Come avviene in Java è possibile aprire stream in lettura e scrittura attraverso i metodi openFileInput(String name) ed openFileOutput(String name, int mode), dove si specifica il nome del file e nel caso di scrittura, si decide la visibilità dello stesso da parte di altre applicazioni. Un valore corrispondente a MODE PRIVATE della classe Context permette di specificare che il file è accessibile solo all’applicazione che lo crea. Un valore impostato a MODE WORLD READABLE permette la lettura ad altre applicazioni, mentre MODE WORLD WRITEABLE permette anche la scrittura. Casi d’uso sono principalmente i seguenti: 54 Tra i metodi get di una shared preference troviamo: getBoolean(String key, Boolean defaultValue), getFloat(String key, Float defaultValue), getInt(String key, Integer defaultValue), getLong(String key, Long defaultValue), getString(String key, String defaultValue). In tutti questi casi i parametri hanno lo stesso significato. Il primo è utilizzato come chiave della preferenza, mentre il secondo imposta il valore di essa ad uno di default, scelto nel caso in cui non venga trovata la chiave corrispondente. 55 Allo stesso modo dei metodi get() è possibile utilizzare i metodi put() sostituendo get a put. Gli attributi da passare assumono lo stesso significato del caso precedente. 51 Capitolo 1 • Lettura o scrittura su filesystem locale. Le modalità di accesso mofica e creazione sono quelle descritte in precedenza. • Lettura o scrittura di informazioni su SD card. I procedimenti di lettura e scrittura sono ugauali a quelli su filesystem locale. L’unica differenza è nella directory in cui tali memorie vengono montate. In queste situazioni è molto utilizzato il metodo: String extStDir = Environment.getExternalStorageDirectory().toString() il quale restituisce una stringa contenente la posizione della directory. • Lettura di un file statico che si trova all’interno della cartella res/raw. In questa cartella sono inseriti file a cui non si vuole applicare nessun processo di ottimizzazione, ma caratteristici dell’identificatore della classe R. Per utilizzare questa tipologia di risorse è necessario utilizzare il metodo: openRawResource(int id) il quale restituisce un oggetto di tipo InputStream. Content Providers: i Content Providers offrono uno strumento ben definito per usare e diffondere dati. Essi sono rappresentati attraverso una semplice URI, 56 , del tipo content://schema. Molti database nativi 57 , come contatti telefonici o media store, possono essere utilizzati dai Content Providers per l’accesso, la modifica e l’eliminazione, dei dati in esso contenuti. 56 È buona norma utilizzare il path del progetto nella URI nel seguente modo content://com.CompanyName.provider.ApplicationName/DataPath. Allo stesso modo la dicitura /RowNumber è rappresentata per effettuare una singola richiesta. Ancora una volta troviamo l’associazione tra una URI ed un mime-type dei corrispondenti elementi. Un mime-type è composto da una parte type ed una parte subtype (ad esempio un mime-type potrebbe essere image/jpeg), dove la prima parte esprime un contenuto e la seconda, dipendente dalla prima, una descrizione della modalità di rappresentazione del dato. 57 Esempi di Content Providers nativi possono essere UserDictionary (contenente i dati relativi al dizionario utente), Setting (conentente le preferenze del dispositivo), MediaStore (gestisce l’accesso ai dati multimediali quali audio, video ed immagini), Contacts (per la gestione dei contatti dell’utente) e CallLog (gestore delle chiamate). 52 Capitolo 1 Database: una delle caratteristiche nella progettazione di applicazioni Android ed in generale per qualsiasi piattaforma o linguaggio è la gestione di dati attraverso un database. Esistono molte tecnologie per la creazione di base dati, ma quella che ha avuto maggior successo fa uso di uno schema relazionale. Google ed altri produttori di sistemi per dispositivi mobili hanno implementato un RDBMS all’interno dello stack, un database embedded chiamato SQLite. Attraverso delle reali necessità, negli ultimi anni sono apparse nuove tecnologie per la memorizzazione dei dati all’interno di un database. Queste vengono comunemente chiamate OODBMS [10] oppure ODBMS dove O sta per Object. Una buona parte di questa tesi è dedica allo studio di queste tecnologie, si rimanda allo specifico capitolo per approfondire i concetti. 53 Capitolo 2 Database ad oggetti e relazionali In molte applicazioni è richiesta la gestione permanente dei dati. L’informatica ha inizialmente messo a disposizione un sistema per gestione delle informazioni mediante dei semplici archivi poco flessibili e performanti. Il programmatore aveva la completa responsabilità circa l’organizzazione e gestione dei dati, il che portava ad una grande ridondanza di dati, spreco di memoria e rischio di inconsistenza degli stessi. In un secondo momento a questi semplici archivi si sono aggiunti sistemi più potenti chiamati banche dati o base dati. Un database è un insieme di dati permanente organizzato per minimizzare la ridondanza e per essere accessibile da parte di più utenti ed applicazioni. Insieme alle basi di dati si citano i DBMS. Un DBMS (Data Base Management System) è un sistema software progettato per consentire la creazione e manipolazione di database. Se in passato i DBMS erano diffusi principalmente presso le grandi aziende e istituzioni, oggi il loro utilizzo è diffuso in ogni contesto. Un DBMS può essere costituito da un insieme complesso di programmi che controllano l’organizzazione, la memorizzazione e il reperimento dei dati in un database. Esso controlla anche la sicurezza e l’integrità della base di dati, non consentendo a più utenti di modificare lo stesso record contemporaneamente mediante il blocco dei record. I linguaggi di interrogazione del database permettono agli utenti di interrogare il sistema e di analizzarne i dati mediante delle query. Molti DBMS support54 Capitolo 2 ano le API (Application Programming Interface) dell’Open Database Connectivity (ODBC) o Java Database Connectivity (JDBC, lo standard per Java), le quali forniscono strumenti standardizzati per l’accesso ai database. 2.1 Database Relazionali e RelationalDBMS Nel 1970 si cominciò a produrre diversi documenti culminati nel Modello Relazionale. Si descrisse un nuovo sistema per archiviare e modificare grandi quantità di dati mediante l’uso di righe (record o anche tuple) e tabelle. Il modello relazionale si basa sulla nozione di relazione appartenente alla teoria degli insiemi. Per ogni record 1 viene definita una chiave 2 , ovvero un identificatore univoco della tupla. La chiave può essere qualunque dato memorizzato, un campo aggiunto specificatamente per questo scopo, o una combinazione di più campi (chiave composta). In questo tipo di rappresentazione l’ordinamento di righe e colonne è irrilevante. Inoltre, nel caso in cui il valore non è disponibile su una n-pla si utilizza la nozione di valore nullo (NULL), il quale può generare problemi nel caso in cui il campo sia importante per la tabella e per questo motivo esistono specifiche per l’inserimento forzato degli elementi. Esistono linguaggi per effettuare operazioni di inserimento, modifica, ed interrogazione. Questi rientrano sotto il nome di: DDL 3 , DML 1 4 e QUERIES 5 , i quali Nelle basi di dati relazionali l’elemento corrispondente al record è chiamato tupla. 2 Una chiave è l’elemento di riferimento che distingue una riga da un’altra. Una chiave può essere interna, se appartiene alla stessa tabella, o esterna, se si fa riferimento ad un campo relativo ad una tabella diversa da quella attuale. Di solito si fa uso di un ID per la creazione di una chiave int. 3 DDL: Data Definition Language. I database relazionali utilizzano istruzioni del tipo CREATE TABLE, che permettono la definizione di una tabella all’interno dello schema. 4 DML: Data Manipolation Language; consente di leggere, inserire, modificare o eliminare i dati in un database. I comandi DML esprimono azioni da effettuare sui dati identificati dalla parola iniziale dell’istruzione che quasi sempre è un verbo. Nel caso di SQL i verbi utilizzati sono SELECT, INSERT, UPDATE e DELETE. 5 QUERIES: Interrogazioni al database effettuate mediante il costrutto SELECT55 Capitolo 2 sono spesso associati ad SQL 6 . Il modello relazionale rappresenta oltre il 90% della tecnologia aziendale riguardo i DBMS. Il successo di questo sistema è dovuto alla sua solidità, al suo linguaggio SQL e al fatto che è stato il primo ad entrare nel mercato globale; le aziende che hanno costruito il proprio DB mediante uno schema relazionale non vedono la necessità di cambiare modello. Al giorno d’oggi molti programmi sono spesso costruiti con l’aiuto della tecnologia ad oggetti e database relazionali. Un sistema ad oggetti è caratterizzato da identità, stati, ecc, mentre il modello relazionale è caratterizzato da tabelle, colonne, righe e chiavi. In questo caso diviene necessario un sistema intermedio detto ORDBMS 8 7 che introduce un nuovo livello nello stack software, dettato da un ORM . 2.1.1 SQL SQL [8] è un linguaggio progettato per leggere, modificare e gestire dati memorizzati in un sistema di gestione di basi di dati, basato sul modello relazionale. La prima versione fu sviluppata da IBM all’inizio degli anni settanta e in quelli successivi sono state rilasciate altre versioni riconosciute dall’ISO 9 . FROM-WHERE. 6 SQL: Structured Query Language. Questo sistema si basa su un solido modello matematico, ma al tempo stesso è di facile comprensione. 7 ORDBMS: object-relational mappers. Oggi molte applicazioni mischiano il modello relazionale e il modello a oggetti; si parla quindi di ORDBMS o Object Relational DBMS. Naturalmente si può utilizzare un programma procedurale con un modello relazionale. Bisogna ammettere che questa soluzione è abbastanza obsoleta, quindi si esula dal descrivere eventuali vantaggi o difetti della stessa. 8 ORM o Object-Relational Mapping. È una tecnica di programmazione che favorisce l’integrazione di sistemi software orientati agli oggetti con sistemi RDBMS. Tra gli ORM si possono citare Hibernate o Tolpick; questi sistemi incrementano la complessità e la latenza del sistema. 9 L’Organizzazione internazionale per la normazione (ISO o International Organization for Standardization); è la più importante organizzazione a livello mondiale per la definizione di norme tecniche. Le norme ISO sono numerate e hanno un formato del tipo ISO 99999:yyyy: Titolo dove 99999 è il numero della norma, yyyy l’anno di pubblicazione e Titolo è una breve descrizione. 56 Capitolo 2 La maggior parte dei sistemi per la gestione di database implementano questi standard ed aggiungono funzionalità proprietarie. In questo modo si creano dialetti che si differenziano, seppur minimamente da altri. SQL progettato originariamente come linguaggio di tipo dichiarativo, si è successivamente evoluto con l’introduzione di costrutti procedurali, istruzioni per il controllo di flusso, tipi di dati definiti dall’utente e varie altre estensioni. Lo standard si divide in: Data Definition Language: (DDL) permette di creare e cancellare database o di modificarne la struttura. Data Manipulation Language: (DML) usato per aggiungere inserire e manipolare i dati. Data Control Language: (DCL) usato per autorizzare gli utenti o un gruppo di essi ad accedere ai dati. Query language: (QL) permette di interrogare il database. Tra i costrutti fondamentali appartenenti alle precedenti categorie si citano: • CREATE TABLE: per la creazione di una tabella dei campi e degli id in essa contenuti. • ALTER TABLE: per modificare la struttura di una tabella (aggiungere, rimuovere e modificare un campo). • DROP TABLE: per la cancellazione di una tabella. • INSERT: inserimento dei dati in una tabella. • SELECT: ricerca di campi all’interno di una singola tabella. A questo costrutto si associa la clausola FROM (tabelle cui cercare i campi) e WHERE (clausole di selezione). • JOIN: utilizzato per cercare campi all’interno di più tabelle. Questa tecnica è in grado di fondere le precedenti. 57 Capitolo 2 Come un qualsiasi linguaggio di programmazione, anche l’SQL utilizza una serie di simboli atti a definire uguaglianze, fare confronti e calcoli mediante operatori. Gli operatori messi a disposizione dal SQL standard si dividono in quattro categorie: • Operatori di confronto; • Operatori aritmetici; • Operatori condizionali; • Operatori logici; 2.1.2 SQLlite Una delle caratteristiche di Android riguarda la possibilità di gestire i dati attraverso un database relazionale. Si tratta di SQLlite, un DBMS che ha tra le sue caratteristiche principali quella di essere molto leggero (circa 500kb), veloce, semplice ed open-source. Essendo adatto per dispositivi a risorse limitate, è stato implementato nello stack Android (oltre che a molti dispositivi elettronici, incluso molti riproduttori MP3, iPhone ed iPod). In Android ogni database SQLite [9] è parte integrante dell’applicazione a cui si riferisce; gli strumenti per la gestione di SQLlite si possono dividere nelle classi usate per la gestione dei cursori 10 e in quelle per la gestione delle informazioni. È inoltre possibile associare un insieme di DB-SQLite alla stessa applicazione. Il database è integrato in un singolo file, non sono necessarie configurazioni e non serve una connessione client-server per interrogare il DB (questa non è nemmeno possibile). Al contrario della maggior parte dei RDBMS, non è stato implementato l’uso delle sintassi SQL come: ALTER TABLE, TRIGGER e VIEW (viste). È possibile utilizzare un metodo per la creazione del database e dichiarare se è apribile solo in lettura (OPEN READONLY ), oppure se è possibile accedervi anche in scrittura (OPEN WRITE ). In alternativa si può creare un database in memoria 10 Un cursore definisce un insieme di operazioni che si possono classificare in: operazioni di movimento le quali permettono di posizionare il cursore in varie posizioni, ed operazioni di controllo che permettono di fare test sui dati estratti. Infine ci sono le operazioni che permettono l’accesso ai dati contenuti nel cursore stesso. 58 Capitolo 2 senza crearne il rispettivo file, di modo che, il DB è cancellato al momento della chiusura dell’applicazione di riferimento. La creazione delle tabelle avviene allo stesso modo con cui si creano in un normale database relazionale, cioè con l’esecuzione delle classiche istruzioni SQL del tipo CREATE TABLE seguito dalle chiavi, dagli eventuali indici ecc. Per i database creati con SQLlite è strettamente raccomandato aggiungere una chiave identificativa con l’attributo autoincrement 11 . Classe SQLiteDatabase la classe SQLiteDatabase mette a disposizione gli strumenti per effettuare le query che si possono classificare in: • ExecSQL: in grado di eseguire script SQL di vario tipo purchè non di query (SELECT ). • Delete: permette di eliminare dati a partire dal nome della tabella e l’eventuale clausola WHERE con i rispettivi valori. • Insert: permetterne l’inserimento di un nuovo elemento all’interno della tabella. • Update: prevede in genere un oggetto di tipo ContentValues 12 contenente i nuovi valori da aggiornare e le eventuali nuove informazioni relative alla clausola WHERE. • Replace: usata nel caso in cui si ha la necessità di sostituire completamente i valori di un insieme di righe. • Query: insieme di API che permettono l’estrazione dei dati dalle tuple. Il risultato di una operazione di query fa riferimento ad un oggetto Cursor, il quale permette di scorrere le informazioni restituite. 11 L’attributo autoincrement indica che ad ogni nuova tupla inserita si inserisce un nuovo identificatore, in modo del tutto trasparente all’utente. 12 Per inserire i dati si utilizza un oggetto di tipo ContentValues. Il metodo put del ContentValues prende in ingresso il nome del valore (String key) e il valore stesso (String value); l’oggetto ContentValues è in pratica è una mappa che associa al nome di una colonna, il rispettivo valore utilizzato per il passaggio dei valori. 59 Capitolo 2 Classe SQLiteQueryBuilder Spesso si ha la necessità di creare query dinamiche il cui insieme di colonne, di clausole where o criteri di ordinamento, non sono noti al momento della compilazione. In questi casi viene utilizzata la classe SQLiteQueryBuilder, la quale permette di incapsulare in un unico oggetto la logica di creazione delle query. 2.2 ODBMS ed RDBMS Supponiamo di aver creato un programma con un linguaggio orientato ad oggetti, ad esempio Java. Questo programma ha un numero di oggetti da dover salvare all’interno di un qualsiasi database. Assumendo di aver creato una semplice classe da memorizzare in uno schema relazionale, si deve creare codici SQL per la creazione delle tabelle. Allo stesso modo si dovranno scrivere molte righe di codice per effettuare query, upload ed eliminazioni. Il precedente progetto potrebbe essere implementato utilizzando la teoria degli ORDBMS. In questo caso lo sviluppatore non deve gestire il lavoro di traduzione di una tupla in un oggetto. Infatti, questo avviene in modo semi-trasparente. L’unico compito lasciato allo sviluppatore è la gestione della traduzione di una tabella in un oggetto (che di solito avviene mediante file XML). La precedente ipotesi ha dei limiti dettati dai tempi di latenza richiesti per effettuare il mapping e dalla gestione dello stesso. Gli sviluppatori si sono chiesti perciò se tutti questi problemi potevano essere risolti. Il risultato fu la creazione dei “Pure Object Database”. Con questa soluzione, oltre a non dover effettuare nessun tipo di traduzione tra modello relazionale e quello ad oggetti, non si deve creare nessuna struttura per il database, poichè essa è definita direttamente dalle relazioni delle classi. Esistono molti database ad oggetti e molti di essi sono caratterizzati dalla licenza open-source. Lo scopo della tesi è quello di associare una base di dati ad un’applicazione Android, quindi si può restringere il cerchio, cercando sistemi che non hanno bisogno di una connessione client-server per operare con i dati in esso contenuti. Il 60 Capitolo 2 risultato evidenzia Db4o [11] [12] prodotto dalla Versant e Perst [15] prodotto da McObject. 2.3 DBMS ad oggetti Molti sviluppatori hanno conoscenza dei database. La maggior parte di essi hanno familiarità con la programmazione orientata agli oggetti ed hanno necessità di utilizzare un database per mantenere la persistenza. Utilizzando le nozioni introdotte in precedenza si deve effettuare un mapping tra un sistema ad oggetti verso un sistema relazionale. Esther Dyson 13 ha espresso la seguente affermazione per fare un paragone tra Relational Database ed Object Database: Using tables to store objects is like driving your car home and then disassembling it to put it in the garage. It can be assembled again in the morning, but one eventually asks whether this is the most efficient way to park a car. In casi in cui la struttura del progetto è costituita solo da un elemento, potrebbe essere semplice convertire l’oggetto in una tabella di un database relazionale. Sfortunatamente non è sempre cosı̀ semplice; potrebbero esserci oggetti che hanno riferimenti ad altri o collezioni di essi. In casi come questi risulta difficile creare tabelle a partire dai precedenti. Questo problema può essere eliminato mediante l’utilizzo di ODBMS 13 14 . Gli Esther Dyson (Zurigo, 14 luglio 1957) è una giornalista statunitense nota esperta campo delle tecnologie digitali emergenti. Dyson e la sua azienda sono specializzati nell’analisi dell’impatto delle tecnologie informatiche, nei mercati, sull’economia e nella società. 14 ODBMS: Object Database Management System. Nel 1989 all’interno della pubblicazione “The Object-Oriented Database System Manifesto”, Malcom Atkinson e Francois Bancilhon definirono un ODBMS come un sistema di basi di dati orientato agli oggetti che deve soddisfare due criteri: deve essere un DBMS e un sistema orientato agli oggetti. Il primo criterio si traduce in 5 richieste: persistenza, gestione del salvataggio dati, concorrenza, recovery e creazione di query ad hoc. Il secondo requisito si traduce in in sette affermazioni: oggetti complessi, identità degli oggetti, incapsulazione, tipi di classi, estensibilità, overriding, ereditarietà. La strada degli ODBMS però è ancora più lunga, infatti inizia nel lontano 1980 con il progetto ORION che porterà alla nascita di ITASCA e Versant. Per avere il primo progetto Open-Source si dovrà aspettare il 2004 (Db4o). 61 Capitolo 2 Figura 2.1: Sistema ORM, messo a confronto con un database ad oggetti (Db4o in particolare). ODBMS non sono una nuova tecnologia poichè sono stati implementati nelle applicazioni fin dall’inizio degli anni 90, ma non hanno avuto almeno inizialmente una grande notorietà. A seguito della creazione di questi sistemi è stato fondato l’ODMG 15 che ha cercato di stabilire le regole per i database ad oggetti, quali ODL ed OQL 17 16 . Questi standard sono stati adottati con molte estensioni, ma non sono stati mai presi come regole da seguire. La versione finale dello standard ODMG, ODMG 3.0, venne rilasciata nel 2001, ma il gruppo si è sciolse poco dopo. Mediante l’utilizzo di database ad oggetti, lo schema del database è rappresentato dalle stesse classi. Gli oggetti sono salvati esattamente nello stesso modo in cui essi sono creati all’interno dell’applicazione. In questa struttura è importante mantenere le relazioni delle classi all’interno del database, ed al contrario dei database 15 ODGM: Object Database Management Group. L’ODMG è stato concepito nell’estate del 1991 da fornitori di database ad oggetti. Nel 1998 l’ODMG ha cambiato il suo nome, in modo da riflettere le specifiche di database ad oggetti e relazionali ad oggetti (prodotto di mapping). Tra il 1993 e il 2001 la ODMG ha pubblicato cinque revisioni. 16 ODL: Object Data Language. Linguaggio per la definizione di oggetti equivalente al linguaggio DDL di strutture relazioniali. 17 OQL: Object Query Language (linguaggio standardizzato per le query). Con l’OQL è possibile effettuare query in modo del tutto simile all’SQL, mantenendo strutture base SELECT-FROM-WHERE, ma eliminando INSERT DELETE ed UPDATE. L’idea di creare query in questo modo venne abbandonata nel 2001 e nel 2005, venne proposto di creare query mediante il linguaggio di programmazione utilizzato. 62 Capitolo 2 Figura 2.2: Visione degli OID, relativi agli oggetti, mediante il tool Object Manager. relazionali è possibile l’inserimento di relazioni inverse, a cui daremo spazio in un paragrafo dedicato. Un oggetto è salvato mediante una chiave univoca OID 18 . Questa chiave fa in modo che ogni oggetto sia univoco rispetto agli altri, in modo da poter creare oggetti identici ma distinti fra loro. L’identificatore è solitamente un numero e non è visibile agli utenti ed agli sviluppatori. Anche se esistono molte analogie tra il concetto di ID tipico dei modelli relazionali e quello OID, esistono molte differenze tra di loro. Mentre una chiave è creata, visibile e può essere modificata dagli utenti, un OID non è modificabile ed è un concetto del tutto trasparente all’utente. L’unicità di una chiave concerne solamente le relazioni a cui essa riferisce, mentre gli OID permettono di identificare una singola istanza all’interno di tutta la base dati. In questo caso si potrebbe incorrere in problemi poichè è possibile creare e salvare duplicati di oggetti che già esistono nel database. Questo problema è risolto lasciando allo sviluppatore la scelta di poter aggiungere oggetti identici, mediante controlli effettuati via codice. 18 OID: Object ID. Chiave univoca all’interno del database. Essa è utilizzata per mantenere le relazioni tra gli oggetti in modo del tutto trasparente all’utente e allo sviluppatore. Anche per le relazioni inverse si fa uso degli OID. 63 Capitolo 2 Figura 2.3: semplice struttura ad albero di una base ad oggetti, contentente Piloti, Scuderie ed Automobili. L’intera base dati può essere vista sotto forma di un grafo o di un albero in cui, i nodi sono costituiti dagli oggetti rappresentati, mentre gli archi indicano i collegamenti esistenti. Lo spostamento da un nodo all’altro è indicato con il termine navigazione o attraversamento. La navigazione è il modo con cui si effettua l’accesso all’interno di una base dati orientata agli oggetti: ci si sposta da un oggetto verso un’altro a lui direttamente connesso fino a giungere al nodo foglia (finale). In questi ultimi anni molte aziende stanno avendo un occhio di riguardo verso gli OODBMS. Le applicazioni che traggono i maggiori vantaggi dall’utilizzo di questo approccio sono quelle che gestiscono relazioni complesse tra oggetti 19 . Benefici di questo modello vanno dall’eliminazione di molte linee di codice per accedere ai dati (Query, Insert ed ORM), alla riduzione o eliminazione di configurazioni. 19 La letteratura scientifica enfatizza sulle performance delle basi dati ad oggetti: viene affermato che su dati complessi, un DBMS orientato agli oggetti si dimostra dal 10% al 100% più veloce di un RDBMS. L’ampiezza di tale intervallo viene giustificata dal diverso grado di complessità del dato memorizzato. 64 Capitolo 2 2.4 Db4o Db4o 20 è un prodotto sponsorizzato dalla Versant Corporation 21 utilizzato in più di 170 paesi in tutto il mondo, con una comunità composta da più di 60.000 membri. La missione degli sviluppatori è fornire un’alternativa all’approccio relazionale. Db4o è nato nel 2004, principalmente utilizzato per dispositivi mobili e per progetti che girano su Java o .NET. Tra le industrie che usano db4o si possono citare aziende che si occupano di comunicazione, scienze mediche e trasporti. Tra le principali industrie ci sono: Boeing, Bosch ed Intel. Molto frequentemente queste industrie scelgono il database Versant nel caso in cui si devono gestire oltre 10GB 22 di dati. Altra caratteristica importante riguarda il fatto che db4o a discapito di altri DBMS embedded consente la modalità client-server 23 , oltre ad un sistema di replicazione DRS (database replication system) verso un server a modello relazionale. Db4o è basato da una singola libreria 24 facile da implementare in un’appli- cazione, sia essa basata su Java oppure su .NET. Il suo successo sicuramente dovu20 Db4o è stato creato dalla Db4object inc., fondata nel 2000 da Carl Rosenberger. Qualche anno dopo è stato rilasciato il primo prodotto: db4o 1.0 per Java. Nel 2004 la compagnia è stata incorporata sotto la CEO Christof Wittig. 21 La Versant Corporation è sita nella città di Redwood, California. Versant è leader nel campo dei database ad oggetti, supportando tecnologie open-source e commerciali. Tra i progetti open-source spicca sicuramente Db4o, mentre la soluzione commerciale adatta ad applicazioni destkop, è stata chiamata VOD (Versant Object Database). 22 È stato provato che db4o può essere utilizzabile con database fino a 254GB. Se il database cresce oltre questo punto si possono creare nuovi file. Questa soluzione può eliminare il limite di spazio, ma in questi casi è consigliabile l’uso di database relazionali, i quali hanno limiti di dimensione ben superiori. 23 Molte applicazioni interagiscono con il database mediante un modello client/server. Con l’utilizzo di Db4o questo non è necessario. Le ragioni di questa scelta sono sicuramente da imputare all’utilizzo prevalente in client standalone che non consentono connessione verso nessun server. Db4o non è stato il primo sistema ad introdurre questa caratteristica, esistono infatti molti database relazionali che fanno uso della stessa modalità. 24 Esistono tre diverse versioni di Db4o; in tutti i casi bisogna includere all’interno del progetto una libreria di riferimento denominata con il nome di core. In alternativa è possibile includere nel progetto solo determinate classi diminuendo la memoria occupata. 65 Capitolo 2 to alle elevate performance, agli zero costi di amministrazione ed alla sua elevata semplicità. Altro punto fondamentale sta nella sua versione open-source che ha nettamente aumentato il successo di questo sistema. Per tutte le caratteristiche esposte in precedenza, Db4o è una valida alternativa ad SQLite per la gestione di database all’interno delle apps Android. Come nel caso di SQLite, il file del database è incluso nella directory data/data/package-name/ in un singolo file con estensione .db4o. In alternativa si può utilizzare l’estensione .yap (yet another protocol), ma è consentito utilizzare qualsiasi altra estensione. 2.4.1 Licenza La doppia licenza dei prodotti sta diventando uno dei modelli fondamentali per i progetti software delle aziende, mantenendo in questo modo le potenzialità di ognuna delle caratteristiche open-source e closed-source. È importante capire cosa permettono questi modelli per evitare di incorrere in violazioni, specialmente per le licenze open. Di solito per il prodotto open si usa la licenza GNU 25 . La Versant offre tre differenti licenze: General Public Licence (GPL) verrsione 3: il prodotto è liberamente utilizzabile per questa licenza sotto le specifiche della licenza GPL. Commercial License: questa licenza a discapito della GPL, include opzioni premium, come il supporto tecnico 24/7, ed inoltre è possibile contravvenire alle richieste necessarie nella licenza GPL. Db4o Opensource Compatibility Licence (dOCL): utilizzabile per progetti a libero dominio che implementano db4o, ma che non rientrano nelle specifiche GPL o commerciale. 25 GPL: General Public License (version 2). La GNU General Public License è una licenza per software libero. È comunemente indicata con l’acronimo GNU GPL o semplicemente GPL. La GNU GPL assicura all’utente libertà di utilizzo, copia, modifica e distribuzione. 66 Capitolo 2 2.4.2 Object Container ed Operazioni basilari L’Object Container può essere considerato come la porta di accesso al database. Normalmente si deve aprire la connessione quando l’applicazione inizia, chiudendola nel momento in cui essa termina. Le operazioni basilari includono il caricamento, la modifica, l’eliminazione e le query sugli oggetti. Essi sono trattati nel dettaglio nelle prossime righe. Apertura Database: per aprire il database non si deve inserire nessuna istruzione. L’unica condizione richiesta è il passaggio all’Object Container del file-name relativo al database in questione. Chiusura Database: è molto importante la chiusura dell’ObjectContainer attraverso il metodo close(). Questo dichiara la chiusura di una connessione aperta. Chiudendo la transizione si scrivono gli oggetti creati o modificati nel database nella sessione corrente. Caricare oggetti: caricare oggetti nel database è estremamente facile. Bisogna solo passare l’oggetto da salvare attraverso il metodo store(). Utilizzando questo metodo i dati non sono scritti nel database immediatamente, ma saranno scritti alla fine della transizione. Delete: per eliminare un oggetto si utilizza il metodo delete(), cui passare l’oggetto da eliminare. Attualmente db4o non verifica se un oggetto che si intende eliminare sia referenziato con un altri, per cui è compito dello sviluppatore effettuare questa verifica. Update: in questo caso bisogna recuperare gli oggetti, modificarli mediante i metodi setxxx() caratteristici delle classi, ed infine utilizzare nuovamente il metodo store(). Se si vuole effettuare una modifica è necessario effettuare operazioni di recupero e modifica nella stessa transizione. In caso contrario viene inserito un nuovo elemento nel database, il che porta ad un grave errore. 67 Capitolo 2 Opzioni: prima dell’apertura dell’Object Container è possibile dichiarare una serie di configurazioni, come la stampa della versione del database, la creazione di indici 26 , ecc. Index: come per la maggior parte dei RDBMS, Db4o supporta gli indici aggiungendo una voce per ogni oggetto. In questo modo operazioni di inserimento e modifica sono leggermente più lente, ma le query sono altamente preformanti 27 . Tipi primitivi come interi, long e double, possono essere indicizzati. In casi come questo le query raggiungono prestazioni significative. In altro modo possono essere indicizzati stringhe, DateTime, DateTimeOffset, BigDecimal e BigNumber allo stesso modo dei tipi primitivi. Non posso essere invece indicizzati Array e collezioni. 2.4.3 Query Probabilmente la caratteristica fondamentale dei database è quella di poter reperire dati da esso. La capacità di effettuare query è fondamentale per il successo di una base dati. Il modo di definire le query può essere assimilato al modello di dati; i database relazionali ad esempio sono sempre associati al loro linguaggio SQL 28 . Essendo Db4o un sistema ad oggetti ci si rende facilmente conto che è necessaria una nuova struttura per effettuare query. Db4o supporta diversi meccanismi per effettuare le query. Per ognuno di essi si ha come risultato un oggetto di tipo ObjectSet, il quale è una API che ha le stesse 26 Gli indici rendono altamente preformanti le query, aumentando però i tempi di latenza di modifiche ed inserimenti. Per queste ragioni è sconsigliabile il loro uso per database di piccole dimensioni, in quanto le perdite in tempo potrebbero essere superiori ai benefici indotti. 27 Ogni volta che si desidera salvare un oggetto con un indice, Db4o ha bisogno di aggiungere ulteriori dati. Gli sviluppatori indicano che è consigliabile l’utilizzo degli indici in schemi di oltre 10.000 oggetti. 28 A confermare l’importanza che questo linguaggio ha avuto nei sistemi relazionali la dicitura SQL è spesso inserita all’interno del nome di un RDBMS, ad esempio MySQL, PostGreSQL, SQLite. 68 Capitolo 2 caratteristiche di un iteratore. Utilizzando metodi come HasNext e Next è possibbile scorrere, vedere se esiste un successivo elemento e spostare di conseguenza l’oggetto ObjectSet al successivo elemento. I modi con cui è possibbile effettuare le query vanno sotto il nome di NQ (Native Query), QBE (Query By Example) e SODA Queries. Da precisare che è possibile effettuare ricerche sia su tipi semplici di dati come interi, che su Array, stringhe o addirittura oggetti. Il compito di scegliere la tipologia di query da utilizzare nell’applicazione è lasciata allo sviluppatore. In alcune circostanze si preferisce avere una soluzione performante. In casi come questi vengono utilizzate le SODA. In altri casi è possibile richiedere una soluzione confortevole. In questi casi si utilizzano le NQ. Infine ci citano le QBE, ideali per esercitarsi, ma limitate nelle sue funzionalità. Per reperire i dati di due oggetti affetti una relazione è sufficiente effettuare solo una ricerca verso il nodo radice (oggetto principale). In un sistema relazionale invece è necessario effettuare due query separate oppure un Join. Si può facilmente intuire che con db4o si possono eliminare molte righe di codice e quindi possibili errori. Query By Example Il primo metodo messo a disposizione per effettuare le query sono le Query By Example. Esse sono sono utili per ricercare oggetti ma hanno funzionalità molto ristrette. Il modo più semplice per usare le QBE è passare un oggetto “tipo” (utilizzando metodi setxxx()) al metodo queryByExample(). Il motore di db4o seleziona tutte le corrispondenze con l’oggetto creato e ritorna solo quelle trovate. Le limitazioni delle Query-By-Example riguardano l’impossibilità di effettuare query contenenti espressioni logiche, oltre alla necessità di dover creare un oggetto costruttore. Esse non possono inoltre contenere valori di default come 0 nel caso di numeri, stringhe vuote o valori NULL. Nel caso in cui si devono effettuare selezioni che vanno al di la delle possibilità delle QBE, si utilizzano le Native Query e le SODA Query, illustrate nei prossimi paragrafi. 69 Capitolo 2 Native Query Le native-query utilizzano la semantica del proprio linguaggio di programmazione 29 , quindi è un ottimo strumento per effettuare ricerche all’interno del database. Le native-query sono state introdotte in Db4o 5.0 e con esse è possibile utilizzare i metodi getxxx() delle specifiche classi. In questo modo si possono effettuare ricerche usando comparatori, range di valori, operatori logici e di uguaglianza (allo stesso modo è possibile utilizzare operatori composti dalla somma di più operatori logici). Nel caso in cui il risultato dell’espressione è true viene ritornato l’oggetto specifico. Ultimo appunto sta nel fatto che internamente db4o cerca di analizzare le native query per convertirle in SODA, introducendo tempi di latenza superiori. SODA Query Le ultime API utilizzate per effettuare le query sono denominate SODA 30 . Questa metodologia è adatta per la generazione dinamica di selezioni e per la gestione di query molto performanti, basate sulla nozione di query-graph. Un querygraph lascia allo sviluppatore il compito di scegliere quali oggetti cercare attraverso il metodo constrain (il quale aggiunge un nodo alla query), ed usare chiamate al metodo descend per applicare specifici campi di ricerca. Nella struttura a grafo i nodi rappresentano le classi e gli archi rappresentano le relazioni tra i nodi. È possibile inserire costanti per ogni nodo per scegliere quali oggetti sono candidati ad essere inclusi nel risultato della ricerca. Con le SODA è possibile inserire metodi di comparazione ed operatori logici. Esistono inoltre comparatori speciali per le stringhe come start-with, ends-with e like. Come nei dababase relazionali è possibile utilizzare metodi per l’ordinamento ed elementi riguardanti le QBE (in questo caso si inserisce un oggetto “tipo” (QBE) e si effettua la query attraverso specifici metodi (SODA)). 29 L’idea di far integrare le query con il proprio linguaggio di programmazione ha avuto molto successo. Ad esempio la microsoft nel 2006 ha progettato LINQ (Language Integrated Queries) in una versione del database DLINQ. 30 Le SODA sono state inizialmente create come progetto open-source da Carl Rosemerger ma con le nuove versioni, questo strumento è completamente integrato all’interno di Db4o. 70 Capitolo 2 Figura 2.4: Semplice struttura a grafo, per una query SODA, in cui si desidera selezionare un Oggetto Persona, con attributo nome, uguale a Lincoln. Spesso le SODA sono difficili da capire ed interpretare, quindi molti programmatori scelgono di utilizzare le Native Query (attraverso strutture del proprio linguaggio di programmazione). 2.4.4 Transizioni Tutte le operazioni effettuate in db4o sono caratterizzate da una transizione. Esiste sempre una transizione che si sta eseguendo caratterizzata dall’istanza della classe Object Container. Ogni transizione agisce implicitamente, ma il progettista è in grado di decidere quando effettuare una commit 31 per rendere persistenti le modifiche. Per effettuare una transizione di commit bisogna invocare l’omonimo metodo commit() all’oggetto contenitore. Se si incorre in un qualsiasi tipo di problema (crash, distacco dell’energia elettrica) durate l’operazione, questa viene interrotta e lo stato del database torna automaticamente all’istante prima della chiamata. Allo stesso modo della commit, si può utilizzare anche il metodo di rollback. Utilizzando questo metodo è necessario effettuare una chiamata a refresh() per essere sicuri che gli oggetti in memoria siano gli stessi contenuti nel database. 31 Oltre ad essere decisa dall’utente la commit è effettuata in modo trasparente alla chiusura dell’object cotainer. 71 Capitolo 2 Nella modalità client/server offerta dagli sviluppatori di Db4o ci possono essere più transizioni concorrenti nello stesso momento. In questa situazione si rende necessario l’uso del concetto di isolazione. Con transizioni concorrenti c’è la possibilità che un utente provi a leggere o modificare gli oggetti del database durante il ciclo di vita di un’altra che sta operando sugli stessi oggetti. La strategia risolutiva va sotto il nome di livello di isolazione, tipica dei concetti introdotti nelle proprietà ACID. Con questa caratteristica il database implementa il livello di isolazione, bloccando gli oggetti in questione. 2.4.5 Proprietà ACID Il concetto di ACID 32 è stato una delle prime proprietà riconosciute nella teoria dei database. Dall’acronimo si intuisce che un database deve rispettare quattro principi. Atomicità: i dati vengono effettivamente salvati sulla base dati solo se una transizione avviene con successo. In caso contrario lo stato del database non può cambiare. Consistenza: un database si trova sempre in uno stato consistente. Ogni transizione lo porta da uno stato consistente ad un’altro. Isolazione: non è consentita la modifica di dati se una transizione non è completata. Ad esempio avendo due client che accedono al database operando in maniera concorrente, si opera in due diverse transizioni completamente isolate tra loro. I gradi di isolazione vengono divisi in quattro livelli: Grado zero: una transizione non può modificare i dati utilizzati da un’altra transizione. Grado uno: composto dal grado zero, ma non è possibile effettuare una commit di modifica fino alla fine della transizione. Grado due: composta dal primo grado, ma non è possibile leggere dati di altre transizioni. 32 ACID: Atomicity-Consistency-Isolation-Durability 72 Capitolo 2 Grado tre: grado due, ma non è possibile leggere dati di un’altra transizione prima che è stata effettuata una commit. Durabilità: concetto base nella teoria dei database, il quale afferma che i dati devono essere persistenti al suo interno. 2.4.6 Relazioni inverse Nella realizzazione di un progetto basato su una base di dati ad oggetti, si possono introdurre le relazioni inverse. Per capire bene il concetto, si propone un esempio basato sulle normali tecniche di programmazione. Supponiamo di avere una classe Persona che implementa una seconda classe Indirizzo. Come si può risalire alla Persona che abita nel particolare Indirizzo? In effetti, usando le tecniche di programmazione ad oggetti, questo non è possibile. Per effettuare la navigazione in entrambe le parti bisogna inserire un nuovo attributo nella classe Indirizzo, aggiungendo cosı̀ un OID verso la classe Persona. Questa corrispondenza circolare può essere utile nello sviluppo di applicazioni che utilizzano database ad oggetti, ma molte volte, non è altamente performante, quindi è lasciato allo sviluppatore l’incarico di scegliere se e come inserire le relazioni inverse. 2.4.7 Tipologie di relazioni ed Ereditarietà In ambito applicativo, vengono spesso messi in gioco oggetti composti, creando una struttura molto complessa da dover progettare. I vari tipi di relazioni si dividono in: relazioni uno-a-uno, uno-a-molti e molti-a-molti. Sicuramente la tipologia di relazione più semplice da implementare è quella unoa-uno. In questo caso è necessario dichiarare l’oggetto secondario all’interno di quello principale. Le relazioni uno-a-molti sono realizzate mediante l’inclusione di un ArrayList all’interno di una classe madre. Anche gli ArrayList sono trattati come oggetti; la lista ed ogni elemento al suo interno hanno un proprio OID. Nei modelli ad oggetti le relazioni molti-a-molti vengono gestite in maniera analoga al caso precedente con l’uso di ArrayList. In casi come questi potrebbe essere nec73 Capitolo 2 Figura 2.5: Esempio di relazione uno-a-molti, per le classi Impiegato e Manager, mediante un ArrayList. 74 Capitolo 2 essaria l’inclusione delle relazioni inverse, ma questa scelta resta sempre a discapito dello sviluppatore. Un’altra soluzione può essere applicata nel caso in cui le relazioni molti-a-molti debbano necessariamente includere metodi o attributi. In questi casi, in analogia con il modello relazionale, si crea una nuova classe intermedia, contenente metodi ed attributi che si desidera implementare. Un ultima nota è da dedicare all’ereditarietà 33 , la quale è un concetto chiave nella programmazione ad oggetti ed è difficile da implementare in un modello relazionale. Con db4o questa caratteristica è molto semplice da implementare, attraverso la chiave extends. Ancora una volta si fa riferimento alle OID, per tener traccia di oggetti ereditati o implementati. 2.4.8 Reference cache Per archiviare gli oggetti db4o fa uso una una reference-chace. Si tratta di una tabella che mappa gli oggetti all’interno della memoria mediante l’identificativo interno (id). Quando si vuole cercare un oggetto, il sistema guarda prima nella reference-cache, se l’oggetto non è presente viene caricato nel disco. Di default si fa uso di questa cache, ma questa funzione può essere anche disabilitata. È possibile inoltre rimuovere oggetti da essa manualmente, poichè in automatico vengono eliminati solo i riferimenti nulli. 2.4.9 Equivalence ed Equality Nella terminologia utilizzata negli oggetti ci sono due diversi concetti che prendono il nome di equivalenza ed uguaglianza. Equivalenza: due oggetti che hanno la stessa OID sono considerati equivalenti. 33 L’ereditarietà è uno dei concetti fondamentali nel paradigma di programmazione ad oggetti. Essa consiste in un legame che il linguaggio di programmazione, o il programmatore stesso, stabilisce tra due classi. Nello specifico si ha una classe (classe figlia o derivata) che estende metodi ed attributi di una classe padre (superclasse). 75 Capitolo 2 Figura 2.6: Esempio di relazione molti-a-molti, per le classi Impiegato e Progetto, mediante ArrayList e relazioni inverse. 76 Capitolo 2 Figura 2.7: Interazione tra la memoria ed i dati persistenti, mediante cache. Uguaglianza: se due oggetti hanno lo stesso stato vengono considerati uguali. Ad esempio se si effettua una query su oggetti di tipo persona si possono avere oggetti non equivalenti ma che si riferiscono allo stesso oggetto. 2.4.10 Concetto di identità e trasparent persistence Db4o usa il concetto di identità per identificare gli oggetti, in modo che, se si carica un oggetto in maniera e tempo diverso viene restituito sempre lo stesso. In maniera molto semplice, lo stesso oggetto nel database sarà sempre rappresentato dallo stesso oggetto in memoria. Per implementare questo meccanismo, ogni Object Container cerca di fare un mapping tra gli oggetti in memoria e quelli salvati. Quando si caricano gli stessi oggetti attraverso diversi Object Container, essi avranno una diversa entità in memoria. Solo nel caso in cui si utilizza lo stesso Object Container gli oggetti vengono rappresentati dalla medesima entità. Quando si modifica un oggetto vengono salvati solo i cambiamenti avvenuti nello stesso, modificando gli oggetti di livello inferiore, fino ad una certa profondità (impostata per default questa ad uno). Con questa caratteristica solo i cambiamenti 77 Capitolo 2 avvenuti nello specifico oggetto sono salvati, mentre le modifiche agli oggetti di livello inferiore sono scartate. Per ovviare al problema è possibile scrivere ogni oggetto individualmente (soluzione utilizzabile per sistemi semplici, ma improponibile per modelli complessi), oppure usare il concetto di trasparent-presistence. Mediante l’uso della trasparent persistence (TP) è possibile registrare un oggetto mediante l’uso del metodo store() e lasciare al database il compito di gestire modifiche future. Questa tecnica porta diversi benefici: • il codice non dipende dal grado di attivazione • solo gli oggetti di cui ci interessa visionare i campi sono caricati in memoria • nessuna modifica è persa. Per supportare la TP gli oggetti hanno bisogno di implementare l’interfaccia Activable. Per utilizzare la precedente ipotesi bisogna inserire un attributo di tipo Activator e due metodi dell’interfaccia Activable. Il primo di essi è in grado di legare un Activator allo specifico oggetto, mentre il secondo è chiamato prima di ogni operazione di read o write (nello specifico si deve inserire una chiamate all’interno dei metodi setxxx() e getxxx()). La precedente metodologia è molto ripetitiva e sottoposta ad errori; può essere del tutto automatizzata creando build-script oppure utilizzando file per la configurazione. Anche per eliminare un oggetto valgono gli stessi ragionamenti fatti per le modifiche. Utilizzando il metodo delete() solo l’oggetto passato al metodo viene eliminato. In casi come questo gli elementi che rimangono in vita hanno riferimento pari a null. 2.4.11 Concetto di attivazione Il concetto di attivazione, insieme al concetto di persistenza, è caratteristica fondamentale di db4o e dei ODBMS in generale. Ma perchè si rende necessario l’uso dell’attivazione? Dimostriamolo con un esempio. Nella figura sottostante ci troviamo di fronte ad una struttura ad albero tipica di Db4o. Esiste un solo nodo di root e molti sotto nodi. Nel momento in cui si recupera l’oggetto root da una query, tutti i sotto oggetti devono essere creati in 78 Capitolo 2 Figura 2.8: Esempio di recupero di un insieme di elementi, senza l’utilizzo dell’attivazione. memoria comportando un grande spreco di risorse se le strutture ad albero sono molto grandi. Fortunatamente db4o non segue questa strategia. Quando si recuperano oggetti da una query, non si recupera tutta la struttura ma solo la parte del grafo d’interesse. Questo avviene grazie al concetto di attivazione, il quale si verifica quando: • Si iterano i risultati di una query. • Gli oggetti sono attivati esplicitamente con i metodi di attivazione nell’Object Container. • I membri di una collezione sono attivati automaticamente quando la collezione è attivata. Questa funzione è stata impostata di default dagli sviluppatori ed stato scelto di attivare i nodi fino ad una profondità pari a quattro. L’attivazione può essere esplicita attraverso l’uso del metodo activate(Object , int ), dove il primo parametro è l’oggetto di riferimento, mentre il secondo è un intero che specifica il livello di attivazione voluto. Il valore minimo dell’intero è può essere 79 Capitolo 2 Figura 2.9: Esempio di recupero di un insieme di elementi, con l’uso dell’attivazione. impostato a 1, il quale ritorna solo oggetti a livello più alto con eventuali attributi primitivi interni alla classe. Come seconda opportunità è possibile disattivare oggetti precedentemente attivati. In questo caso si utilizza il metodo deactivate(object.get(), int), dove il secondo parametro ha lo stesso significato del caso precedente. Se lo si vuole è possibile anche disattivare l’attivazione settando il parametro numero due del metodo activate(Object, int) a NULL o default. In questo caso verrà caricato tutto il grafo. Per concludere è possibile utilizzare il concetto di attivazione in modo trasparente. Questa modalità è configurabile all’interno del database, elimina il problema dei livelli di attivazione e rende questa proprietà trasparente agli sviluppatori. 2.4.12 Configurazione Db4o offre molte interfacce per gestire le configurazioni inserite nel package com.db4o. config, nelle quali è possibile controllare le operazioni di Db4o ed il suo ObjectContainer. Tutte le configurazioni inserite vengono applicate a partire dal successivo ObjectContainer creato o alla successiva connessione ad un server Db4o e possono essere applicate a molti oggetti ocntenitori. 80 Capitolo 2 Figura 2.10: Esempio di configurazione, utilizzata per diversi ObjectContainer. Per inserire le configurazioni è prima necessario creare un oggetto di tipo Configuration nel seguente modo: Configuration conf = Db4o.Configure() successivamente è possibile effettuare chiamate ad uno dei seguenti metodi, dove si è supposto l’esistenza di una classe Persona, cui applicare determinate configurazioni: conf.ObjectClass(typeof(Person)).ObjectField(name).Indexed(true): che indica la volontà di creare un indice all’interno della classe Persona sull’attributo “name”. conf.CbjectClass(typeof(Person)).CascadeOnDelete(): utilizzato per le eliminazioni in cascata degli oggetti a partire da quello principale. L’uso di questa opzione può essere molto pericolosa nel caso in cui esistono riferimenti multipli all’interno del database. conf.CbjectClass(typeof(Person)).CascadeOnUpdate(): come al caso precedente, ma in questo caso si agisce sugli update delle classi. conf.GenerateUUIDs(Int32.MaxValue): indica la volontà di generare UUID. Questa configurazione implica l’uso di long object ID, i quali sono unici indipendentemente dalla macchina che li genera. Questi ID fanno diminuire le prestazioni del database e sono utilizzati nel DRS (si rimanda allo specifico paragrafo per ulteriori approfondimenti). 81 Capitolo 2 conf.GenerateVersionNumbers(Int32.MaxValue): in questo caso si salva un numero di versione per ogni oggetto. Utilizzando il parametro -1 si di disabilita questa opzione, con il valore Int32.MaxValue tutte le classi hanno un numero di versione, mentre usando 1 si setta il numero di versione solo per le classi indicate dallo sviluppatore. conf.AutomaticShutDown(false): questa configurazione impostata per default a true, indica la possibilità di chiusura automatica dell’ObjectContainer se il JDK termina o non ha riferimenti al garbadge collector. Inserendo un valore pari a false si forza lo sviluppatore ad inserire il metodo close() per chiudere il container. conf.LockDatabaseFile(false): metodo impostato a true per default per la gestione degli accessi concorrenti al database. Impostando il parametro a false nessuna operazione di look viene effettuata. conf.SingleThreadedClient(true): configurazione utilizzata nella modalità clientserver. Impostando il parametro a false la gestione dei client avviene su un singolo thread, mentre un parametro impostato a true abilita i client alla modalità multithreaded, la quale è molto performante per lo scambio di messaggi asincroni ma utilizza più risorse. conf.WeakReferences(false): metodo che indica l’utilizzo di una weak reference (introdotta i precedenza). Questo approccio consuma molta memoria poichè il garbadge collector non può pulire oggetti inutilizzati; è necessario effettuare una chiamata ad ObjectContainer.purge(object) per rimuovere tutti gli oggetti inutilizzati. I successivi esempi di configurazione influenzano il file del database. È possibile con essi cambiare la struttura, il contenuto del database e come nei casi precedenti è necessaria la creazione di un oggetto di tipo Configuration. conf.BlockSize(8): questo metodo imposta la grandezza dei blocchi del file relativo al database. Il valore può essere un numero da 1 a 127 ma deve sempre essere multiplo di 8. Il valore di default è 1 e con questo valore il database può 82 Capitolo 2 raggiungere i 2GB. Gli sviluppatori raccomandano un valore pari ad 8 in modo da raggiungere i 16GB di grandezza. conf.Encrypt(true): db4o può effettuare due algoritmi di crittografia. Il metodo più semplice imposta questo metodo con il parametro true e successivamente si inserisce una password. Questo sistema introduce un livello di sicurezza molto basso, utilizzabile se non ci sono particolari vincoli a riguardo. conf.Io(new XteaEncryptionFileAdapter(password)): questa configurazione permette agli sviluppatori di configurare il proprio Adapter I/O; è possibile crearlo o utilizzare uno fornito da Db4o (contenuti nel package com.db4o.io). conf.Unicode(false): db4o è in grado di supportare gli Unicode come formato di memorizzazione delle stringhe. Tra i metodi di configurazione ci sono anche opzioni applicabili per un singolo ObjectContainer o ObjectServer. In questo modo è possibile effettuare modifiche della configurazione per particolari ObjectContainer o ObjectServer, mantenendo inalterate le configurazioni globali. 2.4.13 Client-server mode Un sistema client-server consiste in genere in un numero non definito di client che interagiscono con un unico server. I server forniscono servizi attraverso deamon 34 che ascoltano ed iniziano delle connessioni con i client. Come già affermato, le API di db4o includono la funzione opzionale denominata client-server mode. Questa modalità richiede la creazione di un oggetto ObjectSercer, cui passare il nome del file da aprire e la porta da utilizzare. 34 Un demone (tradotto erroneamente dall’inglese, daemon) è un programma che agisce in background, cioè senza che sia sotto il controllo diretto dell’utente. In generale hanno la funzione di rispondere a determinate richieste che siano di rete, hardware, etc. La loro caratteristica fondamentale a differenza dei normali programmi è che i demoni sono normalmente in esecuzione per tutta una sessione di lavoro. Il motivo è che devono sempre essere in ascolto per soddisfare eventuali richieste provenienti dall’utente o dall’esterno. 83 Capitolo 2 Figura 2.11: Esempio di di comunicazione tra un server e diversi client. Db4o supporta tre diversi tipi di interazioni tra client e server. La prima modalità è definita networking mode e viene utilizzata quando si lavora con un database remoto. In questo caso i client aprono una connessione TCP/IP per eseguire istruzioni di inserimento, modifica, eliminazione o query da e verso il database. La seconda modalità è definita embedded mode e non coinvolge sistemi distribuiti. Anche in questo caso il client ed il server sono attori ben distinti, ma al contrario del caso precedente essi si trovano nella stessa virtual machine. Questa seconda modalità non è molto diversa dalla precedente, infatti è necessario passare l’intero 0 al metodo che apre il server (il quale indica che il server si trova in locale). L’ultima modalità è utilizzabile per comunicazioni out-of-band verso il server. In questo caso le informazioni passate non appartengono al protocollo db4o e non consistono in oggetti. È possibile inviare messaggi verso il server tipo: do a defragment, stop yourself, perform a savecopy. Mediante l’utilizzo di una delle tre modalità client/server è stato necessario introdurre un sistema di accesso controllato ai dati. È necessario infatti specificare username e password per effettuare il login con il server. Anche nelle modalità viste in questo paragrafo si utilizza una cache per l’accesso ai dati persistenti. In questo caso ogni client ha una cache di riferimento, la quale aiuta ad avere buoni tempi di risposta. La presenza della cache comporta tanti problemi quando diversi client lavorano con gli stessi oggetti, ma questi posso essere saltati utilizzando diverse strategie illustrate di seguito. • Se gli utenti non lavorano con gli stessi oggetti nello stesso momento non 84 Capitolo 2 Figura 2.12: Esempio di una struttura client-server, che racchiude tutti i casi prededentemente trattati. sussiste il problema. • Si può fare il refresh ogni volta che un client effettua una operazione di modifica o inserimento, il quale comporta un notevole scambio di dati. • Minimizzare la cache dei vecchi oggetti mediante l’uso di sessioni separate nel client. In questo modo si crea un nuovo container ed una cache pulita. Comunicazione SSL Per default db4o non effettua una comunicazione criptata, però è possibile la comunicazione SSL 35 . Per effettuare una comunicazione di questo tipo si deve semplicemente implementare il SSLSupport 36 . 35 Il Secure Sockets Layer(SSL) è uno dei protocolli crittografici che permettono una comunicazione sicura e una integrità dei dati su reti TCP/IP, come ad esempio Internet. SSL cifra la comunicazione dalla sorgente alla destinazione al di sopra del livello di trasporto. 36 Classe utilizzata per la creazione di una connessione SSL in ambito Db4o. Il costruttore di questa classe non necessita di nessun parametro. 85 Capitolo 2 2.4.14 DRS. Data Replication System Il concetto di Replication [13] è definito da wikipedia.com nel seguente modo: the provision of redundant resources (software or hardware components) to improve reliability and fault-tolerance. Nel nostro caso si parla di risorse software e più precisamente dei dati salvati nel database. Per effettuare la sincronizzazione si fa uso di un ObjectContainer e un peer 37 , rappresentato da un identificatore unico per tenere traccia dell’origine degli oggetti. Grazie alla natura dell’identificatore, non possono esservi due peer con lo stesso valore. È possibile scegliere di replicare tutti i dati (oppure alcuni) di un peer verso un altro 38 . Esso è spesso unidirezionale ma è possibile renderlo bidirezionale. Il DRS è stato disegnato per per avere un altro grado di integrazione tra dispositivi mobili e applicazioni web (o destokop). Casi d’uso sono ad esempio l’integrazione di apps commerciali sincronizzate con un database sito su di un server. Questo concetto potrebbe anche essere valido quando è impossibile aggiornare i dati di un database remoto ovunque; attraverso il DRS è possibile farlo in un secondo momento. Per utilizzare questo sistema di replicazione è necessario assegnare un UUID 39 ad ogni oggetto e un numero di versione. Queste ipotesi non sono verificate da db4o come default, quindi bisogna configurarle esplicitamente. 37 Con il termine peer si intende dire che ogni nodo può fungere sia da cliente che da servente. Questa terminologia è molto utilizzata in sistemi P2P (peer to peer), dove esisto un numero indefinito di nodi con le precedenti caratteristiche. 38 Operando su dispositivi mobili le risorse sono limitate protrerrebbe essere sbagliato replicare l’intero database. In questi casi è possibile replicare solo alcune parti scelte mediante una semplice condizione o con una query più complessa. 39 UUID: Unique universal identifiers. Il formato degli UUID è stato standatdizzato dalla Open Software Foundation. Un UUID è un numero a 128 bit, il quale contiene una firma del database generata da un algoritmo che contiene il nome dell’host, l’indirizzo, il timestamp corrente e due valori randoom di tipo long. Gli UUID sino salvati come istanze di classe Db4oUUID, i quali contengono una parte per la firma ed una parte contenente l’identificatore all’oggetto. Oltre i precedenti è necessario l’utilizzo di un numero di versione del database. 86 Capitolo 2 Figura 2.13: Esempio di Data Replication System, in un possibile contesto reale. 87 Capitolo 2 Il DRS può essere utilizzato attraverso la sincronizazione tra db4o-to-db4o, db4oto-Hibernate/RDBMS, db4o-to-VOD ed Hibernate/RDBMS-to-Hibernate/RDBMS. Esso non è implementato all’interno di Db4o, quindi è necessario includere delle specifiche librerie rilasciate dagli stessi sviluppatori Versant. 2.4.15 Db4o ed SQL (SQLite) Nella letteratura spesso si associa al termine di database alla struttura tipica dei RDBMS formata da tabelle. Questo è dovuto al fatto che il 90% del mercato è governato da modelli relazionali. Negli ultimi anni però, con l’aumentare del successo dei linguaggi di programmazione ad oggetti, è stato necessario fornire un’alternativa ai database relazionali. Db4o ed SQLite sono ambedue embedded database utilizzabili durante l’esecuzione di un’applicazione. Essi rimuovono il layer associato ad una configurazione client-server, però Db4o supporta la modalità client-server e la crittografia (che può essere utilizzata in tutte le situazioni). Con database Versant ogni azione è associata ad una transizione. Una transizione parte quando il database viene aperto e termina alla sua chiusura. Le transizioni sono implicite quando viene effettuata la chiamata ai metodi commit() e close(). Con SQLite invece, la funzione di autocommit è utilizzata di default ed una transizione parte utilizzando comandi SQL. Mediante l’utilizzo di database relazionali si devono scrivere righe di codice per la creazione del database, delle tabelle e dei suoi campi. In Db4o questo non è richiesto; non sono richiesti codici per la creazione e mantenimento del database. Aprire o chiudere la connessione è molto semplice in entrambi i casi. Il vero vantaggio di db4o sta nell’inserimento dei dati nel database. Usando un RDBMS bisogna inserire i vari elementi nelle diverse tabelle, mentre con un ODBMS bisogna effettuare una sola chiamata al metodo di store contenente l’oggetto di riferimento, quelli in relazione e collezioni di essi. Altra considerazione importante avviene quando si cerca di effettuare delle query. Naturalmente utilizzando un database relazionale si è costretti a ricostruire degli oggetti a partire dal risultato della query. Questo implica un notevole lavoro per lo sviluppatore che deve necessariamente creare dei metodi adatti a farlo. 88 Capitolo 2 Db4o supporta metodi per effettuare il backup del database, mentre non è possibile in SQLite. Siccome il database è contenuto in un unico file, in questo caso, per effettuare un backup, si deve necessariamente copiare a mano il file in questione. 2.4.16 Performance Le performance sono un fattore chiave per i database; anche per Db4o non ci sono eccezioni. È necessario effettuare dei test con varie combinazioni hardware ed applicazioni. Tutto questo è già stato implementato in un benchmarks 40 scritto in Java, denominato PolePosition [14]. Attraverso questo progetto open-source si evincono i pro ed i contro dei database ad oggetti ed in particolare su Db4o. Questo benchmarks racchiude una serie di test e risultati in una grande varietà di database che vanno dai RDBMS ai ODBMS. Nella tabella sottostante si mostrano i risultati che illustrano i casi in cui Db4o opera molto bene. I test possono essere raccolti nei seguenti casi: • Scrivere un volume lineare: semplice esempio in cui scrivono 10,000 oggetti di un solo tipo, senza dipendenze. • Leggere, scrivere ed eliminare un albero: in questo test si eseguono delle semplici query, all’interno di un grafo composto da otto livelli e da semplici oggetti. • Query ed ereditarietà: in questo test si effettuano delle normali operazioni in una serie di grafi a 5 livelli; ogni albero contiene cinque oggetti, dove il successivo deriva da quello precedente. • Query indicizzata: in questo caso si mostra quanto può essere veloce una query verso un singolo oggetto, usando un indice su di un campo intero. • Recuperare un oggetto dall’ID: in contrasto con quello che avviene nel caso precedente, si effettua una query attraverso l’ID. 40 Con il termine benchmark si può intendere un insieme di test software volti a fornire una misura delle prestazioni di un computer o la determinazione della capacità di un software di svolgere più o meno velocemente un particolare compito per cui è stato progettato. 89 Capitolo 2 Figura 2.14: Tabella che mostra i casi in cui, Db4o è altamente performante rispetto agli altri sistemi. Figura 2.15: Tabella che mostra i casi in cui, i database sono altamente performanti. Dai risultati ottenuti si verifica ciò che è facile attendersi; nel caso in cui si ha il bisogno di creare una struttura molto complessa, Db4o è altamente performante rispetto ad altri DBMS. Contrariamente, se si devono effettuare delle semplici query, l’SQL e quindi i RDBMS sono più adatti. Questa caratteristica è rappresentata nella seguente tabella, la quale riassume i seguenti casi: • Query per le string: query attraverso un valore di tipo string, su 1,000 oggetti. • Delete: eliminazione di 1,000 oggetti a partire dall’ID. I precedenti test utilizzano db4o nella modalità locale e si sono utilizzate le SODA query. Riscrivendo il codice utilizzando le native queries si riscontra un aumento della latenza del 10%. Questo dimostra anche che le SODA queries hanno maggiori performance. 90 Capitolo 2 2.5 Perst Come Db4o, Perst 41 è un database ad oggetti embedded, open-source a doppia licenza 42 . Esso è disponibile per due linguaggi di programmazione: Java e C# (per applicazioni .NET) e fa uso degli OID per creare riferimenti tra gli oggetti. La distribuzione è anche disponibile in una versione Lite per applicazioni Java che girano su telefoni cellulari, PDAs ed altri dispositivi basati su Java ME. Questa è una versione semplificata nella quale sono state eliminate diverse funzioni basilari come l’accesso da parte di più client o il look di file. Questa seconda versione occupa circa il 30% di spazio in meno rispetto quella completa. 2.5.1 Salvataggio degli oggetti Con Perst è possibile il salvataggio degli oggetti utilizzando un istanza della classe Storage, ottenuta a sua volta con i metodi della classe StorageFactory. Per ottenere i riferimenti di tutti gli elementi creati si utilizza un oggetto chiamato root. Questo è il solo elemento persistente sul quale si accede in modo speciale, mentre gli accessi agli altri avvengono attraversando gli oggetti. Il sistema può avere solo un oggetto root e si utilizzano i metodi Storage.getRoot o Storage.setRoot per ottenere il riferimento o creare una istanza. L’uso della persistenza avviene con la classe Persistent. Per salvare un nuovo elemento è necessario recuperare l’oggetto root ed in un secondo momento possono essere salvati nuovi elementi nel database attraverso il metodo store. Per il salvataggio degli elementi è possibile anche utilizzare una tecnica che prende il nome di persistenza per raggiungibilità. Per diventare persistente un oggetto deve essere referenziato da qualche altro elemento persistente. L’unica eccezione 41 Perst è stato creato da Konstantin Knizhnik nel 2003. Nel 2006 Perst è stato trasferito a McObject, industria for-profit, sita a Washinton (Stati Uniti). Prima della versione 4.0 le API seguivano JDK 1.4, mentre a partire da questa si utilizza JDK 1.5 e superiore. 42 Gli utenti possono reperire e modificare Perst sotto i termini della GNU (General Public Licence) come pubblicato dalla Free Software Foundation. Per singoli o organizzazioni che non possono o non vogliono rispettare i termini della GPL è disponibile una licenza commerciale. 91 Capitolo 2 è data all’oggetto principale che diventa persistente quando viene registrato come radice di archiviazione. Tutti gli oggetti all’interno della base dati possono essere esplicitamente eliminati utilizzando il metodo deallocate, o implicitamente alla chiamata dal garbage collector Perst, il quale de-alloca tutti gli oggetti persistenti che non sono raggiungibili dal root. 2.5.2 Relazioni tra oggetti Come per gli altri DBMS, anche in Perst si possono creare relazioni tra gli oggetti nella base di dati. La tipologia più semplice da implementare è sicuramente quella uno-a-uno. In questa situazione basta inserire il riferimento di una classe A in una seconda classe B. Il discorso è leggermente più complesso in relazioni uno-a-molti e molti-a-molti. In queste situazioni bisogna inserire collezioni di elementi all’interno degli oggetti, le quali possono essere racchiuse in: Standard Java Array. Standard Java Collection. Perst salva queste collezioni all’interno di oggetti persistenti per rendere possibile il loro utilizzo. Perst Link. Analoghe ad un ArrayList Java, ma a differenza di esse, si caricano gli elementi di un Link solo quando viene effettuata una richiesta su di essi. Come per gli Array, un Link non è un oggetto separato ma è inserito in quello contenitore. Perst Relation. L’unica differenza tra essi e i Link è che essi sono salvati come oggetti separati nel database. Perst list. Implementazione di una lista persistente basata su un B-Tree in cui è possibile l’accesso mediante gli indici. Perst set. Anche essi sono implementati usando un B-Tree, ma in essi non si può avere accesso mediante gli indici. 92 Capitolo 2 2.5.3 Attivare gli oggetti Questo sistema offre due diverse soluzioni per attivare gli oggetti. Esse sono: Ricorsione Esplicita: si lascia al programmatore il compito di caricare esplicitamente tutti glioggetti. Ricorsione Implicita: carica in modo ricorsivo tutti gli oggetti di riferimento. In questa situazione invocando l’oggetto root viene caricata tutta la base dati, il che è indesiderabile, specialmente lavorando con database di grandi dimensioni. 2.5.4 Cercare Oggetti Il meccanismo di caricamento degli oggetti in Perst ha dei vincoli imposti per consentire alle query di essere molto preformanti. Gli oggetti vengono salvati in collezioni implementati come B-Tree; inoltre i programmatori sono costretti ad inserire gli indici per il salvataggio degli oggetti. I B-Tree sono costituiti da pagine di grandi dimensioni consententi coppie key, value, dove key è una chiave del valore e cioè un indice. Gli indici possono essere di cinque tipologie, le quali sono racchiuse nelle seguenti righe. Indici semplici: indice semplice creato utilizzando il metodo createIndex della classe di Index. Indici e chiavi: nella maggior parte dei casi, una chiave viene memorizzata in uno dei campi dell’oggetto. Per effettuare questa operazione viene fornito il metodo createFieldIndex nel quale si specifica il nome del campo chiave. In questa tipologia, se la chiave deve essere aggiornata, l’oggetto deve prima essere cancellato a partire dall’indice e poi reinserito, effettuando l’aggiornamento con il nuovo valore di chiave. Indici spaziali: l’uso diffuso di dispositivi GPS e la popolarità di servizi come Google Maps aumenta in modo significativo il numero di applicazioni che lavorano con dati di tipo geografico. Perst consente l’uso di due metodi per la reazione di indici spaziali. 93 Capitolo 2 Indice composto: il database supporta un indice composto da una chiave di diversi valori. Indice multidimensionale: un indice multidimensionale può essere utilizzato nelle ricerche dove ci sono un numero maggiore di uno di criteri da utilizzare. Ad esempio, un indice su lastName,firstName consente la ricerca di persone specificando nome e cognome, solo cognome, ma non utilizzando solo il nome. 2.5.5 Transizioni Ogni operazione effettuata con Perst avviene all’interno di una transizione, in modo da salvare e modificare gli oggetti solo al termine di essa. Questo avviene automaticamente portando il database da uno stato consistente ad un altro. La principale pecca di questo approccio si ha quando di vuole supportare un accesso concorrente dove diversi utenti vogliono lavorare con gli stessi oggetti nello stesso momento (questa situazione è comunque risolvibile). 2.5.6 Relational Database Wrappers Mediante il wrapper si provvede ad una serie di API che rendono simile l’uso del database ad un modello relazionale. La classe utilizzata per questo approccio, di nome Database, provvede alle seguenti funzioni: • Non necessita di un oggetto root. • Mantiene gli indici creati dal programmatore o dal sistema • Usa il blocco delle tabelle. • Provvede ad un linguaggio per effettuare le query denominato JSQL. JSQL È possibile considerare JSQL come un parente di SQL in cui non è necessario specificare le clausole SELECT, FROM e WHERE, poichè si lavora con gli oggetti e non con tuple e tabelle. 94 Capitolo 2 Per effettuare ricerche con JSQL bisogna utilizzare la classe Query e per la sua esecuzione è necessario definire un iteratore (Iterator ) ed una stringa rappresentante le condizioni. Questo nuovo lunguaggio provvede ad un meccanismo per localizzare velocemente gli oggetti attraverso una chiave primaria. In questo caso si ha l’analogia degli indici utilizzati in un database di tipo relazionale. 2.5.7 Repliation System Perst supporta un sistema di tipo master-slave in cui può esistere un solo nodo master su cui aggiornare il database e più nodi di slave che effettuano richieste di lettura al master. In questa architettura, le operazioni possono essere effettuate in modo asincrono senza attendere conferma dalla replica, o sincrono, attendendo conferma dalla replica prima di effettuare la transazione. Il sistema fornisce un’altra opzione di replica detta statica quando il numero di replicanti è fissato al momento dell’apertura del master, o dinamica, in cui nuovi slave possono collegarsi al nodo master. 2.5.8 Perst vs Db4o La differenza che viene subito alla mente tra i due sistemi è sicuramente il diverso grado di trasparenza offerto. Le operazioni in Db4o sono molto intuitive, mentre con Perst tutte le operazioni e il database derivano da una classe. Le precedenti affermazioni si trasformano in due note: • Il codice è più fluido senza l’utilizzo di classi. • Senza questo livello di trasparenza è più facile incorrere in errori. In Db4o sono concesse un numero molto grande di opzioni configurabili, varie tipologie di connessioni client-server ed un sistema di replicazione bidirezionale. Dei precedenti punti, Perst fa uso di un numero esiguo di configurazioni, gestisce un sistema di replicazione master-slave e non è in grado di stabilire una connessione client-server se non con il sistema di replicazione. 95 Capitolo 2 Nota di merito a Perst è dettata dalle sue prestazioni. Come Db4o è altamente performante su un numero di oggetti molto grande contenuti nella base di dati. In questa situazione dai test effettuati su benchmark, si evince che Perst effettua operazioni di inserimento, modifica ed eliminazione in tempi (in alcuni casi secondi) minori rispetto Db4o. 96 Capitolo 3 Realizzare un’applicazione Android-Db4o 3.1 Introduzione Per creare applicazioni in Android e Db4o, prima di ogni cosa è necessario scaricare gli strumenti adatti. Si parla in questo caso dell’SDK e dei pacchetti .jar relativi al database, entrambi scaricabili nella sezione sviluppatori, all’interno delle rispettive pagine web ufficiali. É strettamente consigliabile l’uso dell’IDE Eclipse sul quale viene fornoto l’OME 1 plug-in di Db4o, ed i riferimenti all’SDK per effettuare operazioni come la creazione di un nuovo AVD utilizzando l’interfaccia grafica. Avendo effettuato le precedenti operazioni è possibbile creare ogni tipo di applicazione, ed in questo caso una in particolare, alla quale saranno dedicate le prossime sezioni. 3.2 Il problema L’applicazione che è stata implementata nella tesi prevede la gestione di un campionario di merci da parte di un venditore e/o rappresentante, il quale ha il com1 OME: Object Manager Enterprise è il plugin messo a disposizione dalla Versant per la visione degli oggetti contenuti nel database e per effettuare semplici query su di esso. 97 Capitolo 3 pito di rappresentare prodotti mediante APPUNTAMENTI ed effettuare eventuali ORDINI da consegnare ai CLIENTI. L’applicazione prevede l’inserimento di PRODOTTI da parte del venditore, ognuno dei quali è caratterizzato da una categoria (alimenti, elettronica ecc.), da una o più immagini. Ogni prodotto può essere venduto singolarmente o a pacchi e nel secondo caso sarà necessario specificare quanti elementi sono presenti all’interno di ogni singolo pacco. Ogni venditore è in grado di inserire APPUNTAMENTI che si avranno con i clienti, i quali sono caratterizzati da un indirizzo. Un cliente ha un indirizzo di riferimento, contatti (telefono, cellulare, e-mail) e può avere zero o più ordini (con eventuali SCONTI ), i quali sono composti da una lista di prodotti scelti (con relativa quantità), una DATA, una modalità di pagamento, uno stato ed un costo. 3.3 Casi d’uso In UML, gli Use Case Diagram o diagrammi dei casi d’uso possono considerarsi come lo strumento di rappresentazione dei requisiti funzionali di un sistema. In sostanza spiegano in che modo è possibile interagire con il sistema ma non come queste operazioni avvengano. Ogni diagramma è caratterizzato da diversi elementi che possono relazionarsi tra loro. Tra gli elementi principali si citano: System rappresenta il contenitore degli elementi delle caratteristiche del sistema. Questi sono rappresentati graficamente mediante un rettangolo vuoto all’interno dei quali vengono rappresentate le entità interne, mentre le entità esterne (appartenenti al dominio o al contesto del sistema) sono posizionate all’esterno. Actor un attore è una classe con stereotipo actor rappresentante un ruolo coperto da un certo insieme di entità che interagiscono con il sistema, quali utenti umani, sistemi software o dispositivi hardware. Gli attori sono rappresentati graficamente da un’icona che rappresenta un uomo stilizzato. 98 Capitolo 3 Use Case un caso d’uso è rappresentato graficamente come un’ellisse contenente il suo nome. Si può intendere questo strumento come una classe di comportamenti correlati agli Attori. Le associazioni che incorrono nel sistema possono essere dei seguenti tipi: Actor e use case questo tipo di associazione congiunge gli attori con i casi d’uso a cui essi partecipano. Ogni attore può essere associato a un qualsiasi numero di casi d’uso ed essi implicano uno scambio messaggi. Generalizzazione In questa situazione, un “sottoattore” è in grado di partecipare a tutti i casi d’uso a cui partecipa il “superattore”, ed eventualmente può partecipare a use case aggiuntivi. Inclusione la relazione di inclusione indica che la funzione rappresentata da uno dei due use case include completamente la seconda. Le inclusioni vengono rappresentate graficamente da una linea tratteggiata con indicazione dello stereotipo include. Estensione indica che la funzione rappresentata dallo use case “estendente” può essere impiegata della funzione “estesa”. Questa situazione è rappresentata da una linea tratteggiata con indicazione dello stereotipo extend. Il diagramma dei casi d’uso può essere molto utile per la realizzazione della nostra applicazione BAgent. Il successio paragrafo è dedicato allo scopo. 3.3.1 Casi d’uso Capionario I casi d’uso relativi all’applicazione BAgent possono essere raccolti nella figura 3.1. Nello specifico, esiste un solo Attore che opera nell’applicazione: Venditore il quale interagisce con BAgent fornendo un insieme di dati riguardanti ordini, prodotti, appuntamenti e clienti, i quali verranno inseriti all’interno del database ad oggetti. Il venditore ha a disposizione anche una serie di opzioni, ad esempio per la gestione dell’accesso all’applicazione mediante una password. 99 Capitolo 3 ud: Campionario Gestione Clienti Gestione Ordini Gestione Prodotti Gestione Categorie Venditore Gestione Tipologie Prodotti Figura 3.1: Diagramma dei casi d’uso, dell’applicazione BAgent. 100 Capitolo 3 I casi d’uso invece, sono i seguenti: Gestione Prodotti raccoglie elementi di inserimento, modifica ed eliminazione dei prodotti. Ogni prodotto può essere inserito all’interno degli ordini ed ogniuno di essi è caratterizzato da una propria categoria e da una o più immagini scattate direttamente dalla fotocamera. Un venditore può anche effetturare ricerche a seconda della tipologia o del nome del prodotto. Gestione Ordini anche in esso sono racchiuse le operazioni di inserimento, modifica ed eliminazione. Un ordine può anche essere caratterizzato da uno sconto associato e da uno stato (Attesa, Pagato, Bloccato). Per gli ordini è consentita la ricerca per stato, data di creazione o cliente. Gestione Appuntamenti al suo interno sono racchiuse le solite operazioni di inserimento, modifica ed eliminazione, inoltre è consentito l’eliminazione degli appuntamenti passati e la ricerca per data o cliente. Gestione Clienti incluso in esso vi sono le opersazioni di inserimento, modifica ed eliminazione, oltre alle ricerche per cognome o codice fiscale. Per ogni cliente è posibbile effettuare chiamate, inviare SMS e gestire l’importazione dei contatti da e verso la rubrica del telefono. Gestione Tipologie da associare ad un prodotto mediante un nome l’iva ed una lista di attributi specifici. È possibile creare, modificare o eliminare ogni tipologia. 3.4 Architettura logica La creazione dei progetti in Android richiede delle specifiche imposte dagli stessi progettisi. Ogni file deve essere inserito in un determinato folder per evitare la generazione di errori. Ad esempio, i file XML per il layout vanno sotto la cartella /res/layout, le risorse nella cartella /res/values, mentre le classi che specificano le Activity vanno inserite all’interno di opportuni package. 101 Capitolo 3 Questa architettura introduce una netta separazione tra la logica di esecuzione e la logica di presentazione delle applicazioni. Per questo motivo si è scelto di marcare questa scelta anche all’interno delle classi Java mediante l’utilizzo del pattern MVC. 3.4.1 MVC ed organizzazione dei package L’MVC (Model View Controller) è un pattern 2 architetturale molto diffuso nello sviluppo di interfacce grafiche di sistemi software object-oriented. Il pattern è basato sulla separazione dei compiti in tre ruoli principali: • il model, che fornisce i metodi per accedere ai dati dell’applicazione. • le view, che visualizzano i dati contenuti nel model e si occupano della presentazione dei dati. • il controller, che riceve i comandi dell’utente, i quali si ripercuotono nel sistema con la modifica degli altri due componenti. I dettagli delle interazioni fra le precedenti entità dipendono molto dalle tecnologie usate (linguaggio di programmazione, eventuali librerie ecc.) e dal tipo di applicazione (applicazione web, desktop ecc.). Il core dell’applicazione viene implementato dal Model che incapsula lo stato e definisce i dati e le operazioni che possono essere eseguite su questi. Esso quindi definisce le regole di interazione con i dati, esponendo alla View ed al Controller le funzionalità per l’accesso e l’aggiornamento. La logica di presentazione dei dati viene gestita dalla View. Ciò implica che questa deve fondamentalmente gestire la costruzione dell’interfaccia grafica (GUI) che rappresenta il mezzo mediante il quale gli utenti interagiranno con il sistema. Ogni GUI può essere costituita da schermate diverse, le quali svolgono diverse funzioni all’interno dell’applicazione. L’ultima generalizzazione è composta dal Control, il quale ha la responsabilità di trasformare le interazioni dell’utente della View, in azioni eseguite dal Model. 2 Nell’ingegneria del software un pattern può essere definito come una soluzione progettuale generale ad un problema ricorrente che si presenta in diverse situazioni durante la progettazione e lo sviluppo del software. 102 Capitolo 3 Queste classi realizzano la mappatura tra input dell’utente e processi eseguiti dal Model e selezionano le schermate (View ) richieste, quindi si può affermare che il Controller implementa la logica di controllo dell’applicazione. BAgent: Architettura in package Nell’applicazione BAgent è stato introdotto il pattern MVC, il quale implica una diversa organizzazione in package a seconda del contesto. I package possono essere racchiusi nei seguenti punti: com.android.campionario.Controller utilizzato per la gestione delle classi Controller. Le classi incluse in questo folder iniziano con la lettera C. com.android.campionario.View per la gestione delle View, dove le classi incluse, iniziano con la lettera V. com.android.campionario.Entity in questo package sono descritti gli oggetti specifici del dominio in questione. Ogni classe inizia con la lettera E ed è in grado di gestire oggetti in memoria centrale, i quali in questo caso sono anche persistenti, cioè recuperati dal database Db4o. com.android.campionario.Foundation le classi Foundation conoscono l’implementazione a livello fisico dei dati ed introducono dei metodi per accedere al database Db4o. Tutte le sottoclassi ereditano una superclasse Foundation, la quale contiene metodi generici per l’accesso al database utilizzati da ogni sottoclasse. Utilizzando un database relazionale ogni Entity si tracuce in una classe Foundation, per via della natura in tabelle del database. Utilizzando Db4o, si può ricorrere in un approccio meno dispensioso, perchè operazioni di inserimento, modifica ed eliminazione possono essere effettuate sia sugli oggetti pricipali, sia in quelli inclusi in essi. Questa affermazione può essere tradotta in un numero di classi minori rispetto le Entity. Insieme alle precedenti classi e package si possono essere anche citate le classi Boundary che realizzano la GUI, cioè l’interfaccia dell’applicazione. 103 Capitolo 3 3.4.2 Pattern Singleton Il Singleton è un design pattern con lo scopo di garantire che ad ogni classe venga creata una ed una sola istanza, oltre a fornire un punto di accesso globale ad essa. Questo pattern è utilizzato per implementare il concetto matematico “singleton” 3 , restringendo l’instanziazione delle classi verso un solo oggetto. L’implementazione più semplice di questo pattern prevede una classe singleton con un unico costruttore privato, in modo da impedire l’istanziazione diretta. La classe fornisce inoltre un metodo “getter” statico che memorizza il riferimento in un attributo privato. BAgent: Singleton Il concetto esposto in precedenza può essere esteso in applicazioni che lavorano con le classi. Nel caso dell’applicazione da creare viene utilizzato sia il linguaggio di programmazione Java e sia un database ad oggetti. Il database mantiene al suo interno i rifermimenti tra gli oggetti in memoria e quelli all’interno del database. In operazioni di modifica o eliminazione, si deve essere certi di operare con il medesimo oggetto e per farlo è stato necessario introdurre questo pattern nell’applicazione mediante la classe SingletonPatternBridge espressa nelle seguenti righe di codice. public c l a s s SingletonParametersBridge { private s t a t i c SingletonParametersBridge instance = null ; p r i v a t e HashMap<S t r i n g , Object> map ; public s t a t i c SingletonParametersBridge getInstance () { i f ( i n s t a n c e == n u l l ) i n s t a n c e = new S i n g l e t o n P a r a m e t e r s B r i d g e ( ) ; return instance ; } 3 In matematica un singoletto (o singleton) è un insieme contenente esattamente un unico elemento. Ad esempio un insieme è un singoletto se e solo se la sua cardinalità è 1 104 Capitolo 3 private SingletonParametersBridge () { map = new HashMap<S t r i n g , Object > ( ) ; } p u b l i c b o o l e a n f i n d P a r a m e t e r ( S t r i n g key ){ r e t u r n map . c o n t a i n s K e y ( key ) ; } p u b l i c v o i d addParameter ( S t r i n g key , Object v a l u e ) { map . put ( key , v a l u e ) ; } p u b l i c Object getParameter ( S t r i n g key ) { r e t u r n map . g e t ( key ) ; } p u b l i c v o i d removeParameter ( S t r i n g key ) { map . remove ( key ) ; } } nel quale si è creato un attributo map di tipo HashMap, dove il primo parametro è una stringa identificativa, mentre il secondo un oggetto. I metodi findParameter, addParameter e removeParameter, sono utilizzati rispettivamente per cercare una certa chiave per aggiungere un novo oggetto o per la rimozione di esso. 3.5 Modello di dominio Un modello di dominio (o domain model) descrive le varie entità che fanno parte di un sistema con le loro relazioni. Questo approccio è utilizzatto nell’ingegneria del software ed è in grado di descrivre un modello concettuale del dominio di interesse, il quale è in grado di descrivere entità, attributi, ruoli e relazioni. 105 Capitolo 3 Il vantaggio di questo approccio è senza dubbio la grande qualità nella descrizione di un problema e anche molto efficiente, per la coordinazione dei compiti da eseguire in un team di elementi. Nel caso di BAgent si possono descrivere le entità nel seguente modo: Entità Attributi Cliente indirizzo principale: Indirizzo; altri indirizzi: ArrayList<Indirizzo>; nome: String; cognome: String; contatti: Contatti; codice fiscale: String; partita iva: String; data attivazione: Data. Contatti telefono: String; cellulare: String; email: String; fax: String. Idirizzo via: String; numero civico: String; CAP: String; comune: String; provincia: String; regione: String. Appuntamento data: Data; ora: Ora; cliente: Cliente; Indirizzo: Indirizzo. Prodotto nome: String; categoria: Tipologia; opzioniTipologia: String[]; pacco: boolean; numeroElementi: int: costo: Double; descrizione: String. Immagine path: String; nome immagine: String; estenzione: String. Ordine data: Data; cliente: costo totale: Double; Cliente; stato: String; pagamento: String; elementi:ArrayList<Elementi>. Elemento quantità: int; prodotto: Prodotto. Sconto codice: String; casuale: String; sconto: double. Tipologia nome: String; IVA: double; opzioni: ArrayList<OpzioneTipologia>. OpzioneTipologia nome: String; campo obbligatorio: boolean. Data giorno: int; mese: int; anno:int. Ora ora: int; minuti: int. Come si intuisce dalla tabella, gli attributi possono essere tipi semplici, oggetti, 106 Capitolo 3 o collezioni di essi. A partire da essi si possono anche ricavare le relazioni che si vengono a creare all’interno dell’applicazione: l’inclusione di un singolo oggetto può indicare una relazione 1..1, mentre un ArrayList di elementi, una cardinalità pari ad N. Nella figura sottostante è riportato il modello di dominio relativo all’applicazione. EProdotto EElemento ETipologia integra aggiunge riferito a inserisce EOrdine −data EOpzioneTipologia EImmagine EData usa si riferisce a aggiunge implementa EOpzioneTipologiaProdotto riferito a EOra ESconto EAppuntamento implementa EIndirizzo riferito a riferito a EContatti aggiunge EOpzioni ha ECliente Figura 3.2: Modello di dominio dell’applicazione BAgent. 107 Capitolo 4 Analisi dei casi d’uso e SSD In questo capito vengono descritti gli SSD (System Sequence Diagram). Si crerà un diagramma di sequenza per l’applicazione BAgent e a partire da esso si descriverà il codice Java per la gestione degli utenti. 4.1 System Sequence Diagram Un Sequence Diagram è un diagramma utilizzato per descrivere uno scenario, il quale rappresenta una sequenza di azioni predefinite. In genere è possibile creare più diagrammi delle sequenze per una sola applicazione, ognuno dei quali è in grado di descrivere un determinato caso d’uso. Il Sequence Diagram descrive le relazioni che intercorrono tra gli Attori che si vogliono rappresentare. Il messaggio è sincrono se l’emittente rimane in attesa di una risposta, o asincrono, nel caso in cui l’emittente non aspetta nessuna risposta ma può continuare con altre operazioni. Un messaggio generato in risposta ad uno precedente è detto messaggio di risposta, mentre un messaggio in cui il ricevente è nello stesso tempo l’emittente è detto ricorsivo. Un diagramma di sequenza può essere letto in due modi; in orizzontale e in verticale. • in verticale (asse temporale) si mostra l’istante di tempo in cui un oggetto viene chimato, muore o effettua una chiamata; 108 Capitolo 4 • in orizzontale si mostrano gli oggetti e le operazioni che compiono. Gli SSD vengono rappresentati graficamente nel seguente modo: istanze di classi Rappresentate da rettangoli con il nome della classe e l’identificatore dell’oggetto, oppure semplicemente con un nome che contraddistingue un’istanza della classe. Attori Sono riportati sulla sinistra con frecce di interazione verso oggetti del sistema; possono non essere riportati nel caso in cui lo scenario viene avviato a sua volta in un’altro contesto. Messaggi Rappresentati mediante frecce tra un attore ed un oggetto o fra due oggetti. I messaggi di ritorno (se riportati) hanno una linea tratteggiata, quelli sincroni terminano con una freccia triangolare piena, mentre quelli asincroni terminano con una freccia semplice. L’ordine dei messaggi (dall’alto verso il basso) indica la sequenza con il quale essi vengono scambiati. 4.2 Il nostro caso d’uso: Aggiungere un Cliente Per capire le interazioni che avvengono tra il database Db4o ed Android, si fornisce la struttura per la creazione del caso d’uso “aggiungi cliente”. Il modo migliore per descrivere le operazioni che avvengono in questa situazione è senza dubbio la creazione del diagramma di sequenza. Le interazioni di un utente con il sistema possono racchiudersi in: • Aggiunta dettagli Cliente. • Aggiunta Indirizzo Principale. • Aggiunta Recapiti Cliente. Nella fugura 4.1 è stato descritto l’SSD del nostro caso d’uso. Come si vede avvengono diverse interazioni tra l’attore e il sistema, le quali saranno descritte nel dettaglio nelle prossime sezioni. 109 Capitolo 4 sd: Campionario Attore : Controller : View : Foundation : . Aggiungi Indirizzo principale . Aggiungi Indirizzi secondari . Aggiungi Recapito 1) . Aggiungi Cliente 2) . Recupera Elementi 2) Restituisci Elementi . controllaValori . store 1) Verifica Aggiunta Figura 4.1: Diagramma di sequenza per il caso d’uso: Aggiungi Cliente. 4.2.1 Aggiungere un Indirizzo Per aggiungere un indirizzo è possibile creare un nuovo diagramma di stato nel quale si evincono le relazioni tra l’attore principale ed Android. Nello specifico è possibile verificare le operazioni di recupero degli elementi relativi dai contatti telefonici. Codice relativo Per ogni indirizzo è possibile recuperare gli elementi direttamente dalla rubrica, visualizzando la lista dei contatti mediante una Activity Built-in. Le righe di codice per richamare l’attività descritta in precedenza sono le seguenti I n t e n t i n t e n t = new I n t e n t ( I n t e n t . ACTION PICK, C o n t a c t s C o n t r a c t . Contacts .CONTENT URI ) ; s t a r t A c t i v i t y F o r R e s u l t ( i n t e n t , CONTACT RESULT) ; nel quale sono state utilizzate le costanti statiche Intent.ACTION PICK ed ContactsContract.Contacts.CONTENT URI e il metodo startActivityForResult, indica110 Capitolo 4 sd: Campionario Attore : Controller : View : 1) . Importa Contatti da Rubrica 1) Restituisci valori Contatto 2) . salva 3) . recuperaValoriInseriti 3) RestituisciValori . VerificaValori 2) ritornaRisultato Figura 4.2: Diagramma di sequenza per il caso d’uso: Aggiungi Indirizzo Principale. 111 Capitolo 4 to per ritornare il contatto da cercare (che sarà selezionato). Avendo selezionato il contatto desiderato, si deve effettuare un override del metodo onActivityResult per reperire i dati dell’elemento selezionato. p r o t e c t e d v o i d o n A c t i v i t y R e s u l t ( i n t requestCode , i n t r e s u l t C o d e , I n t e n t data ) { s w i t c h ( r eq u e s t C o d e ) { c a s e CONTACT RESULT: i f ( r e s u l t C o d e == RESULT OK) { g e s t i s c i I n d i r i z z i ( data ) ; } } } In questo codice è stata creata una struttura switch per identificare lo specifico risultato mediante una costante intera associata (CONTACT RESULT). Si rendono necessarie le precedenti righe perchè ogni Activty è in grado di collaborare con le altre, fornendo e ricevendo informazioni attraverso il metodo startActivityForResult, gli Intent e le costanti statiche che identificano l’elemento che sta ritornando. Per completare la trattazione relativa alla creazione di un indirizzo, si mostra come è stato creato il metodo gestisciIndirizzi, all’interno del quale vengono recuperati valori come cap, numero civico, città, paese ecc. Per eseguire il comando è necessario effettuare una nuova query con URI definita in ContactsContract.Data.CONTENCT URI e creare un ciclo che ha un numero di ripetizioni pari agli indirizzi recuperati del determinato utente (casa, lavoro, ecc.). Uri c o nt a ct Da t a = data . getData ( ) ; Cursor c r = managedQuery ( contactData , n u l l , n u l l , n u l l , null ); i f ( c r . moveToFirst ( ) ) { String contactId = cr . getString ( 112 Capitolo 4 c r . getColumnIndexOrThrow ( C o n t a c t s C o n t r a c t . Contacts . ID ) ) ; Cursor p o s t a l s = g e t C o n t e n t R e s o l v e r ( ) . query ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l . CONTENT URI, n u l l , C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l .CONTACT ID + ” = ” + c o n t a c t I d , n u l l , n u l l ) ; i n t postFormatted= p o s t a l s . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l .FORMATTED ADDRESS) ; i n t postTypeNdx= p o s t a l s . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l .TYPE) ; i n t v i a= p o s t a l s . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l .STREET ) ; i n t comune= p o s t a l s . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l . CITY ) ; i n t cod= p o s t a l s . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l .POSTCODE) ; i n t prov= p o s t a l s . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l .REGION ) ; i n t pobox= p o s t a l s . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . S t r u c t u r e d P o s t a l .POBOX) ; w h i l e ( p o s t a l s . moveToNext ( ) && t r o v a t o != t r u e ) { [...] } postals . close (); 113 Capitolo 4 nel quale viene estratta la URI (contactData) relativa al contatto selezionato e di conseguenza viene effettuata una Query per recuperare i dati di esso. Ogni query restituisce un cursore e con esso si possono effettuare tipiche operazioni agli elementi restituiti. Gli interi posti prima del ciclo while sono utilizzati per recuperare rispettivamente i dati relativi all’indirizzo completo, al tipo (principale, secondario, ecc.), alla via, città, codice postale, regione e numero civico, mediante il metodo getString(int) che può essere espresso ad esempio per il primo intero nel seguente modo. p o s t a l D a t a= p o s t a l s . g e t S t r i n g ( postTypeNdx ) ; Inserendo la precedente riga all’interno del ciclo si possono recuperare i valori dei vari campi relativi a tutte le altre proprietà. Questa metolodia è stata implementata a partire dalla versione 2.0 di Android, per questo, non è possibile installare l’appḂAgent in dispositivi che utilizzano una versione precedente. La scelta è stata dettata principalmente dai dati, i quali affermano che le versioni 1.x hanno circa il 2% degli accessi al market android. Da notare che per utilizzare la lista dei contatti è necessario l’inserimento di un particolare premesso all’interno del file manifest, che in questo caso dichiara la possibilità di accedere alla rubrica telefonica. <uses −p e r m i s s i o n a n d r o i d : name=”a n d r o i d . p e r m i s s i o n .READ CONTACT” /> 4.2.2 Aggiungere Recapiti Per aggiungere i recapiti, quali telefono fisso, mobile, email e fax, ancora una volta è stata creata una nuova Activity adatta a questo scopo. In questo caso l’SSD è simile al precedente ed è espresso nel seguente modo. Per recuperare gli elementi dai contatti telefonici si rende necessaria l’inclusione del permesso <uses −p e r m i s s i o n a n d r o i d : name=”a n d r o i d . p e r m i s s i o n .READ CONTACT” /> all’interno del file manifest. 114 Capitolo 4 sd: Campionario Attore : Controller : View : 1) . Importa Contatti da Rubrica 1) Restituisci valori Contatto 2) . salva 3) . recuperaValoriInseriti 3) RestituisciValori . VerificaValori 2) ritornaRisultato Figura 4.3: Diagramma di sequenza per il caso d’uso, Aggiungi Recapiti. 115 Capitolo 4 In questo caso ogni tipologia di recapito richiede una specifica query; per questo motivo sono trattate singolarmente. Codice relativo: recupero id e nome Per recuperare dati come l’ID o il nome del contatto è necessario utilizzare la URI salvata in ContactsContract.Contacts.CONTENT URI. ContentResolver cr = getContentResolver ( ) ; Cursor c u r = c r . query ( C o n t a c t s C o n t r a c t . Contacts . CONTENT URI, null , null , null , null ) ; i f ( c u r . getCount ( ) > 0 ) { w h i l e ( c u r . moveToNext ( ) ) { String id = cur . g e t S t r i n g ( c ur . getColumnIndex ( C o n t a c t s C o n t r a c t . Contacts . ID ) ) ; S t r i n g name = c u r . g e t S t r i n g ( c u r . getColumnIndex ( C o n t a c t s C o n t r a c t . Contacts . DISPLAY NAME ) ) ; i f ( I n t e g e r . p a r s e I n t ( c u r . g e t S t r i n g ( c ur . getColumnIndex ( C o n t a c t s C o n t r a c t . Contacts .HAS PHONE NUMBER))) > 0 ) { [...] } } } Nel precedente codice si è creata una istanza di ContentResolver e successivamente è stato utilizzata questa istanza per effetturare la query, la quale ritorna un Cursore contenente la lista dei contatti. Il successivo passo eseguito verifica se l’iteratore contiene elementi, e in tal caso effettua un ciclo dove il primo valore (id ) è un ID identificativo dell’utente corrente, mentre la seconda variabile (name) contiene il nome associato. L’ultima istruzione verifica se l’utente corrente (corrispondente alla posizione attuale del cursore) ha o meno un numero di telefono, attraverso l’istruzione if e la costante statica ContactsContract.Contacts.HAS PHONE NUMBER. 116 Capitolo 4 Codice relativo: recupero telefono fisso Come per altri dati relativi ad un contatto, i numeri di telefono sono contenuti in una tabella separata, quindi hanno bisogno di una seconda query. Per effettuare questa ricerca è necessario utilizzare la URI contenuta all’interno della variabile ContactsContract.CommonDataKind.Phone.CONTENT URI. All’interno del codice viene utilizzato un ciclo While per recuperare il numero di uno specifico contatto mediante un ID (definito nella precedente sezione). i f ( I n t e g e r . p a r s e I n t ( c u r . g e t S t r i n g ( c u r . getColumnIndex ( C o n t a c t s C o n t r a c t . Contacts .HAS PHONE NUMBER) ) ) > 0 ) { Cursor pCur = c r . query ( C o n t a c t s C o n t r a c t . CommonDataKinds . Phone . CONTENT URI, null , C o n t a c t s C o n t r a c t . CommonDataKinds . Phone .CONTACT ID + ” = ?” , new S t r i n g [ ] { i d } , n u l l ) ; w h i l e ( pCur . moveToNext ( ) ) { [...] } pCur . c l o s e ( ) ; } Codice relativo: recuperare numeri secondari Da precisare che ogni contatto può contenere diversi numeri di telefono. Questi possono essere numeri fissi, mobili, fax, ma anche numeri secondari appartenenti alle precedenti categorie. In questo secondo caso si evidenzia la tipologia del numero ed il numero stesso attraverso attrettante variabili (number e type). i f ( phone . moveToFirst ( ) ) { f i n a l i n t contactNumberColumnIndex= phone . getColumnIndex ( Phone .NUMBER) ; f i n a l i n t contactTypeColumnIndex= phone 117 Capitolo 4 . getColumnIndex ( Phone .TYPE) ; w h i l e ( ! phone . i s A f t e r L a s t ( ) ) { f i n a l S t r i n g number= phone . g e t S t r i n g ( contactNumberColumnIndex ) ; f i n a l i n t type= phone . g e t I n t ( contactTypeColumnIndex ) ; f i n a l i n t t y p e L a b e l R e s o u r c e = Phone . getTypeLabelResource ( type ) ; doSomethingWithAContactPhoneNumber ( number , typeLabelResource ) ; phone . moveToNext ( ) ; } } phone . c l o s e ( ) ; Codice relativo: recuperare email Anche per recuperare le email è necessario eseguire una query dove la URI relativa a queste è salvata in ContactsContract.CommonDataKinds.Email.CONTENT URI. Cursor emailCur = c r . query ( C o n t a c t s C o n t r a c t . CommonDataKinds . Email . CONTENT URI, null , C o n t a c t s C o n t r a c t . CommonDataKinds . Email .CONTACT ID + ” = ?” , new S t r i n g [ ] { i d } , n u l l ) ; w h i l e ( emailCur . moveToNext ( ) ) { S t r i n g e m a i l = emailCur . g e t S t r i n g ( emailCur . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . Email .DATA) ) ; S t r i n g emailType = emailCur . g e t S t r i n g ( emailCur . getColumnIndex ( C o n t a c t s C o n t r a c t . CommonDataKinds . Email .TYPE) ) ; } 118 Capitolo 4 emailCur . c l o s e ( ) ; } 4.2.3 Aggiungere un Cliente Una volta aggiunti gli Indirizzi ed i Recapiti è possibile inserire i dati relativi ad un Cliente come nome, cognome, codice fiscale e in un secondo momento effettuare l’inserimento all’interno del database Db4o. Questa struttura può essere rappresentata all’inteno di un diagramma SSD, il quale è lo stesso introdotto all’inizio del capito. sd: Campionario Attore : Controller : View : Foundation : . Aggiungi Indirizzo principale . Aggiungi Indirizzi secondari . Aggiungi Recapito 1) . Aggiungi Cliente 2) . Recupera Elementi 2) Restituisci Elementi . controllaValori . store 1) Verifica Aggiunta Figura 4.4: Diagramma di sequenza per il caso d’uso: Aggiungi Cliente. Codice relativo Avendo già chiamato le Activity corrispondenti alla creazione di un indirizzo o un recapito esposte in precedenza, è possibile salvarne i risultati all’interno del metodo onActivityResult. 119 Capitolo 4 @Override p u b l i c v o i d o n A c t i v i t y R e s u l t ( i n t requestCode , i n t r e s u l t C o d e , I n t e n t data ) { s u p e r . o n A c t i v i t y R e s u l t ( requestCode , r e s u l t C o d e , data ) ; switch ( requestCode ) { //AGGIUNGI INDIRIZZO c a s e (ADD IND ) : { i f ( r e s u l t C o d e == A c t i v i t y . RESULT OK) { EInd = ( E I n d i r i z z o ) b r i d g e . getParameter ( ” E I n d i r i z z o ” ) ; } break ; } //AGGIUNGI RECAPITI c a s e (ADD REC) : { i f ( r e s u l t C o d e == A c t i v i t y . RESULT OK) { ECont = ( EContatti ) b r i d g e . getParameter ( ” EContatti ” ) ; } break ; } Il precedente codice verifica quale attività sta tornando mediante lo switch e se essa ritorna un risultato positivo attraverso la costante Activity.RESULT OK. In questo caso si instanzia il relativo oggetto con il valore ripreso dalla classe Singleton. Si noti che in questo caso gli oggetti Indirizzo o Contatti non sono stati ancora inseriti nella base dati. Essi sono solo salvati in memoria utilizzando il pattern specifico. La precedente affermazione dimostra un’ulteriore proprietà della classe implementata per lo scambio di oggetti univoci tra Activity, la quale non è possibile con 120 Capitolo 4 le API messe a disposizione dai progettisti Andorid. Una volta recuperati i dati relativi ad Indirizzi e Contatti ed avendo inserito gli elementi di un utente è possibbile salvarlo all’interno del database con il seguente metodo. p r i v a t e View . O n C l i c k L i s t e n e r v e r i f i c a A g g i u n t a=new View . OnClickListener () { p u b l i c v o i d o n C l i c k ( View v ) { S t r i n g [ ] r e t= VAggCl . r e c u p e r a V a l o r i E d i t i T e x t ( etNome , etCognome , etCf , e t P i v a ) ; i n t v a l u e= c o n t r o l l a V a l o r i ( r e t [ 0 ] , r e t [ 1 ] , r e t [ 2 ] , r e t [ 3 ] , EInd , ECont ) ; i f ( v a l u e ==0) { E C l i e n t e EC= new E C l i e n t e ( r e t [ 0 ] , r e t [ 1 ] , ECont , EInd , EDate , r e t [ 3 ] , r e t [ 2 ] ) ; f c . s t o r e (EC ) ; s e t R e s u l t (RESULT OK ) ; finish (); } } }; Lo specifico metodo, attivato alla pressione del pulsante di conferma, recupera prima i dati relativi al cliente attraverso la classe VAggiungiCliente e poi effettua una serie di controlli sui dati con delle specifiche Regular Expression. Se non si riscontrano errori l’oggetto Cliente è inserito all’interno del database attraverso il metodo fc.store(EC), dove EC è il valore da salvare, mentre fc è un oggetto foundation relativo al cliente (FCliente), il quale si occupa dell’interazione con il database per gli oggetti Cliente attaverso la classe generica Foundation. Il metodo store può essere creato effettuando una chiamata al metodo db() della superclasse Foundation, il quale restituisce un oggetto di tipo ObjectContainer 121 Capitolo 4 utilizzato per accedere ai dati inseriti nella base dati, mentre la commit() rende l’oggetto persistente all’interno di esso. public void s t o r e ( ECliente e c l ) { db ( ) . s t o r e ( e c l ) ; db ( ) . commit ( ) ; } Molto esaltante il fatto che l’oggetto Cliente contiene al suo interno sia Indirizzi che Recapiti; mediante le precedenti righe di codice verranno salvati tutti gli elementi contenuti all’interno di esso, diversamente dai database relazionali nei quali è necessario effettuare diverse operazioni di insert per ogni elemento da inserire. Come specificato in precedenza, db() è un metodo della superclasse Foundation e può essere implemtentato nel seguente modo. p r o t e c t e d O b j e c t C o n t a i n e r db ( ) { try { i f ( oc==n u l l | | oc . e x t ( ) . i s C l o s e d ( ) ) oc= Db4o . o p e n F i l e ( dbConfig ( t h i s . o b j e c t ) , db4oDBFullPath ( c o n t e x t ) ) ; r e t u r n oc ; } c a t c h ( E xc e pt i o n e ) { Log . e ( t h i s . path , e . t o S t r i n g ( ) ) ; return null ; } } Nel precedente codice viene creato un nuovo oggetto di tipo ObjectContainer solo nel caso in cui non è presente in memoria. Questa situazione riduce i costi associati alla sua creazione. 122 Capitolo 4 4.3 Modificare ed eliminare un Cliente Operazioni di modifica ed eliminazione dei Clienti sono anche esse molto semplici utilizzando un database ad oggetti. I prossimi paragrafi sono dedicati alla modifica ed all’eliminazione di un cliente, inserito con i costrutti descritti in precedenza. In questi casi, i diagrammi di sequenza possono essere molto simili tra loro, nei quali la prima operazione da eseguire è il recupero dello specifico oggetto, mentre la seconda è la modifica o l’eliminazione di esso. sd: Campionario Attore : Controller : Foundation : 1) . Seleziona Cliente 2) . Recupera Cliente 2) Ritorna Cliente 1) Visualizza Cliente 3) . Esegui Operazione . store/delete 3) Notifica Attore Figura 4.5: Diagramma di sequenza per il caso d’uso: Modifica o Elimina Cliente. 4.3.1 Modificare un Cliente La modifica dei clienti può essere pensata in modo molto simile alla creazione. In questo caso basta recuperare l’oggetto desiderato ad esempio mediante la seguente 123 Capitolo 4 query SODA (adatta per la creazione di query dinamiche come nel nostro caso) inclusa nella classe FCliente. p u b l i c A r r a y L i s t <ECliente > r e t r i v e C o g n o m i C l i e n t i ( S t r i n g c ) { A r r a y L i s t <ECliente > e c= new A r r a y L i s t <ECliente > ( ) ; ObjectSet <Object> o s r e s= s u p e r . findByName ( c , t h i s . cognome , order , l i k e ) ; w h i l e ( o s r e s . hasNext ( ) ) e c . add ( ( E C l i e n t e ) o s r e s . next ( ) ) ; return ec ; } Il precedente metodo ritorna un ArrayList poichè il cognome da cercare (unico parametro passato) potrebbe essere contenuto più volte all’interno della base dati. Il risultato di una query è sempre espressa mediante un oggetto di tipo ObjectSet, mentre super.findByName(c, this.cognome, order, like) è un metodo della superclasse Foundation che può essere espresso nel seguente modo. p u b l i c ObjectSet <Object> findByName ( S t r i n g name , S t r i n g des ) { Query query= db ( ) . query ( ) ; query . c o n s t r a i n ( t h i s . o b j e c t ) ; query . descend ( de s ) . o r d e r D e s c e n d i n g ( ) . c o n s t r a i n ( new S t r i n g ( name ) ) ; r e t u r n query . e x e c u t e ( ) ; } Si noti l’enorme potenzialità della struttura. Il metodo findByName(String name, String des) può essere utilizzato oltre alla ricerca per cognome di un Cliente, anche per ricercare un ordine, un prodotto ecc. 124 Capitolo 4 Dopo aver recuperato gli elementi è possibbile effettuare la chiamata al metodo update di FCliente per rendere persistenti i cambiamenti. p u b l i c v o i d update ( E C l i e n t e EC1 , E C l i e n t e EC2) { EC1 . s e t C o d i c e F i s c a l e (EC2 . g e t C o d i c e F i s c a l e ( ) ) ; EC1 . setCognome (EC2 . getCognome ( ) ) ; EC1 . s e t D a t a N a s c i t a (EC2 . g e t D a t a N a s c i t a ( ) ) ; EC1 . s e t I n d i r i z z o (EC2 . g e t I n d i r i z z o ( ) ) ; EC1 . setNome (EC2 . getNome ( ) ) ; EC1 . s e t P a r t i t a I v a (EC2 . g e t P a r t i t a I v a ( ) ) ; EC1 . s e t C o n t a t t i (EC2 . g e t C o n t a t t i ( ) ) ; i f (EC2 . r e t r i v e A l l A l t r i I n d i r i z z o ( ) != n u l l ) EC1 . s e t A l l A l t r i I n d i r i z z i (EC2 . retriveAllAltriIndirizzo ()); s t o r e (EC1 ) ; } Il significato di questo codice è molto semplice. Vengono passati un oggetto Cliente (EC1) da modificare ed un oggetto Cliente (EC2) contenente i nuovi valori da inserire all’interno del primo. Le chiamate ai metodi setxx() sono quelle tipiche dei linguaggi di programmazione ad oggetti, per cui mediante Db4o si ha una perfetta integrazione con esso (Java in questo caso). L’ultima riga è rappresentata dal metodo store(), il quale è lo stesso utilizzato per la creazione di un nuovo oggetto nel database. Db4o, internamente, riesce a verificare che si tratta di un elemento già inserito, quindi, effettua solo la modifica dei valori al suo interno. 4.3.2 Eliminare un Cliente L’eliminazione di un cliente è del tutto simile alla sua modifica. Anche in questo caso si deve recuperare un oggetto o una lista di essi per poi effettuare la chiamata 125 Capitolo 4 al metodo delete() della super classe Foundation. p u b l i c v o i d d e l e t e ( Object o ) { db ( ) . d e l e t e ( o ) ; db ( ) . commit ( ) ; } Il parametro passato all’interno del metodo è un Object generico, quindi implica che il metodo è utilizzabile in ogni contesto dell’applicazione. Le righe di codice inserite al suo interno hanno gli stessi significati discussi in precedenza. db() recupera un ObjectContainer, mentre il metodo commit() rende persistenti le modifiche, quindi in questo modo si effettua la chiusura di una transizione. 126 Capitolo 5 Conclusioni 5.1 Il problema Durante la stesura della tesi, si è più volte scritto che esistono diversi sistemi per dispositivi mobili. Alla luce di quanto espresso nei primi capitoli, appare chiaro che ogni programmatore potrebbe predilire la programmazione in teconologie open come Android. Allo stesso modo, anche gli utenti finali potrebbero trovarsi davanti una vasta gammma di prodotti tra cui scegliere, con un sistema performante, dalle innumerevoli estensioni e dai costi, in alcuni casi molto ridotti rispetto agli antagonisti. Nell’applicazione cui si è discusso negli ultimi due capitoli della tesi, si è posto di risolvere il problema generico riguardante la gestione di un campionario di merci, prodotti, clienti ed appuntamenti. Per il precedente motivo è stato necessario scegliere un sistema per la gestione della persistenza dei dati. 5.2 La soluzione La maggior parte delle applicazioni scritte per Android o per qualsiasi smartphone hanno bisogno di un database di tipo embedded, cioè che non necessita di una connessione client server per accedere ai dati in esso contenuti. Questa caratteristica è tipica di SQLite, Db4o e Perst. 127 Capitolo 5 Per la gestione della persistenza dei dati, essendo Android scritto con un linguaggio di programmazione Pure Object, Java, è stata naturale la scelta di un DBMS orientato ad oggetti. Come si è visto, un OODBMS riduce le righe di codice utilizzate per accededere ai dati contenuti nel database e per la conversione tra un modello ad oggetti (Android) in uno relazionale (RDBMS ), tipicamente realizzato mediente un ORM. Sicuramente il successo di un sistema per dispositivi mobili è dato dal numero di estensioni o applicazioni disponibili. Ad avvalere questa affermazione, gli stessi svilupatori Android hanno fornito tool e plugin per la loro realizzazione. L’applicazione di riferimento della tesi, BAgent, è stata realizzata mediante l’IDE Eclipse ed i plugin OME (Object Manager Enterprise), utilizzato per Db4o, ed ADT (Android Development Tool), usato per lo sviluppo dell’applicazione Android. Per la creazione dei codici si è fatto uso dei Patterns MVC e Singleton, i quali mi hanno aiutato a definire varie classi per la suddivisione dei compiti e per la gestione di oggetti univoci per la comunicazione all’interno del progetto e del database. 5.3 considezioni personali Gli argomenti principali di questo testo sono Android e i DBMS. Per ognuno di essi esprimo di seguito un mio parere. Android e smartphone. Di solito la scelta di quale dispositivo mobile comprare non avviene a seconda del sistema su cui si basa, ma in base alla conoscenza che le persone hanno di quello smartphone. Questa situazione porta di solito il cliente a comprare un prodotto a scatola chiusa, di cui non si conoscono le reali caratteristiche e il mondo che gira attorno ad esso. Una differenza che si può trovare nei diversi sistemi, sta nella varietà di prodotti tra cui scegliere. L’associazione partner di Google (OHA) conta costruttori di dispositivi mobili e mobile carriers, al contrario di altri, che fanno capo ad una singola azienda. La precedente affermazione non si traduce però in termini di guadagno, infatti da un indagine risulta che Apple guadagna più dell’insieme di tutte le aziende che utilizzano Android nei propri device. 128 Capitolo 5 Programmare nei diversi sistemi per dispositivi mobili risulta essere molto diverso. In Android non sono necessarie royalities ed è possibile inserire liberamente le applicazioni nel market. Se si vuole programmare ad esempio per Iphone, il prodotto deve essere testato più volte prima di ricevere l’ok per includerlo nello store. Dalle precedenti righe si capisce che uno degli aspetti fondamentali nella programmazione e nella realizzazione di smartphone, come accade in ogni campo, riguarda costi e profitti. I prodotti che utilizzano il sistema Google possono partire da poche centinaia di euro, mentre altri dispositivi hanno un costo molto elevato. Forse una delle ragioni di questo gap è proprio da implicare alla natura dei sistemi in essi contenuti. L’informatica è una materia di studio che ha pochi anni rispetto altre, ma è una delle poche che fornisce prodotti open. Secondo il mio parere, gli stessi programmatori dovrebbero favorirne la crescita e stimolare l’evoluzione di questi elementi e di Android nel nostro contesto. OODBMS e RDMBS. Gli OODBMS si associano sempre ai linguaggi di programmazione orientati agli oggetti. Siccome questi sono arrivati in seguito ai linguaggi procedurali, si è dovuto necessariamente incorporare le strutture per la memorizzazione dei dati attraverso gli RDBMS, i quali hanno avuto un successo strabiliante. Negli ultimi anni sembra che qualcosa stia cambiando. Stanno nascendo molti sistemi per la gestione di base dati ad oggetti e questo fa ben sperare per il futuro. L’unica incognita è ancora legata a fattori economici. Una grande azienda sarà disposta a cambiare il proprio RDBMS verso un ODBMS ?. In applicazioni Android il database di riferimento è SQLite, il quale è stato incluso all’interno dello stack. Per contro, la teconologia utilizzata in BAgent è stato un ODBMS che va sotto il nome di Db4o. Esso è un sistema reperibile sotto licenza open-source caratterizzato dall’essere di tipo embedded, cioè non necessita di connessioni client-server per l’accesso ai dati in esso contenuti. Il secondo sistema per la gestione dei dati trattato nella tesi è Perst. Apparentemente ha le stesse caratteristiche incontrate in Db4o, ma al suo interno le cose cambiano molto. Per ogni operazione da eseguire bisogna sempre far riferimento ad un particolare oggetto. Personalmente preferisco l’approccio adottato in Db4o, il quale introduce un livello maggiore di trasparenza verso il programmatore, diminuendo i 129 Capitolo 5 codici e di conseguenza i possibili errori. Il processo precedentemente esposto porta a minori tempi di latenza nel momento in cui si effettuano operazioni di inserimento, ricerca ed eliminazione su milioni o miliardi di oggetti. Questo proposito potrebbe far cambiare del tutto la mia opinione sui due sistemi, ma dovendo implementare i due in un dispositivo Android, potrebbe essere altamente improbabile dover operare con una cosı̀ grande quantità di oggetti. Per questo motivo si vuol dare grande merito agli sviluppatori di Perst che sono riusciti a far ottenere delle performance quasi sorprendenti a discapito di un maggiore sforzo nella creazione dei codici. 5.4 sviluppi futuri Riguardo BAgent potrebbe essere utile nel futuro l’adattamento dell’applicazione per i tablet. In questo caso basterebbe modificare i file .xml, migliorando i layout per i device in questione. Ulteriori sviluppi potrebbero riguardare la gestione del sistema di localizzazione GPS. Ormai quasi tutte le applicazioni scritte in Android fanno riferimento a questo sistema. In un’applicazione come BAgent, in grado di gestire appuntamenti con i clienti, potrebbe essere molto efficente la localizzazione su mappa di dove questi avverranno. Un ulteriore sguardo al futuro potrebbe riguardare una gestione centralizzata dei dati mediante il sistema DRS (Data Replication System) introdotto da Db4o. Questa tecnica è in grado di replicare i dati all’interno del database del dispositivo, in uno sito su di un server, mediante una connessione TCP/IP. Con questa caratteristica l’applicazione potrebbe essere utilizzata all’interno di aziende che hanno un database centralizzato (da creare o addirittura già esistente) con molti agenti di commercio. Ogni utente che utilizzerà l’applicazione potrà essere in grado di salvare nuovi elementi nel database interno e in un secondo momento, inviare dati al server centrale, in modo da eliminare eventuali problemi di connessione e per la condivisione dei dati. 130 Ringraziamenti Il momento più bello durante il lungo viaggio di scrittura di una tesi è sicuramente quello in cui arrivi ai rigraziamenti. In queste situazioni ripercorri gli istanti che ti hanno portato ad un gradino dalla tanto ambita laurea in ingegneria informatica. Per questa ragione ho intenzione di ringraziare la mia famiglia, che mi ha permesso di arrivare fin qui, ed i miei amici, cui non scriverò i loro nomi singolarmente, poichè leggendo queste righe capiranno di esserre in questa stretta cerchia. Un ulteriore ringraziamento va a tutte le persone che mi sono state vicine nel periodo in cui ho frequentato l’università, mentre, dopo essere ritornato nel mio paese, mi hanno ospitato per pranzi, caffè ed intere notti. 131 Bibliografia [1] Bitmap.it, Mercato smartphone. http://www.bitmat.it/articolo.php?aId =0000090444&cId=46&cpId=20&n=Dispositivi+mobili:+un+mercato+in+ crescita+costante. [2] Delvecchio A. 2011, sistemi per dispositivi mobili. http://www.iphoneitalia.com/nel-panorama-dei-dispositivi-mobili-google-edapple-continuano-a-mantenere-un-dominio-quasi-incontrastato-289690.html. [3] Nittoli L. 2011, sistemi per dispositivi mobili (2). http://www.androidworld.it/2011/11/20/un-grafico-mostra-landamento-dellevendite-globali-degli-smartphone-e-la-straordinaria-crescita-di-android-63262/. [4] Meier, R. 2010, Professional Android 2 - Application Development. Wiley Puplishing, Inc. Indiana. [5] Carli, M. 2010, Android - Guida per lo sviluppatore, Apogeo. [6] Google, 2012, Documentazione ufficiale per gli sviluppatori. http://developer.android.com/guide/basics/what-is-android.html. [7] Google, 2012, Android Target Device. http://developer.android.com/resources /dashboard/platform-versions.html. [8] Di Felice P. 2008, Appunti base dati uno - RDBMS. [9] SQLite, Documentazione ufficiale per gli sviluppatori. http://www.sqlite.org. 132 Capitolo 5 [10] ODBMS.org, 2012, ODBMS. http://www.odbms.org/. [11] Paterson J., Edlich S., Hrning H., Hrning R. 2006, The definitive guide to Db4o. Apress. [12] Versant, 2012, Documentazione ufficiale per gli sviluppatori Db4o. http://community.versant.com/. [13] Versant, 2012, DRS - Db4o. http://www.db4o.com/about/productinformation/ drs/. [14] Polepos, 2012, Benchmarks database. http://www.polepos.org/. [15] McObject, 2012, Documentazione ufficiale http://www.mcobject.com/perst. [16] Bigatti M., il linguaggio Java, Hoepli informatica. 133 per gli sviluppatori. Appendice A Programazione ad oggetti e Java A.1 Programmazione ad oggetti Il paradigma ad oggetti ha cambiato il modo di rappresentare le identità all’interno di un’applicazione. In questo tipo di programmazione (OOP, Object Oriented Programming) vengono create classi 1 , le quali sono rappresentate da un modello. A partire dal modello vengono creati più oggetti 2 , diversi tra loro, ma che hanno la stessa struttura. Per esempio, per instanziare molti oggetti di tipo Impiegato bisogna utilizzare una classe Impiegato. Ogni oggetto dello stesso tipo corrisponderà ad un istanza della classe differente da qualsiasi altra. Obiettivo fondamentale di questa programmazione è la riusabilità del codice. Avendo creato una specifica classe, essa può essere utilizzata più volte all’interno dell’applicazione, oltre ad essere esportabile in altre. Ogni oggetto è caratteristico di una propria entità, in modo da distinguerlo dai propri simili. Questo meccanismo è offerto dalla notazione di maniglia (handle) dell’oggetto, nota con il nome formale di identificatore o Object ID. Ogni classe è caratterizzata da metodi ed attributi. Gli attributi sono elementi caratteristici di un determinato oggetto come ad esempio il nome per una Persona. 1 Una classe è la struttura dalla quale vengono creati oggetti all’iterno dell’applicazione 2 Gli oggetti possono essere paragonati ad entità fisiche create a partire dalle classi, all’esecuzione dell’applicazione. 134 Appendice A Gli attributi possono rappresentare tipi semplici, oggetti o collezione di essi. Un metodo può essere paragonato ad una funzione di un linguaggio procedurale 3 atto a lavorare con gli attributi dell’oggetto corrispondente. Metodi base questo in tipo di programmazione sono ad esempio i setxxx() e getxxx(), che rispettivamente modificano o recuperano il valore di un determinato dato. Mediante l’uso dei metodi viene creata una sorta di incapsulamento tra l’ambiente esterno e gli attributi degli oggetti. I soli elementi che possono accedere agli attributi sono i metodi della specifica classe. Dal momento che solo le operazioni dell’oggetto possono leggere ed aggiornare i dati di quest’ultimo, queste operazioni formano un anello di protezione attorno al nucleo centrale composto dalle variabili. Due oggetti possono comunicare tra loro attraverso l’invio di messaggi di tre tipi: Messaggio informativo: il quale fornisce informazioni al destinatario. Messaggio interrogativo: che richiede informazioni al destinatario. Messaggio imperativo: che richiede di eseguire metodi al destinatario. Gli oggetti creati all’interno delle applicazioni hanno la caratteristica di non essere persistenti. Essi vengono cancellati dalla memoria al momento della chiusura dell’applicazione, quindi, occorrono strumenti per consentire il salvataggio di essi all’interno di strutture ben definite (base di dati o serializzazione). A.2 Java 4 Java: storia e caratteristiche è uno dei linguaggi di programmazione Object Oriented, caratterizzato da quattro principali pecurialità: 3 Un linguaggio procedurale è formato da funzioni e sottofunzioni. Il compito di esse è restituire dati ottenuti al suo interno, le quali non avranno più memoria di essi. Un oggetto al contrario, è consapevole del suo passato e mantiene le informazioni al suo interno per tutta la durata dell’applicazione. 4 Java è stato creato a partire da ricerche effettuate alla Stanford University negli anni Novanta. Nel 1992 nasce il linguaggio Oak prodotto da Sun Microsystems. Tale nome fu successivamente cambiato in Java a causa di un problema di copyright (il linguaggio di programmazione Oak esisteva già). Java, oltre ad essere un linguaggio di programmazione è un marchio registrato dalla Oracle. 135 Appendice A • È un linguaggio Object Oriented puro 5 , di tipo type safe 6 . • Contiene strumenti e librerie per il networking. • È progettato per eseguire codice da sorgenti remoti in modo sicuro. • Ha una grande portabilità. La portabilità 7 del linguaggio è stata certemente una grande innovazione rispetto ad altri, i quali, dopo aver ottenuto il codice sorgente, lo compilano in base alla piattaforma. In Java queste procedure si traducono nei seguenti punti: • il codice viene tradotto da una copilatore in bytecode (codice intermedio) • il bytecode viene passato ad un internprete chiamato Java Virutal Machine (JVM). In un primo momento Sun decise di destinare questo prodotto alla creazione di applicazioni complesse per piccoli dispositivi elettronici, mentre nel 1993 iniziò a farsi notare come strumento per programmare in internet. Grazie alle applet, le pagine web diventarono interattive a livello client. Qusto linguaggio fu inizialmente rilasciato come Java Development Kit 1.0 (JDK 8 1.0), il quale comprendeva il runtime Java (virtual machine e librerie) e gli stru- menti di sviluppo (compilatore e altri strumenti). Successivamente venne fornito un pacchetto che comprendeva solo il runtime chiamato Java Runtime Environment (JRE). 5 In questo contesto, la notazione puro indica che anche i tipi semplici di dati sono oggetti. 6 Java è generalmente considerato un linguaggio con forte tipizzazione. 7 Sebbene sia possibile scrivere in Java, programmi che si comportano in modo consistente attraverso molte piattaforme hardware diverse, bisogna tenere presente, che questi poi dipendono dalle macchine virtuali che sono a loro volta programmi a sè e che hanno inevitabilmente i loro bug. 8 La Sun (ora Oracle) mette a disposizione un software development kit chiamato Java Development Kit (JDK). Esso include un certo numero di tool di uso comune, e altri, atti ad elaborare file sorgenti e/o compilati. 136 Appendice A Siccome le applicazioni non sono uguali sia dal punto di vista funzionale che dal punto di vista architetturale, sono stati definiti gli ambienti J2SE, J2EE e J2ME. L’ambiente J2SE è usato per le applicazioni destkop composte quindi nella maggior parte dei casi di un interfaccia utente (GUI), mentre la J2EE consente la realizzazione di applicazioni denominate enterprise, che permettono l’interattività con sistemi di vario genere (DBMS, WebService). La J2ME è un framework ridotto sulla base della J2SE e che viene utilizzato per lo sviluppo di applicazioni il cui target è handeld e embedded quali smartphone, computer palmari, telefoni cellulari, navigatori satellitari ecc. La J2ME definisce le configuration ovvero quegli ambienti per dispositivi con caratteristiche hardware e software simili. Al momento esistono solo due tipi di configuration: • Connected Device Configuration (CDC): si riferisce a dispositivi con processore a 32 bit dove la VM di riferiemento è la CDC Hotspot Implementation (nota anche come Compact Virtual Machine). • Connected Limited Device Configuration (CLDC): fa riferimento a dispositivi con memoria di almeno 192 KB. La VM di riferimento prende il nome di KVM, dove la K da l’idea delle sue dimensioni. Per sviluppare programmi in Java è teoricamente sufficiente un qualsiasi editor di testo; in pratica, se si vuole scrivere qualcosa di più del classico hello world occorre un ambiente di sviluppo integrato (IDE). 137 Appendice B ORM ed Hibernate Durante la stesura delle tesi si è più volte fatto riferimento agli ORM ed al fatto che essi introducono uno nuovo livello tra l’archituttura relazionale del database e quella ad oggetti. Per dimostrare questa situazione si effettuano alcune considerazioni Su Hibernate. Questo ORM ha bisogno di alcuni file di configurazione per permetterne il corretto funzionamento. Il primo di essi è il file hibernate.cfg.xml che specifica diverse proprietà come username, password, posizione del database ecc. Gli altri file (anche essi di estenzione .xml) effettuano il mapping tra oggetti e tabelle del database. Come si intuisce facilmente, questi contengono le colonne correspondenti alla rispettiva tabella e il gli attributi della classe. L’unica restrizione richiesta con Hibernate è che ogni tabella deve avere un campo di tipo long per l’id. 138