Elaborato Celotti Alessandro n46000135

annuncio pubblicitario
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
Scarica