Android
Objekte per Intent-Extra übergeben
Posted on .Es gibt viele Möglichkeiten, den Zugriff auf Objekte zwischen verschiedenen Activity
– und Service
-Instanzen zu teilen. Die simpelste Lösung ist das Singleton-Entwurfsmuster oder ein statisches Feld, das ein Objekt hält und von verschiedenen Activities abgerufen werden kann.
// global zugängliches Objekt durch statisches Feld
User user = UserHelper.currentUser;
// gloabal zugängliches Objekt mit dem
// Singleton-Muster
ShoppingCart orders = ShoppingCart.getSharedInstance();
Schon einen Schritt aufwendiger ist eine eigene Application
-Klasse für die App zu definieren, die als Attribute die Objekte hält, die zwischen Activities hin und hergereicht werden sollen. Theoretisch existieren diese Instanzen so lange, wie das Application
-Objekt selbst.
MyCustomApplication app = (MyCustomApplication) this.getApplication();
Document doc = app.getCurrentDocument();
Der Nachteil aller dieser Ansätze ist, dass die geteilten Objekte dauerhaft Speicher belegen. Sie gehen verloren, falls im Hintergrund die komplette App beendet wird, z.B. durch Speichermangel. Ggf. wird die App dann nicht mehr richtig wiederhergestellt, wenn sie erneut in den Vordergrund kommt.
Eine weitere Möglichkeit ist Daten in dem Bundle
-Objekt des Intents mit dem Namen „extras“ an eine andere Activity oder einen Service weiter zu geben. Ein Bundle nimmt alle primitiven Datentypen und CharSequence
-Objekte an, sowie Objekte, die entweder die Schnittstelle Serializable
oder Parcelable
implementieren.
Die Technik Daten in einem Bundle zu speichern wird auch in der Methode onSaveInstanceState()
der Activity-Klasse verwendet, wenn eine Activity beendet wird und ihr Zustand später wieder hergestellt werden soll, z.B. wenn die Bildschirmausrichtung sich ändert.
Serializable
Der einfachste Weg, ein Objekt in einem Bundle zu speichern, ist es serialisierbar zu machen, in dem es die Schnittstelle Serializable
implementiert. Diese Schnittstelle erfordert keine weiteren Implementierungen und ist nur dazu da, dem System zu sagen, dass es den Zustand des Objekts durch Reflexion auszulesen soll.
public class Customer implements Serializable{
String firstName;
String lastName;
}
Der Aufwand für diese Technik ist minimal, die Klasse und jede Klasse, die sie als Attribut besitzt, muss lediglich die entsprechende Schnittstelle implementieren. Wenn auf diese weise nur wenige Objekte serialisiert werden, ist dieser Ansatz vollkommen ausreichend. Die Technik ist allerdings sehr performance-intensiv durch den Einsatz von Reflexion. Wenn viele Objekte und umfangreiche Objekt-Bäume serialisiert und deserialisiert werden, kann es zu wahrnehmbaren Verzögerungen kommen beim Start einer Activity.
Parcelable
Android bietet als Alternative zur Serializable
die Schnittstelle Parcelable
, die eine sehr viel performantere Serialisierung bietet. Allerdings ist sie auch aufwendiger bei der Implementierung und Wartung.
Eine Klasse, die als Parcel
serialisiert werden kann, muss zwei Bedingungen erfüllen:
- die Schnittstelle
Parcelable
implementieren - ein statisches Feld mit dem Namen CREATOR besitzen mit einer Objekt, dass die Schnittstelle
Parcelable.Creator
implementiert
Die Methode writeToParcel()
beschreibt, wie der Zustand des Objekts in ein Parcel verpackt wird. Die zweite Methode der Parcelable Schnittstelle describeContents()
gibt im Normalfall immer 0 zurück.
Die Implementierung der Creator Schnittstelle beschreibt in der createFromParcel()
Methode hingegen, wie der Inhalt eines Parcels wiederum entpackt wird, um eine neue Instanz zu erzeugen, die den selben Zustand hat wie das serialisierte Objekt. Die Daten werden aus dem Parcel in der selben Reihenfolge ausgelesen, wie sie in writeToParcel()
in das Parcel
-Objekt geschrieben wurden. Auch bei dieser Schnittstelle ist die zweite Methode der Schnittstelle wenig interessant, weil sie in der Regel immer ein Array bestimmter Größe vom Typ der Klasse mit uninitialisiertem Inhalt erzeugt.
public class Customer implements Parcelable{
String firstName;
String lastName;
public String getFirstName() {return firstName;}
public String getLastName() {return lastName;}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// die Attribute des Objekts wird in ein
// Parcel-Objekt geschrieben
dest.writeString(firstName);
dest.writeString(lastName);
}
public static final Creator CREATOR = new Creator() {
@Override
public Customer createFromParcel(Parcel source) {
// ein neues Objekt wird erzeugt und mit den Daten aus
// dem Parcel initialisiert
Customer obj = new Customer();
obj.setFirstName(source.readString());
obj.setLastName(source.readString());
return obj;
}
@Override
public Customer[] newArray(int size) {
return new Customer[size];
}
};
}
Parceler Bibliothek
Parcler ist eine Bibliothek von John Ericksen, die den nötigen Code zur Implementierung der Parcelable
Schnittstellen automatisch generiert. Um sie zu benutzen, muss man sie lediglich als neue Abhängigkeit in der build.gradle
Datei eintragen:
dependencies {
// [...]
compile "org.parceler:parceler-api:0.2.15"
provided "org.parceler:parceler:0.2.15"
}
Um eine Klasse mit Parceler zu persistieren, muss sie nur mit @Parcel
markiert werden.
@Parcel
public class Customer{
String firstName;
String lastName;
}
Um jetzt eine Instanz als Parcelable
den Extras eines Intents hinzuzufügen ist noch die Methode Parcels.wrap()
nötig.
Parcelable parcel = Parcels.wrap(data);
intent.putExtra(“extra_customer“, parcel);
startActivity(intent);
Umgekehrt braucht man noch die Methode Parcels.unwrap()
, das gesuchte Objekt wieder aus dem Parcelable zu erhalten, dass von Parceler generiert wurde.
Parcelable extra = extras.getParcelable(“extra_customer“);
Customer customer = Parcels.unwrap(extra);
Sollen Attribute nicht serialisiert werden, kann man sie mit der Annotation @Transient
markieren, damit sie von Parceler ignoriert.
@Parcel
public class Customer{
String firstName;
String lastName;
@Transient
String fullName;
}
Parceler benutzt standardmäßig einen leeren Konstruktor. Soll ein anderer benutzt werden kann er mit der @ParcelConstructor
Annotation markiert werden.
@Parcel
public class Customer{
String firstName;
String lastName;
public Customer() {}
@ParcelConstructor
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}