7. April 2013

Android: Komplexe Objekte in Bundle speichern

In Android ist es nötig, gewisse Zustände einer Activity oder eines Fragments zu speichern und wiederherzustellen, insofern die Werte nicht verloren gehen sollen, wenn die Activity bzw. das Fragment zerstört und neu gestartet wird, wie es z.B. bei einem Orientierungswechsel des Geräts geschieht. Die prinzipielle Vorgehensweise zum Sichern solcher Werte wurde bereits in dem Post "Die Activity-Methoden onSaveInstanceState und onRestoreInstanceState" für Activities und in dem Post "Die Fragment-Methoden onSaveInstanceState und onActivityCreated" für Fragments erklärt.

Meistens handelt es sich bei den Werten, die dabei in dem Bundle-Objekt gespeichert werden um primitive Datentypen. Hierfür bietet die Bundle-Klasse jeweils Methoden um Werte hinzuzufügen, bzw. später wieder auszulesen. Es besteht jedoch darüber hinaus auch die Möglichkeit, komplexe Objekte zu sichern, indem die Methode putSerializable(String key, Serializable value):void zum Ablegen eines Objekts in dem entsprechenden Bundle, bzw. die Methode getSerializable(String key):Serializable zum referenzieren eines zuvor gespeicherten Objekts aus dem Bundle, genutzt wird. Das zu sichernde Objekt muss demnach die das Interface java.io.Serializable implementieren.

Sollen allerdings Objektinstanzen von Klassen gespeichert werden, die dieses Interface nicht implementieren und die nicht angepasst werden können, weil sie beispielsweise Klassen der Java-API oder eines Frameworks sind, steht man vor dem Problem, dass solche Klassen nicht ohne Weiteres an ein Bundle übergeben werden können. Eine Möglichkeit, wie dieses Problem umgangen werden kann, soll nun im Folgenden aufgezeigt werden.

Die Lösung besteht darin, eine Wrapper-Klasse zu schreiben, die das Serializable-Interface implementiert und eine Instanz des zu sichernden Objekttyps aufnimmt. Dabei sind grundsätzlich zwei Ansätze denkbar: Entweder man entscheidet sich für einen sehr einfachen Wrapper, der lediglich eine Getter- und eine Setter-Methode auf das gekapselte Objekt anbietet, oder man setzt auf eine komplexere, aber eleganter zu verwendende Implementierung, die zudem als Proxy fungiert, indem sie neben dem Serializable-Interface auch die Schnittstelle des zu sichernden Objekttyps implementiert und alle Methodenaufrufe an das gekapselte Objekt weiterleitet. Die hat den Vorteil, dass sich Instanzen dieser Klasse exakt wie das Original handhaben lassen. Eventuell bereits vorhandener Code muss auf diese Weise kaum angepasst werden. Auf eine mögliche Implementierung dieser Variante soll nun näher eingegangen werden. Als Code-Beispiel ist im Folgenden ein Wrapper für das Interface java.util.List, das als Schnittstelle für alle Listenimplementierungen der Java-Standardbibliothek dient, zu sehen. Als Szenario für die Sicherung einer solchen Datenstruktur wäre beispielsweise ein Listenadapter denkbar, der Suchergebnisse in einer ListView darstellt. Wenn bei einem Orientierungswechsel der entsprechenden Activity der zeitaufwendige Netzwerkzugriff der Suchanfrage nicht erneut durchgeführt werden soll, muss die zugrunde liegende Datenstruktur zwischengespeichert werden.
 public class SerializableList<Type> implements List<Type>, Serializable {  
   
     private static final long serialVersionUID = 1L;  
   
     private List<Type> list;  
   
     public SerializableList(final List<Type> list) {  
         this.list = list;  
     }  
   
     public final boolean add(final Type object) {  
         return list.add(object);  
     }  
   
     public final void add(final int location, final Type object) {  
         list.add(location, object);  
     }  
   
     public final boolean addAll(final Collection<? extends Type> collection) {  
         return list.addAll(collection);  
     }  
   
     public final boolean addAll(final int index,  
             final Collection<? extends Type> collection) {  
         return list.addAll(index, collection);  
     }  
   
     public final void clear() {  
         list.clear();  
     }  
   
     public final boolean contains(final Object object) {  
         return list.contains(object);  
     }  
   
     public final boolean containsAll(final Collection<?> collection) {  
         return list.containsAll(collection);  
     }  
   
     public final Type get(final int location) {  
         return list.get(location);  
     }  
   
     public final int indexOf(final Object object) {  
         return list.indexOf(object);  
     }  
   
     public final boolean isEmpty() {  
         return list.isEmpty();  
     }  
   
     public final Iterator<Type> iterator() {  
         return list.iterator();  
     }  
   
     public final int lastIndexOf(final Object object) {  
         return list.lastIndexOf(object);  
     }  
   
     public final ListIterator<Type> listIterator() {  
         return list.listIterator();  
     }  
   
     public final ListIterator<Type> listIterator(final int location) {  
         return list.listIterator(location);  
     }  
   
     public final Type remove(final int location) {  
         return list.remove(location);  
     }  
   
     public final boolean remove(final Object object) {  
         return list.remove(object);  
     }  
   
     public final boolean removeAll(final Collection<?> collection) {  
         return list.removeAll(collection);  
     }  
   
     public final boolean retainAll(final Collection<?> collection) {  
         return list.retainAll(collection);  
     }  
   
     public final Type set(final int location, final Type object) {  
         return list.set(location, object);  
     }  
   
     public final int size() {  
         return list.size();  
     }  
   
     public final List<Type> subList(final int start, final int end) {  
         return list.subList(start, end);  
     }  
   
     public final Object[] toArray() {  
         return list.toArray();  
     }  
   
     public final <T> T[] toArray(final T[] array) {  
         return list.toArray(array);  
     }  
   
     @Override  
     public final int hashCode() {  
         final int prime = 31;  
         int result = 1;  
         result = prime * result + ((list == null) ? 0 : list.hashCode());  
         return result;  
     }  
   
     @Override  
     public final boolean equals(final Object obj) {  
         if (this == obj)  
             return true;  
         if (obj == null)  
             return false;  
         if (getClass() != obj.getClass())  
             return false;  
         SerializableList<?> other = (SerializableList<?>) obj;  
         if (list == null) {  
             if (other.list != null)  
                 return false;  
         } else if (!list.equals(other.list))  
             return false;  
         return true;  
     }  
   
 }  
Die Klasse implementiert sowohl das Interface java.io.Serializable, als auch das Interface java.util.List. Außerdem handelt es sich um eine generische Klasse, die mit dem Typ der Objekte, die die gekapselte Liste aufnehmen soll, typisiert wird. Über den Konstruktor wird diese Liste übergeben und in dem entsprechenden Attribut gespeichert. Dieses wird dazu verwendet, um alle Aufrufe der Methoden, die in dem List-Interface definiert sind, unverändert an die gekapselte Liste weiter zu leiten. Darüber hinaus wurde auch jeweils eine Implementierung der hashCode- bzw. equals-Methode hinzugefügt, damit sich Klasseninstanzen tatsächlich so verhalten, wie man es von einer Liste erwarten würde.

Das Abspeichern der oben gezeigten Listenimplementierung in einem Bundle könnte beispielsweise wie folgt aussehen:
 public final void onSaveInstanceState(final Bundle outState) {  
   SerializableList<String> serializableList = new SerializableList<String>(new ArrayList<String>());  
   serializableList.add("elem1");  
   serializableList.add("elem2");  
   outState.putSerializable("list", serializableList);  
 }  
Um die, auf diese Weise im Bundle gespeicherte, Liste anschließend wiederherzustellen, sind dann folgende Zeilen nötig:
 public final void onRestoreInstanceState(final Bundle savedInstanceState) {  
   SerializableList<String> serializableList = (SerializableList<String>) savedInstanceState.getSerializable("list");  
 }  
Dabei ist ein Cast vom Interface Serializable auf die Serializable-List implementierung nötig.

Keine Kommentare:

Kommentar veröffentlichen