Facoltà di Ingegneria Corso di Studi in Ingegneria Informatica Elaborato finale in Sistemi Operativi Sviluppo di un’applicazione in ambiente Android per l’acquisizione di misure accelerometriche. Anno Accademico 2011-2012 Candidato: Alessandro Celotti matr. N46/135 I Grazie di Cuore :3 II Indice Introduzione 4 Capitolo 1. Piattaforma Android 6 1.1 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.3 Introduzione Architettura delle Applicazioni Activity Service Content Provider Intent Gestione dei Processi e dei Thread 6 7 7 11 13 13 14 Capitolo 2. Accelerometro su Android 2.1 2.1.1 2.2 2.2.1 2.2.2 2.3 18 Sensori su Android Sensori di Movimento (Accelerometro) Classi ed API per l’Accelerometro Classi Interfacce Esempio Applicazione Accelerometrica 18 20 23 24 25 25 Conclusioni Bibliografia 29 30 III Applicazione su Android di acquisizione di misure accelerometriche Introduzione Attualmente i dispositivi cellulari di questa generazione hanno le potenzialità di un calcolatore. Infatti, grazie alle numerose funzionalità offerte da questi cellulari è possibile: telefonare, mandare un e-mail, controllare calendari e impegni, accedere alla rete, ascoltare musica o addirittura comporre musica, giocare al proprio videogame preferito, scattare una foto ed altre funzioni. Questi piccoli cellulari, chiamati “smartphone”, hanno quindi un grosso potenziale informatico. Ma cos’è uno smartphone? E’ un "telefonino multimediale" o "telefonino intelligente" che ha il grosso vantaggio di potersi collegare in rete in qualsiasi istante, ha una grossa potenza di calcolo per le sue dimensioni e permette affidabilità, tutto questo ad un costo comunque contenuto. “Picking up where amazing left off” dice l’Apple rilanciando il suo ultimo prodotto Iphone 4S. [1] Ogni smartphone ha un proprio sistema operativo, ricordiamo: - Windows Mobile. - Apple iOS. - Android. - Symbian. Con un insieme di caratteristiche hardware sorprendenti: basti pensare ad esempio al microprocessore del Samsung Galaxy s2 è di tipo dual core. [2] Per il collegamento in rete è fornito il meccanismo di connessione a lungo raggio come le Wireless (Wi-Fi) o a breve raggio come la Bluetooth o in alternativa la connessione 3G che permette sia il trasferimento dati “voce” (telefonate digitali) e quelle dati “non-voce” (per l’invio di un’e-mail, instant messaging o il browsing della rete). Altro fattore fondamentale è la sicurezza che devono fornire tali sistemi contro i malware, poiché la maggior parte delle applicazioni sono scaricabili dal Web (basti pensare all’Apple Store) e questo è proprio il compito dell’ENISA (European Network and Information Security Agency). 4 Applicazione su Android di acquisizione di misure accelerometriche Uno smartphone inoltre possiede una serie di sensori permettono di: misurare la temperatura dell’ambiente, la luminosità della stanza per mettere il dispositivo in “stand by” oppure l’utilizzo del GPS integrato per visualizzare la posizione dello smartphone. I sensori più utilizzati all’interno di uno smartphone sono: 1. Giroscopio: fornisce indicazioni sulla posizione e l’inclinazione multi asse dello smartphone. 2. Accelerometro: fornisce dati sull’accelerazione del dispositivo se viene spostato lungo nelle tre dimensioni. 3. Luminosità dell’Ambiente: rivela la luminosità ambientale per apportare modifiche alla luminosità del display. 4. Termometro: fornisce dati riguardanti la temperatura della stanza. Questa serie di sensori sono integrati nell’hardware del dispositivo e spesso vengono gestiti in maniera completamente diversa per ogni smartphone. Punto d'interesse è il sensore accelerometrico, permette il monitoraggio di sistemi basandoci sulla variazione di accelerazione in qualsiasi contesto. Un accelerometro è uno strumento di misura in grado di misurare l’accelerazione di un corpo. La base è quella della rilevazione dell’inerzia di una massa sottoposta ad un’accelerazione: basandoci sulla legge di Newton “a = ∑(Fs/m)”. Un sensore o trasduttori invece è uno strumento che permette di trasformare una qualsiasi grandezza fisica in una tipicamente elettrica. E’ chiaro che quindi le applicazioni di tali sensori accelerometrici possono essere impiegati nella: medicina, monitoraggio dell’ambiente e delle costruzioni, videogiochi, ecc. Esempi di applicazioni: - - - Nella medicina ad esempio questi sistemi possono essere utilizzati nel monitoraggio del movimento umano quindi sul controllo della postura di una persona per facilitare la riabilitazione. [3] Oppure per la valutazione delle “vibrazioni meccaniche” a cui può essere esposta una persona e si basa sull’applicazione di tali sensori accelerometrici al punto di contatto tra le sorgenti delle vibrazioni ed il corpo del lavoratore esposto. [4] Nel monitoraggio dell’ambiente o delle infrastrutture: sono applicati modelli di tipo “sismico”. Da valutazioni delle vibrazioni di determinate zone della struttura. Questi dati elaborati sono acquisiti dagli accelerometri, mettendo in relazione i valori di sollecitazione impressi alla struttura. [5] Videogiochi: ulteriore esempio sono gli odierni videogame che fanno grandissimo uso di tali sensori, basti pensare alla console Wii Sports della Nintendo: posizionando i sensori accelerometrici in un telecomando è possibile giocare a tennis oppure lanciare una palla di bowling. Oppure in applicazioni sugli smartphone è possibile guidare un'automobile semplicemente ruotando il dispositivo. 5 Applicazione su Android di acquisizione di misure accelerometriche Capitolo 1 Piattaforma Android In questo capitolo affronteremo la tematica della piattaforma Android. Cos’è Android, come viene sviluppata un’applicazione di Android con la sua anatomia e la gestione dei processi e dei threads su tale piattaforma. 1.1 Introduzione Ma cos’è Android? E’ uno “software stack” (insieme di programmi che lavorano insieme per produrre un risultato: ad esempio un sistema operativo e le sue applicazioni) per dispositivi mobili, includendo un OS, middleware e applicazioni. L'Android SDK provvede i tools e le API necessarie allo sviluppo delle applicazioni sulla piattaforma Android usando il linguaggio di programmazione Java. Le caratteristiche fondamentali: - Application framework: abilita il riuso e il rimpiazzamento delle componenti. - DVM: ottimizzato per i dispositivi mobili. - Browser Integrato. - Graphics: librerie grafiche 2D e 3D. - Supporto Media: per audio, video e immagini in diversi formati. - Bluetooth, Edge, 3G e Wi-Fi. - Sensori: camera, GPS e accelerometri (dipendenti dall'hardware). La Architettura di Android viene descritta da tale diagramma: - Applicazioni: un insieme di applicazioni scritte in Java: email client, SMS, calendari, mappe, browser e altre. Application Framework: offre una piattaforma open-source di sviluppo, Android fornisce agli sviluppatori la possibilità di creare applicazioni innovative. Grazie all'utilizzo del dispositivo hardware, informazioni sulla posizione di accesso ed eseguire servizi in background. Gli sviluppatori hanno pieno accesso alle stesse framework API usate dalle applicazioni di nucleo. Tale architettura è stata progettata per semplificare il riuso delle componenti. Qualsiasi applicazione è soggetta a vincoli di sicurezza imposti dal framework. Questo meccanismo consente ai componenti di essere sostituiti 6 Applicazione su Android di acquisizione di misure accelerometriche dall'utente. Prevede: Content Providers, Resource Manager, Notification Manager e Activity Manager. Librerie: Android include un insieme di librerie C/C++ usate dai componenti dei sistemi Android. Android Runtime: Android include un set di librerie di nucleo che provvedono molte delle funzionalità disponibili nelle librerie di nucleo offerte dal linguaggio Java. Ogni applicazione lancia un suo processo, con la propria istanza della Dalvik virtual machine (DVM). La DVM è register-based (generica classe di macchina astratta) ed esegue classi compilate da Java. DVM si basa sul kernel Linux per funzionalità base come la gestione dei Thread e della Low-Level Memory Management. Kernel Linux: Android si basa su Linux v2.6 per servizi come: sicurezza, gestione della memoria, scheduling dei processi e modello driver. Il kernel è su di un livello di astrazione tra l'hardware e il resto del "software stack". - - 1.2 Architettura delle Applicazioni Prima dell’avvio degli “application components”, il sistema ricerca nel documento “AndroidManifest.xml” quali componenti inizializzare. Inoltre verifica i permessi, dichiara il numero minimo di API e le librerie API richieste dall’applicazione, le caratteristiche hardware e software dall’applicazione (come i sensori). Gli “application components” sono importanti per lo sviluppo di un’applicazione Android. Ognuno di essi ha una sua funzionalità ed è essenziale la sua esistenza. Ci sono quattro tipi di application compontents con distinto scopo e ciclo di vita: Activities: una “activity” rappresenta un’unica schermata con l’interfaccia utente. Le attività lavorano in gruppo per dare coerenza. Inoltre se è permesso dall’activity padre, si può creare una sorta di gerarchia. Services: un “service” è un componente che lavora in background per operazioni di lunga durata o svolgere attività per processi remoti. Content Providers: prevede la gestione dei dati di un’applicazione in ogni locazione in cui l’applicazione può accedere: file system, SQL database o sul web. Broadcast Receivers: tale componente risponde agli annunci al livello di sistema di trasmissione. La maggior parte degli annunci provengono dal sistema (batteria bassa), tuttavia un’applicazione può lanciare una trasmissione. Un aspetto unico dello sviluppo di un’applicazione Android è che ogni singola applicazione può istanziare una componente di un'altra applicazione. Quando il sistema avvia un componente, in contemporanea parte un processo, se non è già in esecuzione, che istanzia le classi necessarie dal componente, perciò tali applicazioni non hanno un unico entry point. All’interno di un’applicazione Android bisogna inviare un messaggio per utilizzare un determinato componente. Per l’attivazione di un componente bisogna inviare un messaggio asincrono chiamato “itent”. 1.2.1 Activity Un Activity mostra con quali utenti interagire nell’ordine di fare qualcosa, ognuna di essa ha una sua finestra nella quale viene disegnata l’interfaccia utente. Tale finestra solitamente 7 Applicazione su Android di acquisizione di misure accelerometriche riempie lo schermo, anche se in alcuni casi potrebbe essere più piccola ed essere spostata verso l’alto. Un’applicazione consiste in un insieme di attività che sono legate in maniera debole tra di loro. Un’attività in un’applicazione viene considerata come “main” activity, nel momento che l’applicazione viene lanciata per la prima volta. Su questa logica ogni attività può richiamarne un’altra per eseguire altre operazioni, quella precedentemente in esecuzione viene arrestata ma non terminata. Infatti viene conservata l’activity all’interno di una pila la “back stack”, la nuova attività viene inserita anch’essa nella “back stack”. Questa coda utilizza un meccanismo di gestione di tipo LIFO (Last In, First Out), perciò quando un utente ha terminato la sua operazione con la corrente attività e preme il pulsante “back”, l’attività viene estratta dalla coda e viene distrutta riprendendo l’attività precedente. Quando un’attività viene bloccata a causa di un’altra attività, il sistema notifica tale cambio di stato attraverso i “metodi di callback” del ciclo di vita dell’activity. Esistono un insieme di metodi di callback ognuno per specificare un cambio di stato (creazione, distruzione, ripresa) e ognuno di essi fornisce l’opportunità di svolgere un lavoro specifico legato a quel cambio di stato. Ad esempio quando si blocca un’attività tutte le risorse di grandi dimensioni devono essere rilasciate, quando invece è ripresa l’esecuzione dell’attività è possibile ottenere nuovamente tutte le risorse. Creazione di un’Attività: Per creare un’attività bisogna creare una sottoclasse di “Activity” [6] . Nella sotto classe bisogna implementare i metodi di callback necessari per i cambiamenti di stato del ciclo di vita dell’attività. I metodi più importanti sono: 1. onCreate(): serve per creare un’attività e tutte le sue componenti fondamentali. Ad esempio è qui che viene richiamato setContentView() per definire il layout per l’interfaccia utente dell’attività. 2. onPause(): il sistema chiama tale metodo per indicare che l’utente ha lasciato l’attività, questo non significa che l’attività viene distrutta. In questo stato bisognerebbe impiegare tutte le modifiche che devono essere mantenute al di là della sessione utente corrente, poiché l’utente potrebbe non tornare. Iniziare un’Attività: Si può iniziare una nuova attività, chiamando startActivity(), passandogli un’Intent che descrive l’attività che si vuole inizializzare. L’intent specifica l’esatta attività o l’operazione che si desidera eseguire, in modo che il sistema sceglie l’attività più consona all’operazione. Un intent può trasportare pure una piccola quantità di dati per quando sarà avviata l’attività. Capita spesso all’interno di un’attività di dover richiamare una nuova attività nota: in questo caso bisogna semplicemente fornire all’interno dell’intent il nome della classe dell’attività da avviare: Intent intent = new Intent (this, SignInActivity.class); startActivity(intent); *SignInActivity è la nuova attività da iniziare. Capita di dover iniziare un’attività per un risultato in questo caso utilizziamo la chiamata startActivityForResult(), per poter ricevere il risultato dalla successiva attività bisogna implementare il metodo onActivityResult(), in modo che l’attività restituisce un intent all’interno del metodo. 8 Applicazione su Android di acquisizione di misure accelerometriche Terminazione di un’Attività: Per terminare un’attività o utilizziamo il metodo finish() oppure il metodo per terminare un’attività precedentemente inizializzata finishActivity( ). In realtà nella maggior parte dei casi non si dovrebbero utilizzare tali metodi, ma è proprio il sistema Android a gestire la vita delle attività. Gestione dell’Activity Lifecycle: Per la creazione di un’applicazione forte e flessibile occorre implementare per bene i metodi di callback utilizzati nel ciclo di vita dell’activity. Il ciclo di vita di un’attività è direttamente influenzato dalle associazioni con altre attività, dai suoi task e dalla back stack. Un’attività esiste in tre stati fondamentali: Ripreso: l’attività è in primo piano dello schermo ed ha l’attenzione dell’utente. Spesso questo stato è noto come “running”. In Pausa: Un’altra attività è in primo piano ed ha attenzione, ma questa è ancora visibile. Un’attività in pausa è in vita, il suo oggetto rimane in memoria mantenendo tutte le informazioni necessarie e rimane collegata al gestore della finestra, tuttavia può essere uccisa dal sistema in condizioni di memoria estremamente basse. Arrestata: Un’attività arrestata è completamente oscurata da un’altra pertanto si dice che agisce in “background”. E’ sempre ancora in vita con l’oggetto dell’attività che rimane in memoria, ma non è collegato al gestore della finestra. Poiché non è più visibile all’utente, può essere ucciso dal sistema quando è richiesta memoria altrove. Quando un’attività è in pausa o è arrestata, il sistema può rilasciare l’applicazione dalla memoria, richiedere che finisca o semplicemente può uccidere i suoi processi. Fig. 01 – Ciclo di vita dell’attività 9 Applicazione su Android di acquisizione di misure accelerometriche Le procedure di callback per la gestione dell’attività si dividono in tre cicli annidati fondamentali nel Lifetime dell’attività: 1. “Entire Lifetime” dell’attività è legata tra le due chiamate di onCreate() e onDestroy() che identificano la creazione e la terminazione dell’attività. E’ quindi uno stato globale dove le risorse vengono assegnate all’atto della creazione e rilasciate alla terminazione. 2. “Visible Lifetime” legata tra le due chiamate onStart() e onStop(). In questo tempo l’utente può vedere l’attività sullo schermo ed interagire con essa. E’ possibile mantenere le risorse che sono necessarie a mostrare l’attività all’utente. Tali chiamate si alternano più volte durante il ciclo di vita dell’attività poiché queste si alternano ad essere visibili o nascoste dall’utente. 3. “Foreground Lifetime” legata tra le chiamate onResume() e onPause(). L’attività si trova di fronte le altre sullo schermo avendo l’attenzione dell’utente. Un’attività effettua spesso questo tipo di transizione. Alcuni metodi non sono “Killable” altri invece sì (onPause(), onStop(), onDestroy()): ad esempio un’attività diventa killable quando passa dallo stato di onPause() fin quando non ritorna allo stato onResume(). Task e Back Stack: “Un task è una collezione di attività che interagiscono con l’utente per fornire un’operazione.” Tali attività vengono gestiti nello stack con gestione LIFO. Quando aggiungo un’attività questa viene inserita nella coda, nel momento in cui la estraggo sto effettuando una navigazione all’indietro, usando il tasto “back” l’attività viene eliminata dalla coda. Fig. 02 – Esempio inserimento, estrazione dallo “back stack” Un task è un’unità coesiva che può muoversi verso il “background”, quando l’utente inizia un nuovo task o passa alla schermata di Home, tramite il pulsante Home. Nel background tutte le attività sono in stato di “arrestato” mentre il back stack rimane intatto. Un task può invece ritornare nel foreground, riprendendo quello che l’utente aveva lasciato precedentemente. Ad esempio presi in considerazioni due Task A e B con un certo numero di attività ciascuno, se inizialmente il Task corrente è quello A e viene premuto il tasto Home, il task entra in secondo piano e il sistema avvia il task B con il proprio stack con le sue attività. Qualora l’utente dovesse tornare nell’home e viene selezionata l’applicazione iniziata dal Task A, questo torna in foreground con le proprie attività nello stack, riprendendo l’esecuzione. L’utente può decidere se tornare al task B o iniziare un nuovo task. Questo è un esempio di multitasking su sistema Android. Possono essere mantenuti più task in background, ma il sistema può decidere se distruggerli in modo da preservare la memoria per nuove attività. Un ulteriore particolarità è data dal fatto che all’interno della pila è possibile istanziare più volte la stessa attività da più Task. In modo che quando l’utente naviga all’indietro con il pulsante indietro, ogni istanza si rivela per l’ordine con cui le attività sono state aperte. Tuttavia è possibile modificare tale comportamento. Le operazioni di modifica del comportamento del sistema vengono gestite dal “Gestore dei Task”. 10 Applicazione su Android di acquisizione di misure accelerometriche Salvataggio di Stato: Qualora il sistema dovesse distruggere un’attività per riservare memoria, viene distrutto l’oggetto, pertanto il sistema non può semplicemente riprendere l’attività. Deve quindi essere ricreato l’oggetto Activity se l’utente vuole riprendere tale esecuzione. Tuttavia l’utente è ignaro di tale distruzione e si aspetta l’attività così com’era. In questa situazione per preservare l’attività è possibile implementare un metodo di callback che permette di salvare le informazioni relative all’attività in questione: onSaveIstanceState(). Fig. 03 – Salvataggio istanza Il salvataggio dell’attività permette dunque qualora l’attività arrestata o in pausa viene distrutta mediante il metodo onRestoreIstanceState() permette di rilanciare l’attività, ripristinando lo stato dell’attività. 1.2.2 Service Un servizio è un ulteriore application component che può eseguire operazioni lunghe in background e non prevede un’interfaccia utente. Un altro componente può iniziare un servizio e continuerà ad eseguire in background anche se l’utente cambia applicazione. Un componente può legarsi ad un servizio per interagire con esso ed eseguire un Inter Process Communication (IPC). Un servizio esiste in due forme: 1. Started: un servizio è “avviato”, quando un componente, come un’attività, invoca startService(). Una volta avviato, un servizio può eseguire nel background a tempo indeterminato, anche se il componente è stato distrutto. Un servizio “started” nel momento in cui compie un’operazione non ritorna alcun risultato al componente chiamante. Ad esempio un’operazione di download dalla rete. 2. Bound: un servizio è “legato”, quando un’application component è legato dalla chiamata bindService(). Un servizio “bound” offre un’interfaccia client-server che permette ai componenti di interagire con il servizio, mandando richieste, ricevendo risultati, anche facendolo tramite i processi grazie all’aiuto dell’IPC. Questo esegue solo quando è legato a un altro componente. Più componenti possono legarsi al servizio, ma quando vengono slegati, il servizio viene distrutto. 11 Applicazione su Android di acquisizione di misure accelerometriche Pur se i due servizi vengono trattati in maniera separata, il servizio funziona in entrambi i modi. Si chiama un servizio “started” con onStartCommand() quello “bounded” con onBind(). Indipendentemente da come viene definito il servizio, qualsiasi applicazione può iniziare un servizio (anche in applicazioni separate) come si inizia un’attività: tramite un Intent. L’unica attenzione che bisogna fare è che un servizio viene eseguito nel thread principale del processo ospitante, non bisogna creare un proprio thread e non viene lanciato in un processo separato. Per creare un servizio bisogna richiamare la sottoclasse “Service”. [7] Nell’implementazione bisogna ridefinire alcuni metodi di callback, necessari per il ciclo di vita del servizio: - onStartCommand(): metodo che richiede che un servizio venga inizializzato come “started” attraverso startService(), e sia lanciato nel background indefinitamente. Legato a tale metodo ci saranno quelli per arrestare il servizio tramite o stopSelf() o stopService(). - onBind(): metodo utilizzato per legare un componente ad un servizio tramite la chiamata bindService(). Nell’implementazione bisogna provvedere un’interfaccia che i clienti usano per comunicare con il servizio. - onCreate(): metodo usato quando il servizio è stato appena creato, esegue una procedura una sola volta. Se il servizio è già in esecuzione, tale metodo non viene chiamato. - onDestroy(): metodo chiamato quando il servizio non è più utilizzato ed è stato distrutto. Viene implementato per ripulire le risorse come: thread, ricevitori, ecc. Il sistema Android sarà forzato ad interrompere un servizio solo quando la memoria è insufficiente e deve recuperare le risorse di sistema. Se il servizio è legato all'attività in primo piano dell'utente, le probabilità venga ucciso sono molto basse. In caso contrario, se il servizio è started, il sistema ridurrà la sua posizione nella lista dei task in background nel corso del tempo in modo da diventare sensibile all'uccisione. Bisogna quindi gestire una modalità di corretto riavvio da parte del sistema. In modo che sia il sistema a riavviare il servizio qualora le risorse diventino di nuovo disponibili. Per quanto riguarda invece la gestione del ciclo di vita di un servizio possiamo individuare come nel caso delle attività vengono definite i metodi di callback che verranno monitorati attraverso due cicli annidati: “Entire Lifetime”: contenuta nell’intervallo di tempo tra i metodi onCreate() e onDestroy(). Questi vengono chiamati per tutti i servizi, se sono stati creati da startService() o bindService(). “Active Lifetime”: tale intervallo di tempo inizia dalle chiamate onStartCommand() o onBind(), a tali metodi è consegnato l’Intent che è stato fornito dai rispettivi metodi startService() o bindService(). La durata finale di questo intervallo dipende dal servizio: se si tratta di uno “started” il termine coincide con l’intero Lifetime altrimenti se è “bound” il termine coincide con la chiamata del metodo onUnbind(). 12 Applicazione su Android di acquisizione di misure accelerometriche Fig. 04 – Lifetime di un servizio started (sinistra) e bound (destra) Un ultimo aspetto riguarda i servizi “foreground”, considerati come dei particolari servizi utilizzati correntemente dall’utente e per questo non candidati ad essere uccisi dal sistema quando c’è poca memoria. Un Foreground Service deve prevedere una “notifica” per la “status bar”, il che significa che la notifica non può essere respinta a meno che il servizio non sia stato arrestato o rimosso dal foreground. 1.2.3 Content Provider Content Providers gestiscono l’accesso ad un insieme strutturato di dati. Prevedono un incapsulamento del dato e forniscono dei meccanismi per la definizione della “data security”. Pertanto sono delle interfacce standard che collegano i dati in un processo, con il codice che esegue in un altro processo. Quando bisogna eseguire l’accesso ai dati in un content provider, si utilizza un particolare oggetto nel contesto dell’applicazione detto “ContentResolver”, che permette la comunicazione usando il provider come un client. L’oggetto ContentResolver comunica con un ulteriore oggetto detto “Provider”, implementato dalla classe ContentProvider. Questo oggetto riceve i dati richiesti dal cliente, per eseguire le operazioni richieste, ritornando il risultato. Non è necessario sviluppare un proprio provider, se non si intende condividere dati con altre applicazioni. Bisogna, tuttavia, implementarlo quando è necessario copiare e incollare dati complessi o file da un’applicazione ad un’altra. Android include un content provider per la gestione dei dati: audio, video, immagini e informazioni personali. Tale documentazione è fornita dal pacchetto <android.provider>, che anche se con alcune restrizioni, tali provider sono accessibili da qualsiasi applicazione Android. 1. Content Provider di Base: Fornisce informazioni sull’accesso ai dati in un content provider quando i dati sono organizzati in tabelle. Fornisce un insieme di procedure di tipo IPC e per l’accesso sicuro ai dati. 13 Applicazione su Android di acquisizione di misure accelerometriche 2. Creazione di un Content Provider: Implementiamo un provider come una o più classi all’interno di un’applicazione Android. Una delle classi implementa una sotto classe “ContentProvider” che è il ponte tra il provider e le altre applicazioni. Questi sono significativi per dare la disponibilità dei dati ad altre applicazioni che con le proprie attività permettono all’utente di interrogare e modificare i dati organizzati dal provider. 3. Calendar Provider: Il Calendar Provider è un deposito per gli eventi del calendario dell’utente. Vengono fornite una serie di API per l’esecuzione di: ricerche, inserimenti, modifiche e cancellazioni su tale calendari. Tali API possono essere usate dall’applicazione e dai “sync adapters”, ovviamente la gestione e le regole dipendono dal tipo di programma sta eseguendo la chiamata. 1.2.4 Intent La maggior parte dei componenti di un’applicazione vengono attivati da un messaggio, detto “Intent”. L’oggetto intent è una struttura dati passiva che possiede al suo interno una descrizione astratta dell’operazione che deve eseguire o nel caso delle trasmissioni una descrizione di qualcosa che deve ed è stato annunciato. Vengono divisi in due gruppi fondamentali: 1. Explicit Intents: viene inserito nel campo destinazione il nome della componente. Poiché i nomi delle altre componenti non sono noti agli sviluppatori di altre applicazioni, questo tipo di messaggi vengono utilizzati nelle “applicazioni interne”. 2. Implicit Intent: nel campo destinazione non viene inserito il nome della componente. Questo modello di messaggi viene utilizzato per avviare nuove componenti in altre applicazioni. Nel caso dell’esplicito, Android, gestisce facilmente la situazione fornendo il nome della classe con cui lavorare. Tuttavia bisogna trovare un’ottima soluzione anche nel caso degli intent impliciti. Poiché sono in assenza di un obiettivo disegnato, il sistema Android deve trovare la migliore componente in grado di gestire l’intent. Questo per tanto viene gestito confrontando il contenuto dell’oggetto Intent con dei filtri di Intent, strutture associate ai componenti in grado di ricevere intent. Tali filtri pubblicizzano le capacità di un componente e delimitano gli intent che può gestire. Qualora il componente ha i filtri di Intenti può ricevere intenti espliciti e impliciti, se non li ha può ricevere solo intenti espliciti. 1.3 Gestione dei Processi e dei Threads Quando un componente d’applicazione parte e non ci sono altri componenti in esecuzione, il sistema Android fa partire un nuovo processo Linux per l’applicazione con un singolo thread di esecuzione chiamato “main” thread. Di default, tutte le componenti della stessa applicazione sono in esecuzione nello stesso processo e thread. Se un componente invece parte e ci sono già altri processi per l’applicazione, allora il componente viene avviato all’interno di tale processo ed utilizza lo stesso thread di esecuzione. Comunque, è possibile avviare differenti componenti in separati processi, ed è possibile creare dei thread aggiuntivi per ogni processo. Processi: 14 Applicazione su Android di acquisizione di misure accelerometriche Di default, tutti i componenti della stessa applicazione eseguono nello stesso processo e molte applicazioni non dovrebbero cambiare tale comportamento. Tuttavia si può cambiare il comportamento modificandolo tramite il manifesto. Il manifesto supporta un attributo android:process che può specificare in quale componenti esegue il processo. Si può modificare questo attributo in modo tale da poter avere componenti che eseguono nel proprio processo o componenti che condividono lo stesso processo oppure far in modo che una differente applicazione esegua nello stesso processo (sempre che si condivida lo stesso Linux user ID e siano identificati con gli stessi certificati). Android può decidere di arrestare il processo ad un certo punto: ad esempio quando la memoria è bassa ed è richiesta da altri processi che stanno servendo l’utente. Il componente al suo interno viene ucciso e distrutto. Un processo è avviato qualora ci sia la necessità di lavoro per lui. La decisione del processo da uccidere viene scelta (attraverso una serie di regole) dal sistema Android, che pesa la loro importanza relativa all’utente, la decisione dipende quindi dallo stato di funzionamento dei componenti in tale processo. Ciclo di vita dei Processi: Il sistema Android prova a mantenere un processo per molto tempo, fino al momento in cui bisogna rimuovere un vecchio processo per recuperare memoria per un processo nuovo o uno più importante. Il sistema gestisce quindi una “gerarchia di importanza”, per il rimpiazzamento dei processi, basata sulle componenti in esecuzioni nel processo e lo stato dei componenti. I processi con meno importanza vengono eliminati per prima, poi quelli successivi a quelli a minore importanza, fino a risanare le risorse di sistema. Esistono cinque livelli di gerarchia di importanza (il primo è quello più importante, ucciso per ultimo): 1. “Processo Foreground”: E’ necessario per ciò che sta facendo l’utente. E’ di “primo piano” se valgono determinate condizioni: - Ospita un’attività con cui l’utente sta interagendo. - Ospita un servizio legato ad un’attività con cui l’utente sta interagendo. - Ospita un servizio in primo piano che è in esecuzione. - Ospita un servizio che sta eseguendo uno dei suoi callback del lifecycle. In generale, esistono pochi processi foreground. Questi vengono uccisi in ultima istanza, se la memoria è così bassa che non possono continuare a funzionare. In tale punto, il dispositivo ha raggiunto uno stato di paginazione della memoria, in cui è necessario uccidere dei processi in primo piano per mantenere l’interfaccia utente reattiva. 2. “Processo Visible”: Non ha componenti in primo piano, ma è in grado di influenzare ciò che vede l’utente sullo schermo. Un processo “visibile” se valgono determinate condizioni: - Ospita un’attività che non è in primo piano, ma è visibile dall’utente. - Ospita un servizio legato ad un’attività visibile. Un processo visibile è estremamente necessario e non dovrebbe essere interrotto a meno che questo non sia necessario per mantenere in esecuzione i processi in primo piano. 3. “Processo Service”: Un processo che sta eseguendo un servizio che è stato avviato da startService() e non rientra nelle categorie superiori è detto di “servizio”. Questi non sono direttamente legati a tutto ciò che l’utente vede, fanno operazioni di cui di solito l’utente ci tiene, quindi il sistema li tiene in vita a meno che non ci sia abbastanza memoria per tenerli insieme a quelli di primo piano e visibili. 15 Applicazione su Android di acquisizione di misure accelerometriche 4. “Processo Background”: Un Processo di “sfondo” è un processo che mantiene un’attività che non è attualmente visibile all’utente. Questo tipo di processo non ha alcun tipo di impatto diretto sulla “user experience”, ed il sistema può ucciderli in qualsiasi momento per recuperare la memoria per processi di primo piano, visibili o di servizio. Quando ci sono tanti processi di sfondo questi vengono mantenuti in una lista con gestione LRU (least recently used) in modo da assicurare che il processo con l’attività più recentemente utilizzato dall’utente è l’ultimo ad essere ucciso. Se un processo implementa correttamente i metodi del ciclo di vita e salva il suo stato attuale, uccidendo il processo non avrà effetto visibile sull’user experience, perché quando l’utente si sposta all’indietro con l’attività, questa avrà ripristinato tutto il suo stato visibile. 5. “Processo Empty”: Un processo che non ha alcuna componente attiva è detto “vuoto”. L’unica ragione per mantenere questo tipo di processo è a fini di caching, per migliorare i tempi di avvio la prossima volta che un componente deve essere eseguito in tale processo. Il sistema uccide spesso questi processi, in modo da bilanciare le risorse di sistema complessive tra le cache di processo e le cache del kernel sottostante. Android colloca un processo a più alto livello che può, in base all’importanza delle componenti attive nel processo. Il posizionamento di un processo può aumentare a causa di altri processi che dipendono da esso, un processo dipendente da un altro non può essere classificato inferiore al processo a cui dipende. Perché un processo in esecuzione di un servizio è posto più in alto di un processo con attività in background, un’attività che inizia una lunga operazione potrebbe fare bene per avviare un processo per tale operazione, piuttosto che creare un thread di lavoro: ad esempio un’attività che deve caricare una foto su un sito web, avvia un servizio in background che possa continuare il caricamento anche se l’utente lascia l’attività. In questo modo ci sarà almeno un “processo di servizio” a priorità, indipendentemente dall’attività. Thread: Quando un’applicazione viene lanciata, il sistema crea un thread di esecuzione per l’applicazione, chiamato “main”. Questo thread è importante, perché si occupa dell’invio agli eventi dell’interfaccia utente. Tale thread interagisce con le componenti dall’Android “UI toolkit”: per questo il “main thread” viene chiamato il “UI thread” (User-Interface Thread). Il sistema non crea un thread separato per ogni componente. Tutti i componenti che eseguono nello stesso processo sono istanziati nell’UI thread, e le system call ad ogni componente sono inviate da quel thread. Quando l’applicazione svolge un lavoro intenso in risposta ad interazioni con l’utente, questo singolo thread è un modello che produce scarse prestazioni, a meno che l’applicazione non sia stata implementata bene. Quando il thread si blocca, non è possibile inviare alcun tipo di evento, quindi dal punto di vista dell’utente l’applicazione sembra bloccarsi. Ancora peggio è quando l’UI thread si blocca per più di circa cinque secondi, viene ritornato il messaggio “l’applicazione non risponde” (ANR) all’utente, che mal contento può disinstallare l’applicazione. 16 Applicazione su Android di acquisizione di misure accelerometriche L’Android UI toolkit non è thread-safe. [Thread-Safe: Nell’ambito del multithreading, per indicare che una porzione di codice si comporta in modo corretto nel caso di esecuzioni multiple da parte di più thread. In particolare è possibile che i vari thread possono avere accesso alle informazioni condivise, ma queste sono accessibili solo da un thread alla volta]. [8] Non si deve manipolare l’interfaccia utente da un thread di lavoro, bisogna eseguire la manipolazione dell’interfaccia utente dall’UI thread. Due sono le regole fondamentali: Non bloccare l’UI thread. Non accedere all’Android UI toolkit da fuori l’UI thread. Worker Thread: A causa del modello a singolo thread, è necessario che valgano le due regole. Non bisogna far in modo che “non si deve bloccare l’UI thread”. Se bisogna eseguire operazioni che non sono istantanee, è necessario assicurarsi che siano in thread separati (thread di “background” o “worker”). La seconda regola è quella di “non accedere all’Android UI toolkit al di fuori del UI thread”, se non si rispetta tale regola è possibile che si verificano eventi anomali e inaspettati, che possono richiedere molto tempo. Android, risolve tale problema, inserendo una lista di metodi che permettono l’accesso all’UI thread da altri thread: Activity.runOnUiThread(Runnable), View.post(Runnable), View.postDelayed(Runnable, long). Non appena la complessità dell’operazione aumenta, questo tipo di codice diventa complicato e difficile da mantenere. Per gestire queste situazioni complesse con un thread lavoratore, è consigliato utilizzare un gestore nel proprio worker thread, per elaborare i messaggi consegnati dall’UI thread. La migliore soluzione è quella di estendere la classe AsyncTask, che semplifica l’esecuzione del worker thread, processi che necessitano l’interazione con l’interfaccia utente. AsyncTask consente di eseguire un lavoro asincrono sull’interfaccia utente. Svolge il blocco di operazioni in un worker thread e pubblica i risulta sull’UI thread, senza gestire thread o gestori di thread. Bisogna implementare la sottoclasse AsyncTask e il metodo di callback doInBackground(), eseguito in un pool di thread in background. Per aggiornare la UI in maniera sicura, bisogna implementare onPostExecute() che fornisce il risultato da doInBackground() ed esegue nel UI thread. Si può eseguire il task chiamando execute() dall’UI thread. Richiamiamo delle proprietà di tale classe: - Si può specificare il tipo di parametri, i valori di progresso, i valori finali del task. - Il metodo doInBackground() esegue automaticamente in un worker thread. - onPreExecute(), onPostExecute() e onProgressUpdate() sono invocato sull’UI thread. - Il valore ritornato da doInBackgroun() è inviato da onPostExecute(). - Chiami publishProgress() per eseguire onProgressUpdate() sull’UI thread. - Si può cancellare il task in ogni momento, da qualsiasi thread. Metodi Thread-Safe: In alcune situazioni, i metodi implementati potrebbero essere chiamati da più di un thread, pertanto devono essere thread-safe. Questo è vero soprattutto per i metodi che possono essere chiamate in maniera remota. Quando una chiamata di un metodo implementato in un IBinder (interfaccia per un oggetto remoto) [9] originato nello stesso processo nel quale sta eseguendo, il metodo è eseguito nel thread del chiamante. Quando la chiamata ha origine in un altro processo, il metodo viene eseguito in un thread scelto da un pool di thread di cui il sistema mantiene nello stesso processo come l’IBinder. 17 Applicazione su Android di acquisizione di misure accelerometriche Poiché un servizio può avere più di un cliente, più di un pool di thread può impegnare lo stesso metodo IBinder nello stesso momento. I metodi IBinder devono essere implementati threadsafe. Analogamente un Content Provider può ricevere richieste di dati che hanno origine in altri processi. Se le classi ContentResolver e ContentProvider nascondono i dettagli di come l’IPC è gestito, i metodi che rispondono a tali richieste sono chiamati da un pool di thread nel processo del content provider, non l’UI thread per il processo. Dato che questi metodi possono essere chiamati da qualsiasi numero di thread contemporaneamente, anch’essi devono essere thread-safe. Interprocess Communication: Android offre un meccanismo per IPC usando “remote procedure calls” (RPCs), nel quale un metodo viene chiamato da un’attività o un ulteriore componente, ma eseguito in modalità remota, con qualsiasi risultato restituito al chiamante. Ciò comporta decomporre un metodo chiamato ed i suoi dati, ad un livello del sistema operativo che riesce a comprenderlo, trasmettendolo dal processo locale ed il suo spazio di indirizzamento fino al processo remoto ed il suo spazio di indirizzamento, infine la chiamata viene ricomposta e ricostruita. I valori restituiti vengono trasmessi nella direzione opposta. Android fornisce i meccanismi IPC per svolgere tali operazioni, in modo da definire e implementare l’interfaccia RPC. Per eseguire IPC, l’applicazione deve legarsi ad un servizio, tramite bindService(). 18 Applicazione su Android di acquisizione di misure accelerometriche Capitolo 2 Accelerometro su Android In questo capitolo verranno trattati i metodi di gestione dei sensori accelerometrici sul sistema Android. Con una prima introduzione dei meccanismi hardware dei sensori: in particolare quello di movimento, per poi entrare nel dettaglio dell’accelerometro con le varie classi e API (Application Programming Interface) per la gestione di tali sensori. Il risultato verrà finalizzato con un esempio applicativo di una misurazione di un’acquisizione accelerometrica e con una visualizzazione di tale acquisizione. Per quanto riguarda il linguaggio di programmazione utilizziamo Java, la piattaforma di sviluppo per Android sarà l’SDK (Software Development Kit) con l’ADT per eclipse. 2.1 Sensori su Android Molti dei dispositivi Android sono costruiti con dei sensori incorporati che misurano il movimento, orientamento e condizioni ambientale. Questi sono capaci di provvedere dati grezzi con alta fedeltà e precisione, e utili se bisogna monitorare la posizione, il movimento nelle tre dimensioni o le condizioni ambientali del congegno. Android provvede tre categorie di sensori: - Sensori di Movimento: questi sensori misurano la forza di accelerazione e la forza di rotazione lungo i tre assi. La categoria che include: accelerometro, sensori di gravità, giroscopio, sensori del vettore rotazionale. - Sensori Ambientali: questi sensori misurano le variazioni dei parametri ambientali: come la temperatura, pressione, illuminazione e umidità. La categoria include: termometro, barometro e fotometro. - Sensori di Posizione: questi sensori misurano la posizione fisica del dispositivo. Include magnetometri e sensori di orientamento. Il framework provvede un insieme di classi ed interfacce in grado di eseguire una varietà di processi legati ai sensori. Alcune operazioni standard sono: - Determinare quale sensore è disponibile. - Determinare le capacità individuale dei sensori, come: raggio massimo, requisiti di potenza e risoluzione. 19 Applicazione su Android di acquisizione di misure accelerometriche - Acquisizioni di dati grezzi e definire il rate minimo a cui si acquisiscono i dati dei sensori. Il framework che Android offre per i sensori offre l’accesso a numerosi tipi di sensori. Alcuni sono “hardware-based” ed altri “software-based”. Quelli hardware-based sono quelli con componenti fisici installati all’interno del dispositivo, permettono la misurazione da un’acquisizione diretta della grandezza fisica. (vedi accelerometro, ecc.) Quelli software-based sono quelli che non hanno strumenti di misura installati nel dispositivo, e per questo sono detti imitatori di sensori hardware-based. Loro derivano i loro dati da uno o più sensori hardware-based e sono detti “sensori virtuali” o “sintetici”. (vedi il sensore di accelerazione lineare) Tabella 01 – Tipi di Sensori (con caratteristiche) supportati da Android Sensore TYPE_ACCELEROMETER Tipo Hardware Descrizione Misura la forza di accelerazione in m/s2 TYPE_LIGHT Hardware Misura il livello di luminosità in lx TYPE_LINEAR_ACCELERATION Hardware / Misura la forza di Software accelerazione in m/s2 esclusa la forza di gravità TYPE_ORIENTATION Software Misura il grado di rotazione del dispositivo. Usa due sensori (magnetometro, sensore di gravità) con il metodo getRotationMatrix() Uso Comune Rivelamento di movimento Regolazione schermo Rivelamento di movimento Rivelamento di posizione Per l’acquisizione dei dati utilizziamo tali sensori nel framework. Il framework Android offre un pacchetto android.harware che include le seguenti classi e interfacce: - SensorManager: si usa tale classe per creare un’istanza di un sensore di servizio. Tale classe prevede numerosi metodi per l’accesso, ascolto dei sensori e acquisizione dei dati. Fornisce un insieme di costanti usate per fissare i tassi di acquisizione, calibrare i sensori e fornire l’accuratezza dello strumento. - Sensor: si usa tale classe per creare un’istanza di un sensore specifico, in particolare per utilizzare dei metodi per determinare la capacità del sensore. - SensorEvent: si usa tale classe per istanziare un oggetto “sensor event”, che prelevi informazioni dal sensore di eventi. Informazioni quali: dati grezzi, il tipo di sensore che ha generato l’evento e l’accuratezza dei dati. - SensorEventListener: si usa tale interfaccia per creare due metodi di callback che ricevono notifiche (sensore di eventi) quando cambia il valore o la precisione di un sensore. In una tipica applicazione si usano questi “sensor-related APIs” ad eseguire due processi base: 1. Identificazione dei sensori e delle loro capacità: Tale identificazione a runtime è utile se l’applicazione ha caratteristiche di cui far affidamento su un particolare tipo di sensore o una capacità di un sensore. In questo modo si può scegliere un sensore di un dato tipo con la sua implementazione che ha le migliori prestazioni per l’applicazione. 20 Applicazione su Android di acquisizione di misure accelerometriche Per identificare il sensore di un dispositivo, bisogna realizzare un riferimento con il sensore di servizio. Creo un’istanza del SensorManager chiamando il metodo getSystemService() passandogli come argomento SENSOR_SERVICE della classe Context (superclasse di Activity). private SensorManager mySM = (SensorManager) getSystemService(Context.SENSOR_SERVICE); Per acquisire quello che si è ascoltato da ogni sensore usiamo il metodo getSensorList() passandogli per argomento la costante TYPE_ALL o di un singolo sensore usando la costante predefinita. (Ad esempio uso TYPE_ACCELEROMETER per l’accelerometro) List<Sensor> deviceSensors = mySM.getSensorList(Sensor.TYPE_ALL); Si può inoltre determinare se un dispositivo possiede un determinato sensore tramite il metodo getDefaultSensor() passandogli come argomento il tipo di sensore, la chiamata ritorna ‘null’ quando non esiste il sensore specifico. private SensorManager mySM = (SensorManager) getSystemService(Context.SENSOR_SERVICE); if (mySM.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null ) { //Successo! Esiste il sensore. } else { //Fallimento! Non c’è l’accelerometro. } Usiamo i metodi pubblici della classe Sensor per determinare le capacità dei dispositivi. Utile per far in modo che l’applicazione si comporti in maniera differente in base a quali sensori o capacità sono presenti sul dispositivo: ad esempio usiamo getResolution() o getMaximumRange() per ottenere la risoluzione e il raggio massimo di misura, getPower() per i requisiti di potenza dello strumento oppure usare getMinDelay() utile perché fornisce il tasso massimo di acquisizione dei dati, il quale ritorna il minimo intervallo di tempo che un sensore ci impiega per rilevare i dati. 2. Monitoraggio dei sensori di eventi: Il monitoraggio fornisce il come vengono acquisiti i dati dei sensori, infatti un sensore rileva un cambiamento nei parametri della misurazione. Un sensore di evento fornisce: il nome del sensore che ha scatenato l’evento, data e orario dell’evento, la precisione dell’evento e i dati non elaborati. I metodi forniti dall’interfaccia SensorEventListener sono quelli utilizzati per monitorare: onSensorChanged() e onAccuracyChanged(). Questo quando: Cambia la precisione di un sensore: il sistema invoca onAccuracyChanged(), che fornisce un riferimento all’oggetto Sensor che è cambiato e la nuova accuratezza del sensore: rappresentata da quattro costanti. (SENSOR_STATUS_ACCURACY_LOW, SENSOR_STATUS_ACCURACY_MEDIUM, SENSOR_STATUS_ACCURACY_HIGH, SENSOR_STATUS_UNRELIABLE ) Un sensore riporta un nuovo valore: il sistema invoca onSensorChanged(), con un riferimento all’oggetto SensorEvent e le informazioni legate a tale oggetto. 2.1.1 Sensori di Movimento (Accelerometro) La piattaforma Android prevede un insieme di sensori che rilevano il movimento del dispositivo. Due sono solo hardware-based come l’accelerometro ed il giroscopio altri tre possono essere sia hardware-based o software-based come il sensore di gravità, di accelerazione lineare e del vettore di rotazione. Ad esempio i sensori software-based derivano 21 Applicazione su Android di acquisizione di misure accelerometriche i dati da strumenti hardware come magnetometro o accelerometro mentre quelli hardware utilizzano degli strumenti di misura installati nell’hardware del dispositivo. Tutti i dispositivi Android includono un accelerometro, mentre la disponibilità dei sensori software-based dipende da uno o più sensori hardware che deriva i loro dati. I sensori di movimento sono usati per monitorare il movimento come ad esempio l’inclinazione, le vibrazioni, le oscillazioni. Il movimento è solitamente il riflesso di un ingresso diretto dell’utente, in questo caso è relativa al frame di riferimento o del dispositivo o dell’applicazione (come: il controllo di un pallone in un gioco). Oppure può essere il riflesso di un ambiente fisico in cui è posizionato il dispositivo, in tale caso è relativo al frame di riferimento del mondo considerato (come: movimento mentre si guida un automobile). Tali sensori solitamente vengono combinati con sensori di posizione, ad esempio il sensore di campo geomagnetico, per determinare la posizione relativa nel frame di riferimento del mondo (come nel caso di un’applicazione di un navigatore satellitare che valuta la velocità relativa con cui sta viaggiando l’autovettura o il tempo necessario per arrivare a destinazione con quella velocità). Tutti i sensori di movimento ritornano un array multi-dimensionale di valori per ogni SensorEvent rispettivo ai tre assi di coordinate di riferimento (x,y,z): come nel caso dell’accelerometro. Tali dati sono ritornati all’interno di un array di tipo ‘float’ chiamato ‘values’. Tabella 02 – vettore ‘values’ nel caso dell’accelerometro Sensor Dati SensorEvent SensorEvent.values[0] Descrizione Faccelerazione lungo x Unità di Misura TYPE_ACCELEROMETER SensorEvent.values[1] Faccelerazione lungo y m/s2 SensorEvent.values[2] Faccelerazione lungo z Utilizzo dell’Accelerometro: Per poter utilizzare l’accelerometro si usa tale codice: private SensorManager mySM = (SensorManager) getSystemService(Context.SENSOR_SERVICE); private Sensor myS = mySM.getSensorDefault(Sensor.TYPE_ACCELEROMETER); Un sensore accelerometro rileva l’accelerazione applicata al dispositivo (Ad), inclusa la forza di gravità, dalle misure della forze applicate al dispositivo (Fs) e valutandone la massa ‘m’: Ad = - ∑ Fs/m (2.1) Tale relazione tuttavia è influenzata dalla forza di gravità (g), che modifica l’equazione in: Ad = - g - ∑ Fs/m (2.2) Per tale ragione si ottiene quando il dispositivo è fermo (e non accelera), l’accelerometro legge tale grandezza pari a g = 9.81 m/s2. In maniera duale si ha che quando il dispositivo che è in caduta libera verso il suolo ed ha un’accelerazione reale di 9.81 m/s2, l’accelerometro leggerà un valore di 0 m/s2. Per poter ottenere l’accelerazione reale bisogna rimuovere la gravità dal valore acquisito dallo strumento di misura, tale soluzione si apporta utilizzando un filtro passa-alto. Per isolare il termine della gravità invece usiamo un filtro passa-basso. Ad esempio un’applicazione può essere: public void onSensorChanged(SensorEvent event) { final float alpha = 0.8; //usiamo 0.8 per semplice esempio //filtro passa basso per isolare la gravità (esempio sulla componente dell’asse x) gravity[0] = alpha * gravity[0] + (1 – alpha) * event.values[0]; //filtro passa alto per rimuovere la gravità (esempio sulla component dell’asse x) linear_acceleration[0] = event.values[0] – gravity[0]; 22 Applicazione su Android di acquisizione di misure accelerometriche NB: alpha viene valutato come t / (t + dT) dove t è la costante temporale del filtro passa-basso e corrisponde alla latenza che il filtro aggiunge al sensore di eventi, mentre dT è il tasso di consegna dell’evento. Questo è un metodo, tuttavia ne esistono altre tecniche per il filtraggio. Gli accelerometri come altri sensori usano lo standard del sistema di coordinate. Tale risultato viene proposto quando il dispositivo è fermo su di un tavolo nel suo naturale orientamento: Se si spinge il dispositivo da sinistra (quindi si sposta verso destra), la Ax > 0. Se si spinge il dispositivo dal basso (quindi si muove verso l’alto), la Ay > 0. Se si spinge verso il cielo con accelerazione A m/s2, la Az = A + 9.81, la quale corrisponde a quella del dispositivo (+A m/s2) meno quella della forza di gravità (-9.81 m/s2). In stato di quiete l’accelerazione è Ad = +9.81 m/s2, poiché A=0 m/s2 quindi sarà meno quella di gravità. Fig. 05 – Sistema Coordinate Questo sensore è utile poiché non solo rileva il movimento, ma utilizza una potenza dieci volte inferiore a quella usata da altri sensori. Un aspetto negativo è quello di dover implementare filtri passa-alto e passa-basso per eliminare la forza di gravità o ridurre il rumore. Un sensore simile a quello dell’accelerometro è quello dell’accelerazione lineare, che semplifica il problema dell’introduzione dei filtri passa-alto / passa-basso escludendo l’accelerazione di gravità. Infatti la relazione sarà: accelerazione lineare = accelerazione – accelerazione di gravità (2.3) Tale sensore tuttavia ha un offset, che bisogna rimuovere. Il modo più semplice per rimuoverlo è quello di creare una fase di calibrazione nell’applicazione, così che durante tale fase si richiede di fissare il dispositivo sul tavolo per leggere gli offset lungo i tre assi. Così da sottrarre al dato acquisito il valore dell’offset. 2.2 Classi e API dell’Accelerometro Il pacchetto “android.hardware” [10] prevede un insieme di classi e interfacce per la gestione dei sensori compresi nei dispositivi Android. Prevede due Interfacce: 1. SensorEventListener. 2. SensorListener. Le classi fondamentali sono: 1. Sensor 2. SensorEvent 3. SensorManager 2.2.1 Classi Sensor: 23 Applicazione su Android di acquisizione di misure accelerometriche Tale classe prevede una lista di costanti di tipo intero che vengono associati al tipo di sensore che si vuole utilizzare. Tale lista viene usata dal metodo, implementato nella classe SensorManager, getSensorList(int) che preleva la lista dei sensori disponibili. In tale lista troviamo anche la costante che descrive l’accelerometro: int TYPE_ACCELEROMETER il valore di tale costante è 1 (0x00000001). Tale classe provvede anche ad una lista di metodi ‘public’ per operare con tali sensori: Tipo Metodo Descrizione float getMaximiumRange() portata massima del sensore. String getName() ritorna nome del sensore. float getPower() potenza in mA erogata dal sensore. float getResolution() risoluzione del sensore nell’unità del sensore. int getMinDelay() minimo ritardo permesso tra due eventi. int getType() ritorna il tipo generico del sensore. int getVersion() versione del modulo sensore SensorManager: La classe SensorManager permette l’accesso ai sensori del dispositivo. Questo è permesso dopo aver utilizzato la chiamata getSystemService(SENSOR_SERVICE). Di solito conviene al termine delle operazioni o quando l’attività è in pausa disabilitare i sensori di cui non si ha bisogno, in modo da preservare batteria, poiché non vengono disabilitati in automatico quando si spegne lo schermo. Tale classe prevede un insieme di costanti e metodi di cui alcuni di questi sono obsoleti e aggiornati sempre in questa classe o nelle altre tre. I metodi che sono più importanti sono: public Sensor getDefaultSensor(int): uso tale metodo per ottenere un sensore di default dato un certo tipo. Il sensore di uscita può essere anche di tipo composto e i suoi dati possono essere filtrati. L’argomento è il tipo del sensore. public List<Sensor> getSensorList(int): uso tale metodo per avere una lista di sensori disponibili. L’argomento è il tipo del sensore. public boolean registListener(SensorEventListener, Sensor, int, Handler): di tale metodo esiste anche quello senza l’argomento Handler. Lo usiamo per registrare un particolare SensorEventListener in base ad un dato sensore. Ritorna vero se la chiamata ha avuto buon termine. Gli argomenti di questa chiamata sono: - SensorEventListener: oggetto di questa classe. - Sensor: sensore da registrare. - int: il rate con cui vengono consegnati i sensori di eventi. Gli eventi possono essere ritardati o velocizzati a seconda dello specifico rate (SENSOR_DELAY_ unito a NORMAL, UI, GAME o FASTER) tali costanti sono presenti tra i membri pubblici della classe SensorManager. - Handler: il gestore con cui vengono consegnati i sensori di eventi. public void unregisterListener(SensorEventListener, Sensor): annulla la registrazione del SensorEventListener in base al dato sensore. Gli argomenti sono l’oggetto del SensorEventListener e di Sensor. Esiste una versione alternativa che permette di annullare la registrazione di tutti i sensori, omettendo l’argomento Sensor. SensorEvent: La classe, rappresenta un sensore di evento, è legata ai valori generati da SensorEventListener e sono incapsulate all’interno di SensorEvent. Tale classe non ha metodi ma solo membri ad 24 Applicazione su Android di acquisizione di misure accelerometriche accesso ‘public’. Utilizza la definizione del sistema di coordinate usato dal “SensorEvent API”. Riportiamo nella seguente tabella la lista dei membri contenuti nella classe: Tipo Nome Descrizione int accuracy la precisione dell’evento. Sensor sensor il sensore che genera l’evento. long timestamp il tempo in cui è avvenuto l’evento. (in nanosecondi) final float[] values la lunghezza dell’array dipende dal sensore che si monitora. (pag 22) 2.2.2 Interfacce SensorEventListener: Viene usato per ricevere delle notifiche dal SensorManager quando i valori del sensore cambiano. L’approccio è asincrono, ci registriamo attraverso un listener ed in base ad un delay preimpostato riceviamo le notifiche. L’implementazione del listener avviene tramite tale interfaccia. Al suo interno ci sono due metodi con modalità di accesso ‘public’: - abstract void onAccuracyChanged(Sensor, int): viene chiamato quando cambia la precisione del dispositivo, il parametro ‘int’ corrisponde alla nuova accuratezza del dispositivo. - abstract void onSensorChanged(SensorEvent event): viene chiamato quando cambiano i valori di un sensore. L’argomento è l’oggetto event che viene passato come parametro nonostante non lo si possa mantenere, molte volte può far parte di un pool interno ed essere riusato dal framework. SensorListener: Questa interfaccia è molto simile a quella di SensorEventListener con alcune modifiche nei parametri delle funzioni usate, tuttavia è obsoleto rispetto a l’altra interfaccia, pertanto non viene utilizzata. 2.3 Esempio Applicativo Proviamo a valutare un esempio applicativo, riguardante l’acquisizione e la visualizzazione dei dati dovuti alla misurazione da parte dei sensori accelerometrici. Per lo sviluppo dell’applicazione uso l’ambiente di programmazione di Eclipse con l’Android Development Tools. Da eclipse creo un progetto New > Project > Android per avere uno spazio di lavoro per lo sviluppo dell’applicazione. In questo framework vediamo che l’applicazione è divisa in due sottocartelle fondamentali ‘src’ e ‘res’: la prima contenente il codice sorgente in ‘.java’ del programma, il secondo contenente le risorse che verranno usate durante l’esecuzione. Creiamo quindi un’attività di test di questi sensori, la visualizzazione dei dati acquisiti avverrà grazie l'ausilio della classe TextView fissando i dati con setText() e mostrati a video tramite il layout che viene gestito con un file ‘.xml’ interno alla cartella ‘res’ detto ‘main.xml’: nel suo interno ritroviamo un insieme di tag xml che corrispondono ad un ID del layout nel programma così da rendere semplice l’identificazione mediante la chiamata findViewById() . Una volta inclusi i pacchetti necessari, definiamo la classe ‘TestAccelerometerSensor’ come estensione di un’Activity, nella seguente maniera: 25 Applicazione su Android di acquisizione di misure accelerometriche I membri privati corrispondono a delle istanze che verranno spesso richiamate nel corso del programma. package test.accelerometer.sensor; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.content.Context; import android.widget.TextView; //pacchetti per i sensori import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; import android.hardware.SensorEventListener; public class TestAccelerometerSensorActivity extends Activity { private SensorManager gestore; private List<Sensor> accelerometri; private TextView finestra; private TableLayout layout,datiX,datiY,datiZ; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); finestra = (TextView)findViewById(R.id.firstacquisizione); TextView accelerometer = (TextView)findViewById(R.id.presenza); TextView nome = (TextView)findViewById(R.id.nome); datiX = (TextView)findViewById(R.id.datiX); datiY = (TextView)findViewById(R.id.datiY); datiZ = (TextView)findViewById(R.id.datiZ); finestra.setText("Acquisizione Misure"); gestore = (SensorManager) getSystemService(Context.SENSOR_SERVICE); accelerometri = gestore.getSensorList(Sensor.TYPE_ACCELEROMETER); if ((accelerometri.size()) != 0 && (accelerometri) != null) { accelerometer.setText("\nAccelerometro acquisito correttamente.\n"); for (Sensor accelerometro : accelerometri) { accelerometro = gestore.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); nome.setText(accelerometro.getName()); gestore.registerListener(accelerometerListener, accelerometro, SensorManager.SENSOR_DELAY_FASTEST); } } else { accelerometer.setText("\nAccelerometro non disponibile sul dispositivo.\n"); return; } } 26 Applicazione su Android di acquisizione di misure accelerometriche @Override protected void onResume() { super.onResume(); Sensor accelerometro = gestore.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); gestore.registerListener(accelerometerListener, accelerometro, SensorManager.SENSOR_DELAY_NORMAL); } @Override protected void onPause() { super.onPause(); gestore.unregisterListener(accelerometerListener); } @Override protected void onDestroy() { super.onDestroy(); gestore.unregisterListener(accelerometerListener); } SensorEventListener accelerometerListener = new SensorEventListener() { @Override public void onAccuracyChanged(Sensor A,int accuracy) { } @Override public void onSensorChanged(SensorEvent event) { float[] v = new float[3]; v[0] = event.values[0]; v[1] = event.values[1]; v[2] = event.values[2]; datiX.setText("X :"+v[0]); datiY.setText("Y :"+v[1]); datiZ.setText("Z :"+v[2]); } }; } Bisogna effettuare la ridefinizione dei metodi di callback. Ovviamente potremmo utilizzare anche solo onCreate(), questo però apporterebbe dei problemi, specialmente quando il dispositivo viene spento o messo in stand-by, provocando il blocco dell’applicazione. All’interno di onResume() avremo la registrazione del listener, mentre l’annullamento della registrazione all’interno di onDestroy() e onPause(). onCreate() è la prima chiamata che l’applicazione chiamerà: per questo motivo al suo interno ci sono anche le acquisizioni dei sensori. (con sistema di controllo nel caso non dovessero essere presenti gli accelerometri nel dispositivo) Infine bisogna implementare il Listener dall’interfaccia SensorEventListener: ridefinendo anche qui le chiamate onSensorChanged() e onAccuracyChanged(). Solo la prima chiamata inciderà sull’acquisizione dei dati, pertanto è necessario effettuarne l’Override. 27 Applicazione su Android di acquisizione di misure accelerometriche Il for (sensore : lista) è un particolare tipo di costrutto detto ‘ciclo for-each’ usato quindi per scorrere l’intera lista. L’ultima trattazione sarà il file xml che produce il layout: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <TextView android:id="@+id/firstacquisizione" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/presenza" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/nome" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/datiX" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/datiY" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/datiZ" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout> Il risultato finale sarà: Fig 06 – Snapshot Applicazione (SDK Android 4.0.3) 28 Conclusioni In definitiva tali applicazioni sono tuttora in via di sviluppo, con il continuare del tempo avremo sempre più affidabilità e raffinamento dei dati che vengono acquisiti dal dispositivo. Come abbiamo visto anche lo sviluppo delle classi e delle interfacce che permettono di controllare i dati che vengono acquisiti, vengono sempre aggiornate, lasciando indietro quei metodi e/o membri obsoleti. Tale risultato viene considerato ottimo per gli sviluppatori, grazie ad un ottimo ambiente di sviluppo, sempre in aggiornamento. Come il riferimento developer.android.com contente numerose guide, tutorial su riferimenti (quali pacchetti utilizzati da Android contenenti classi e interfacce utilizzate per l’implementazione delle applicazioni dei dispositivi). Risorse contenute all’interno dell’SDK, quali esempi di programmazione di applicazioni su piattaforma Android. Guide sull’istallazione dell’SDK, ambiente di sviluppo e di interfacciamento di un’applicazione direttamente sul proprio personal computer. [11] Non ci resta altro che aspettare le novità che ci riserverà questo sistema operativo che continua la sua crescita verso un mondo, quello degli smartphone, che rimane il fulcro delle nostre odierne vite per le utilità offerte. Ad esempio Android 4.0v fornisce il miglioramento dei sensori in ambienti ospedalieri, abitazioni, centri di fitness e altro ancora. [12] Concludo questo elaborato con dei ringraziamenti ai lettori, sperando che anche voi siate rimasti affascinati e colpiti dal potenziale di tali dispositivi. 29 Bibliografia [1] apple.com/iphone/features/ «Apple Iphone Smartphone» [2] samsung.com/global/microsite/galaxys2/html/index.html [3] biolab.uniroma3.it/materials/Marani_ITA.pdf [4] rcbsrl.it/servizi/rilievi-strumentali/valutazione-esposizione-alle-vibrazioni/ «Ulteriori [5] fim.enea.it/organizzazione/fim-mat-qual/misure-accelerometriche [6] developer.android.com/reference/android/app/Activity.html «Classe Activity» [7] developer.android.com/reference/android/app/Service.html «Classe Service» [8] docs.oracle.com/cd/E19455-01/806-5257/6je9h033e/index.html [9] developer.android.com/reference/android/os/IBinder.html [10] developer.android.com/reference/android/hardware/package-summary.html [11] developer.android.com/ [12] android.com/ «Galaxy Specifiche» «Applicazioni Mediche» Applicazioni Mediche» «Strumentazione Sensori Sismici» «Thread-Safe» «Interfaccia IBinder» «Package Hardware» «Sviluppo App. Android» «Piattaforma Android» 30