Android e l'interfaccia Parcelable
Nello sviluppare applicazioni android, quando abbiamo la necessità di trasferire un oggetto creato da noi da un'activity ad un'altra (oppure anche in altri contesti) dobbiamo ricorrere all'implementazione delle interfacce Parcelable e Parcelable.Creator. L'interfaccia Parcelable serve a registrare l'oggetto che stiamo trasferendo tramite il metodo writeToParcel() all'interno di un Parcel. Mentre l'interfaccia Parcelable.Creator serve a ricostruire l'oggetto.
Interfaccia Parcelable
L'interfaccia Parcelable descrive una modalità di registrazione di un oggetto con tutti i suoi dati primitivi tipo int, boolean, String - oppure un qualsiasi oggetto che a sua volta implementa Parcelable - in un oggetto Parcel associato ad esso. I metodi da implementare sono:
- void writeToParcel(Parcel parcel, int flags): questo metodo riceve un oggetto Parcel dove andremo a trasferire gli attributi del nostro oggetto
- int describeContents: questo metodo in genere ritorna zero. Esso viene utilizzato in alcuni casi particolari che non tratteremo in questo articolo
Parcelable Creator
Le classi che implementano l'interfaccia Parcelable devono anche avere un campo statico chiamato CREATOR, che è un oggetto che implementa l'interfaccia Parcelable.Creator. Questo oggetto serve a ricostruire la classe che implementa Parcelable. I metodi di questa interfaccia sono:
- public Point createFromParcel(Parcel source):questo metodo viene invocato automaticamente nel momento in cui si tenta di ricostruire un ipotetico oggetto Parcelable. Un caso molto comune lo troviamo nella classe Bundle quando invochiamo il metodo getParcelable().
- public Point[] newArray(int size): in questo caso viene ricostruito l'array di un oggetto parcelable. Tutto ci sarà piu chiaro nell'esempio che farema fra poco.
Da notare che l'oggetto Parcel di cui ci serviamo per leggere e scrivere dati, contiene tutti i metodi di lettura e scrittura dei dati primitivi. Ecco l'elenco:
- writeBooleanArray (boolean []) , readBooleanArray (boolean []) , createBooleanArray()
- writeByteArray (byte []) , writeByteArray (byte [], int, int) , readByteArray (byte []) , createByteArray ()
- writeCharArray (char []) , readCharArray (char []) , createCharArray ()
- writeDoubleArray (double []) , readDoubleArray (double []) , createDoubleArray ()
- writeFloatArray (float []) , readFloatArray (float []) , createFloatArray ()
- writeIntArray (int []) , readIntArray (int []) , createIntArray ()
- writeLongArray (long []) , readLongArray (long []) , createLongArray ()
- writeStringArray (String []) , readStringArray (String []) , createStringArray ()
- writeSparseBooleanArray (SparseBooleanArray) , readSparseBooleanArray ()
Esempio con Parcelable
Vediamo un esempio concreto per chiarirci meglio le idee. Definiamo ad esempio una classe Point che rappresenta un punto con coordinate x e y. Dobbiamo trasferirlo da un Activity ad un'altra tramite l'oggetto Intent:
import android.os.Parcel; import android.os.Parcelable; public class Point implements Parcelable{ private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } public Point(Parcel parcel) { this.x = parcel.readInt(); this.y = parcel.readInt(); } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(x); dest.writeInt(y); } public final static Parcelable.CreatorCREATOR = new Parcelable.Creator }() { @Override public Point createFromParcel(Parcel source) { return new Point(source); } @Override public Point[] newArray(int size) { return new Point[size]; } };
Ho evidenziato in grassetto i metodi e costruttori di cui stiamo parlando. L'interfaccia Parcelable.Creator invoca il costruttore (che deve essere presente) Point(Parcel p) per ricostruire l'oggetto quando viene invocato getParcelable() sia da un Intent oppure da un Bundle. Come vedete l'implementazione è stata fatta tramite una inner class. Adesso vediamo un esempio di trasferimento dell'oggetto Point:
Intent intent = new Intent(ActivityA.this, ActivityB.class); Bundle bundle = new Bundle(); bundle.putParcelable("punto", new Point(4,3)); intent.putExtras(bundle); startActivity(intent);
Abbiamo creato un oggetto Point e lo abbiamo incapsulato tramite un Bundle che attraverso il metodo putParcelable() viene registrato nel Parcel. Quindi viene invocato implicitamente il metodo writeToParcel().
... public Point(Parcel parcel) { this.x = parcel.readInt(); // 1 this.y = parcel.readInt(); // 2 } ... public void writeToParcel(Parcel dest, int flags) { dest.writeInt(x); // 1 dest.writeInt(y); // 2 }
Adesso mettiamo il caso di trovarci nell'ActivityB e dobbiamo leggere l'oggetto trasferito tramite l'Intent:
public class ActivityB { ... @Override public void onCreate(Bundle savedInstanceState) { ... Intent intent = getIntent(); Bundle bundle = intent.getExtras(); Point p = (Point)bundle.getParcelable("punto"); } .... }
L'esempio è abbastanza chiaro, attraverso il casting otteniamo l'oggetto che abbiamo trasferita dall'ActivityA. Esistono altri metodi della classe Bundle, per trasferire array di Parcelable, come nel nostro caso array di Point. In questo caso possiamo utilizzare il metodo get/putParcelableArray() o anche ArrayList di Parcelable con i rispettivi metodi get/putParcelableArrayList().
L'utilizzo di Parcelable è un'alternativa alla serializzazione degli oggetti. In Android possiamo comunque ricorrere alla serializzazione però ciò comporta un calo delle prestazioni e quindi non ne è consigliato l'utilizzo.