Today I would like to introduce a flexible and easy method for saving and restoring your Activity state in your Android App. Typically in Android, and Activity can be destroyed by the system for various reasons, either to save memory while the Activity is not visible, or to rebuild the layout when device orientation changes. Whatever the case, it’s your responsibility to save any variables when the Activity goes away and restore them when it comes back. This is accomplished by overriding the onSaveInstanceState
and onRestoreInstanceState
methods of the Activity object. This is explained in more detail here.
Now, what is usually suggested is to set and read info using the various get and put methods of the Bundle
object that is passed to the methods in order to save and restore state. Kinda like this:
public static final String SAVE_KEY1 = "SAVE_KEY1"; public static final String SAVE_KEY2 = "SAVE_KEY2"; public static final String SAVE_KEY3 = "SAVE_KEY3"; @Override protected void onSaveInstanceState(Bundle outState) { outState.putString(SAVE_KEY1, stringValue); outState.putInt(SAVE_KEY2, intValue); outState.putFloat(SAVE_KEY3, floatValue); } @Override protected void onRestoreInstanceState(Bundle savedState) { stringValue = savedState.getString(SAVE_KEY1); intValue = savedState.getString(SAVE_KEY2); floatValue = savedState.getString(SAVE_KEY3); }
Ok, so you’re thinking, what’s the big deal about that? It seems pretty easy. Well, it is pretty easy, and for most situations its good enough. However there are a couple things I don’t like about the approach. First, if you have a large number of fields to save and restore, this can seem tedious. Also, if your data is not flat (i.e., a hierarchy of complex data structures), this simple method might not get the job done. Second, its just a code maintenance issue. Every time you change the data in your activity, you have to update these two functions and re-test them. You run the risk of introducing bugs, like loading the wrong saved value into the wrong field.
The solution to these potential complications is simple. Place all your data for your Activity in a class, and then serialize and deserialize that class as a whole. That way, your onSaveInstanceState
and onRestoreInstanceState
implementations will be the same no matter the size or complexity of your state information, and if the information changes, you will not have to alter these methods. The only thing you need to do is ensure that your class in which you will save your Activity state is able to be serialized. This is done by marking it with the Serializable
interface. As long the class is marked and any types within the class are serializable, you are good to go. However, if you do have some object that just can’t be serialized, you may declare it as transient
. This prevents it from being serialized with all the other fields. Here is a short example of a serializable class:
public class State implements Serializable { public enum ExampleEnum implements Serializable { PAUSED, RUNNING, OVER } //The Transient keyword excludes the field from serialization public transient NonSerializableClass nsc; public int anIntegerValue; public int anotherIntegerValue; public ExampleEnum exampleEnum; public LinkedList listOfStrings; public LinkedList listOfInts; public LinkedList listOfFloats; }
This example just contains some simple values, a couple lists of simple values, and an enumeration. Note that the enumeration is also marked as Serializable
.
Ok, so the we have a serializable class that holds all the info we need. Now what? Well to actually perform the serialization, we can use the ObjectOutputStream
class with the help of the ByteArrayOutputStream
class to turn our class into a array of bytes that we can place in the Bundle. ObjectOutputStream
does the work of serializing the object to the ByteArrayOutputStream
, where the array of bytes is retrieved. Then, to do the opposite, we use the ObjectInputStream
and ByteArrayInputStream
class to turn an array of bytes back into an object. Here is the code for that:
public static final String SAVE_KEY = "SAVE_KEY"; public State state; @Override protected void onSaveInstanceState(Bundle outState) throws IOException { //Serialize state object and write it to bundle ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos); out.writeObject(state); out.flush(); out.close(); outState.putByteArray(SAVE_KEY, bos.toByteArray()); } @Override protected void onRestoreInstanceState(Bundle savedState) throws StreamCorruptedException, IOException, ClassNotFoundException { if(savedState != null) { if(savedState.containsKey(SAVE_KEY)) { ObjectInputStream objectIn = new ObjectInputStream(new ByteArrayInputStream(savedState.getByteArray(SAVE_KEY))); Object obj = objectIn.readObject(); State = (State) obj; }else { state = new State(); } } else { state = new State(); } }
And thats that. Note that this code was pulled from a working example, but as always issues can be introduced in the simplification and presentation process. If you find a problem, let us know in the comments!