1377507 byte - Corso di Programmazione Mobile

Android
Persistenza dei dati
Corso di programmazione di sistemi mobile
1
Gestione dei file
La maggior parte delle applicazioni Android ha bisogno di salvare i dati, anche solo per salvare lo stato
dell'applicazione in modo che i dati dell'utente non vadano persi.
I principali tipi di memorizzazione presenti sono:
•Database SQLite: possibilità di salvare i dati in un database
• SharedPreferences: per salvare una coppia chiave-valore su un file xml generato dal sistema
• Internal storage: spazio interno che risiede in una parte del filesystem a cui solo la nostra l’applicazione
può accedere.
• External storage: spazio esterno spesso si ci riferisci a SD card esterne o alla porzione di disco
accessibile da tutte le applicazioni.
Corso di programmazione di sistemi mobile
2
Internal Storage
Ogni applicazione dispone di un’area protetta ed esclusiva all’interno della quale può effettuare una
qualsiasi operazione senza arrecare inconvenienti al sistema o alle altre applicazioni installate.
La classe Activity dispone dei metodi utili per accedere alla porzione di file system assegnata e ottenere
il riferimento ad uno stream in lettura o scrittura:
public FileInputStream openFileInput(String name);
Se il file non è presente all’interno della memoria dell’applicazione viene lanciata un eccezzione.
public FileOutputStream openFileOutput(String name, int mode);
Restituisce un oggetto che può essere manipolato come un qualsiasi output stream di Java. Il parametro
mode indica il tipo di scrittura del file. (si possono usare combinazioni tramite l’operatore binario OR)
Context.MODE_PRIVATE
Rende il file privato e accessibile solo alla nostra applicazione
Context.MODE_APPEND
Se il file esiste invece di sovrascriverlo gli accoda i nuovi byte che saranno scritti nello stream
Context.MODE_WORLD_READABLE
Rende il file accessibile in sola lettura dalle altre applicazioni installate nel sistema
Context.MODE_WORLD_WRITEABLE
Rende il file accessibile in sola scrittura dalle altre applicazioni installate nel sistema.
Corso di programmazione di sistemi mobile
3
External Storage
I dispositivi Android dispongono di un secondo spazio di memoria, definito "External storage".
Solitamente è una porzione di disco oppure una scheda di memoria che può all’occorrenza essere
rimossa e sostituita.
Quando si vuole scrivere sulla memoria esterna bisogna accertarsi che essa sia disponibile, il metodo per
verificare lo stato è contenuto staticamente nella classe android.os.Environment:
public static String getExternalStorageState();
Environment.MEDIA_MOUNTED
È possibile scrivere e leggere sulla memoria esterna
Environment.MEDIA_MOUNTED_READ_ONLY
È possibile solo leggere la memoria esterna
Environment.MEDIA_UNMOUNTED
Environment.MEDIA_UNMOUNTABLE
Environment.MEDIA_BAD_REMOVAL
Environment.MEDIA_CHECKING
Environment.MEDIA_NOFS
Environment.MEDIA_REMOVED
Environment.MEDIA_SHARED
Uno qualsiasi di questi valori indica che la memoria esterna non
è accessibile e non si possono eseguire operazioni di I/O
Corso di programmazione di sistemi mobile
4
Una volta accertati che sia possibile accedere alla memoria esterna, è possibile recupere il percorso sempre
attraverso il metodo statico di Environment:
public static File getExternalStorageDirectory();
Il file restituito è la radice della memoria esterna, è anche possibile recuperare una delle cartelle pubbliche
attraverso il metodo:
public static File getExternalStoragePublicDirectory(String type);
Environment.DIRECTORY_ALARMS
Directory in cui collocare tutti i file audio da utilizzare come allarmi
Environment.DIRECTORY_DCIM
Directory per le foto e i video
Environment.DIRECTORY_DOCUMENTS
Directory in cui inserire documenti che sono stati creati dall'utente
Environment.DIRECTORY_DOWNLOADS
Directory in cui collocare i file che sono stati scaricati dall'utente
Environment.DIRECTORY_MOVIES
Directory in cui inserire film che sono disponibili all'utente
Environment.DIRECTORY_MUSIC
Directory in cui collocare i file audio da utilizzare come musica
Environment.DIRECTORY_NOTIFICATIONS
Directory in cui collocare i file audio per le notifiche
Environment. DIRECTORY_PICTURES
Directory in cui inserire immagini come screenshot
Environment.DIRECTORY_PODCASTS
Directory in cui collocare tutti i file audio podcast
Environment.DIRECTORY_RINGTONES
Directory in cui collocare tutti i file audio per la suoneria
Corso di programmazione di sistemi mobile
5
A differenza di quanto avviene per la memoria interna Android non mette a disposizione metodi che
ritornino direttamente Stream di dati. Sarà compito dello sviluppatore creare un oggetto File e utilizzare i
metodi java per effettuare operazioni di I/O
String text = "Questo fiore è molto petaloso";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File doc = new File(dir, "Petaloso.txt");
FileOutputStream fos = null;
try {
byte[] data = text.getBytes();
fos = new FileOutputStream(doc);
fos.write(data);
fos.flush();
} catch (Exception e) {
Log.e("FileOutputStream", "Errore durante la scrittura del file", e);
} finally {
if (fos != null)
try {
fos.close();
} catch (Exception e) {
}
}
} else {
Toast.makeText(this, "Impossibile accedere alla sdcard!", Toast.LENGTH_LONG).show();
}
Corso di programmazione di sistemi mobile
6
Shared Preference
Le SharedPreferences sono nate dalla necessità di avere un sistema standard di salvataggio delle
informazioni all’interno di Android (es impostazioni dell’app). Esse ci permettono di salvare dei singoli
dati identificati da una chiave in maniera rapida, semplice e persistente.
Il metodo:
public SharedPreferences getSharedPreferences(String name, int mode)
ci permette di recuperare un oggetto SharedPreference associato al nome passato in ingresso. Mode
può assumere i valori: MODE_PRIVATE, MODE_WORLD_READABLE e MODE_WORLD_WRITEABLE
Per recuperare un valore salvato è possibile invocare i metodi presenti all’interno della classe
SharedPreferences, i più comuni sono:
getBoolean(String key, boolean defValue)
getFloat(String key, float defValue)
getInt(String key, int defValue)
getLong(String key, long defValue)
getString(String key, String defValue)
Corso di programmazione di sistemi mobile
7
Per poter salvare dei dati è necessario recuperare l’oggetto SharedPreferences e invocare il metodo edit che
restituisce un oggetto di tipo SharedPreferences.Editor.
Come per la SharedPreferences all’interno dell’oggetto Editor sono disponibili i metodi per salvare i dati:
putBoolean(String key, boolean value)
putFloat(String key, float value)
putInt(String key, int value)
putLong(String key, long value)
putString(String key, String value)
Una volta terminato l’inserimento dei dati per confermare il salvataggio è necessario invocare il metodo
apply() o commit(). La principale differenza tra i due metodi è che commit() esegue il salvataggio dei dati in
thread separato e non restituisce il risultato dell’operazione.
Corso di programmazione di sistemi mobile
8
Preference Activity
Android mette a disposizione un framework completo per la gestione delle preference, si tratta
dell’insieme delle classi appartenenti al package android.preference.
I componenti di gestione delle preferenze saranno contenute in una particolare specializzazione della
classe Activity: PreferenceActivity il cui layout dovrà essere contenuto in un documento che avrà come
root: <PreferenceScreen>.
Si dovrà utilizzare inoltre la classe PreferenceFragment poiché PrefenceActivity ha dei metodi deprecati.
public class MyPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Caricamento delle preferenze dal file XML
addPreferencesFromResource(R.xml.setting);
}
}
Corso di programmazione di sistemi mobile
9
Un esempio di file setting.xml
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/inline_preferences">
<CheckBoxPreference
android:key="checkbox_preference"
android:title="@string/title_checkbox_preference"
android:summary="@string/summary_checkbox_preference" />
</PreferenceCategory>
</PreferenceScreen>
PreferenceScreen rappresenta la radice della gerarchia delle preferenze, le preferenze possono essere
inoltre raggruppate in tag PreferenceCategory.
Questa implementazione popola in automatico le preferenze una volta create e ne mantiene la persistenza,
per recuperare le modifiche effettuate dall’utente bisogna utilizzare il metodo:
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean check = preferences.getBoolean("checkbox_preference", false);
Corso di programmazione di sistemi mobile
10
Le classi base per le preferenze e gli elementi del file XML a disposizione sono:
Corso di programmazione di sistemi mobile
11
Serializable
Nel passaggio di dati complessi tra servizi, come ad esempio il passaggio di dati fra activity, nasce la
necessità di serializzare i dati.
Un oggetto si dice serializzato se è trasformabile in un array di byte e può essere ricostruito al suo stato
originale. La serializzazione prevede la sola trasformazione dello stato dell’oggetto e non della sua
struttura, cioè della relativa classe. Quindi il bytecode relativo alla classe dovrà essere disponibile in
entrambi i processi che si scambiano questo tipo di oggetti.
Per serializzare un oggetto basta implementare l’interfaccia java.io.Serializable, inoltre tutti i membri
devono essere di tipo serializzabile.
public class Person implements Serializable {
private int id;
private About about;
}
Corso di programmazione di sistemi mobile
12
Parcelable
Android mette a disposizione un’altra struttura per serializzare i dati dal nome Parcelable. È stato creata
questa alternativa perché il meccanismo adottato per comprimere un oggetto Serializable è più lento e
se complesso può portare l’applicazione ad errori di tipo ARN.
A differenza di quanto avviene per una classe che implementa l’interfaccia Serializable l’utilizzo
dell’interfaccia Parcelable richiede più lavora da parte dello sviluppatore.
L'interfaccia Parcelable descrive una modalità di registrazione di un oggetto con tutti i suoi dati primitivi
o un qualsiasi oggetto che a sua volta implementa Parcelable.
Il metodo da implementare è:
public void writeToParcel(Parcel parcel, int flags)
Parcel parcel è l’oggetto dove andremo a trasferire i dati della nostra classe
int flags altre informazioni su come deve essere scritto l’oggetto
public int describeContents()
questo metodo in genere ritorna zero, esso viene utilizzato in alcuni casi particolari.
Corso di programmazione di sistemi mobile
13
Sarà inoltre necessario definire un metodo statico chiamato CREATOR, che è un oggetto che implementa
l'interfaccia Parcelable.Creator, tale oggetto serve a ricostruire la classe che implementa Parcelable.
public class Person implements Parcelable {
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
private int id;
protected Person(Parcel in) {
id = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
}
}
Corso di programmazione di sistemi mobile
14
Database
All’interno del framework Android sono presenti le API per utilizzare i Database, in particolare Android si
avvale del database SQLite.
SQLite è un leggerissimo database engine transazionale che occupa poco spazio in memoria e sul disco
pertanto è la tecnologia perfetta in un ambiente mobile, dove le risorse sono limitate e dunque è
importante ottimizzarne l’utilizzo.
SQLite supporta i principali tipi di dato (Integer, Real, Text) fatta eccezione per i booleani che vengono
memorizzati come interi 0, 1.
All’interno di un applicazione mobile si possono avere infiniti database, essi vengono memorizzati
all’interno dello spazio di memoria riservato all’applicazione nella sottodirectory chiamata databases, il
cui percorso assoluto è:
/data/data/packagename/databases
dove "packagename" è il nome del package del corrispondete alla nostra applicazione.
Corso di programmazione di sistemi mobile
15
Per includere e gestire un database in una app Android è necessario creare:
1) La struttura del database tramite uno script SQL.
2) Una classe java che estende SQLiteOpenHelper per gestire la creazione e i vari aggiornamenti del DB,
inoltre la classe deve poter recuperare un riferimento all’oggetto SQLiteDatabase per accedere ai dati.
3) Una classe per l’interazione con il database sfruttando il riferimento alla classe SQLiteOpenHelper e
contenente metodi per eseguire le operazioni sui dati.
Per creare la tabella sarà necessaria la creazione di una stringa contenente il comando , ad esempio:
private static final String CREA_TABELLA_PERSONE = "CREATE TABLE " +
TABELLA_PERSONE + " (" + ID + " INTEGER PRIMARY KEY,"
+ PERSONE_NOME + " TEXT," + PERSONE_COGNOME + " TEXT," +
PERSONE_FOTO + " TEXT)";
È utile creare anche delle stringhe per i comandi di update o di altre modifiche:
private static final String SQL_DELETE_ENTRIES ="DROP TABLE IF EXISTS " + "Persone" ;
Corso di programmazione di sistemi mobile
16
SQLiteOpenHelper
public class PersonDbHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "PersonDb.db";
private static final int DATABASE_VERSION = 1;
private
private
private
private
public
public
public
public
public
static
static
static
static
static
static
static
static
static
final
final
final
final
final
final
final
final
final
String
String
String
String
String
String
String
String
String
TEXT_TYPE = " TEXT";
REAL_TYPE = " REAL";
INTEGER_TYPE = " INTEGER";
COMMA_SEP = ",";
TABLE_NAME = "person";
_ID = "_id";
NOME = "nome";
COGNOME = "cognome";
FOTO = "foto";
private static final String TABLE_PERSON_CREATE = "CREATE TABLE " +
TABLE_NAME + " (" +
_ID + " INTEGER PRIMARY KEY," +
NOME + TEXT_TYPE + COMMA_SEP +
COGNOME + TEXT_TYPE + COMMA_SEP +
FOTO + TEXT_TYPE + " )";
Corso di programmazione di sistemi mobile
17
public PersonDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(TABLE_PERSON_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (newVersion) {
case 2:
break;
}
}
}
Il metodo onCreate viene invocato una sola volta per la creazione del DB e riceve come input un oggetto di tipo
SQLiteDatabase il quale fornisce i metodi necessari ad effettuare le principali operazioni su un database.
Si può ottenere un’istanza di tale classe anche utilizzando altri due metodi presenti nella classe helper:
getReadableDatabase() e getWritableDatabase(). Tali metodi permettono l’accesso rispettivamente in lettura ed in
scrittura al DB.
Alla fine di ogni utilizzo dell’istanza ottenuta è necessario rilasciare il database invocando il metodo close() . In
alternativa a questo metodo si può usare releaseReference().
Corso di programmazione di sistemi mobile
18
ExecSQL
Nell’onCreate del database si può notare che il comando SQL è inviato tramite l’invocazione del metodo
execSQL.
Tale metodo consente l’esecuzione di qualsiasi comando SQL che non sia di tipo query (quindi che non
preveda la restituzione di informazioni da parte del DB) e sono disponibili due overload.
Il primo richiede semplicemente una stringa SQL da eseguire, l’altro richiede di passare dei parametri per
l’esecuzione dei cosiddetti prepared statements:
db.execSQL("INSERT INTO nomi VALUES(?)", new Object[]{"Andrea"});
La piattaforma ci offre comunque supporto nativo per le operazioni più comuni come delete, insert, update
e query generiche.
Corso di programmazione di sistemi mobile
19
Principali Operazioni sul DB
• Query: si ottiene un oggetto Cursor che può essere usato per spostarsi tra le righe del risultato con i
metodi moveToNext, moveToFirst, moveToLast. Per ogni colonna si possono prelevare i valori tramite i
metodi getString, getLong … tali metodi necessitano dell’indice della colonna desiderata.
Esistono due metodi per effettuare le query:
1. query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String
having, String orderBy, String limit)
2. rawQuery(String sql, String[] selectionArgs)
• Delete: utilizzato per eliminare uno o più record data la tabella è necessario specificare la condizione e gli
eventuali parametri per effettuare il bind ( concetto simile alla clausola WHERE). Ritorna il numero di record
eliminati o errore (-1).
• Insert: per inserire un nuovo record, come valore di ritorno restituisce l’ID della riga inserita o -1 in caso di
errore.
• Update: per modificare dei record inseriti oppure delle righe.
Corso di programmazione di sistemi mobile
20
Query
long itemId;
SQLiteDatabase db = getWritableDatabase();
Cursor c = db.query(
TABLE_NAME ,
// La tabella da interrogare
projection,
// Le colonne che si vogliono ottenere (String[])
selection,
// le colonne della clausola WHERE
selectionArgs,
// I valori per la clausola WHERE
null,
// null indica che non si vuole raggruppare le righe
null,
// nessun filtro per gruppi di righe
sortOrder
// modalità di ordinamento (String)
);
c.moveToFirst();
itemId = c.getLong(cursor.getColumnIndexOrThrow(_ID));
db.close();
Corso di programmazione di sistemi mobile
21
RawQuery
public List<Persona> getPersone() {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
if (cursor == null || !cursor.moveToFirst())
return null;
List<Persona> persone = new ArrayList<Persona>(cursor.getCount());
for (int i = 0; i < cursor.getCount(); i++) {
String nome = cursor.getString(cursor.getColumnIndex(NOME));
String cognome = cursor.getString(cursor.getColumnIndex(COGNOME));
String foto = cursor.getString(cursor.getColumnIndex(FOTO));
long id = cursor.getLong(cursor.getColumnIndex(_ID));
Persona persona = new Persona();
persona.setCognome(cognome);
persona.setNome(nome);
persona.setFoto(foto);
persona.setId(id);
persone.add(persona);
cursor.moveToNext();
}
db.close();
return persone;
}
Corso di programmazione di sistemi mobile
22
Inserimento
Per inserire un oggetto all’interno del database si utilizza il ContentValues che ci consente di inserire i dati
tramite l’assegnazione della rispettiva colonna del database.
public int addNuovaPersona(Persona persona) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(NOME, persona.getNome());
values.put(COGNOME, persona.getCognome());
values.put(FOTO, persona.getFoto());
long result = db.insert(TABLE_NAME, null, values);
db.close();
return result;
}
Corso di programmazione di sistemi mobile
23
Eliminazione
Il valore di ritorno indica il numero di record eliminati o -1 in caso di errore.
public boolean deletePersona(long id) {
SQLiteDatabase db = getWritableDatabase();
int result = db.delete(TABLE_NAME, ID + "=?",
new String[] { Long.toString(id) });
db.close();
return result>0;
}
Corso di programmazione di sistemi mobile
24
Aggiornamento
public Persona updatePersona(Persona persona) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(NOME, persona.getNome());
values.put(COGNOME, persona.getCognome());
values.put(FOTO, persona.getFoto());
int result = db.update(TABLE_NAME, values, ID + "=?", new String[]{
Long.toString(persona.getId())
});
db.close();
return result>0;
}
Corso di programmazione di sistemi mobile
25