views:

114

answers:

4

I have a List<CustomObject> (where CustomObject comes from an external library -- I can't make changes to it). I want to save this in onSaveInstanceState(Bundle), but I can't seem to do it. Here are the options that I've tried:

outState.putSerializable(KEY, (ArrayList<CustomObject>) myList); // because myList is instantiated as an ArrayList
outState.putSerializable(KEY, myList.toArray());

Both options work when switching orientation on the phone (yes, onSaveInstanceState is called when switching orientation -- I checked in logcat). However, when the current activity tries to start another one (with startActivity(Intent)), Android pauses the current activity and calls onSaveInstanceState() again. This time, it fails, for some reason unknown to me. The fishy thing is that onSaveInstanceState() executes successfully. The stack trace printed doesn't point to any of my code:

E/AndroidRuntime(23898): java.lang.RuntimeException: Parcel: unable to marshal value my.custom.Object@5e07e43b
E/AndroidRuntime(23898):    at android.os.Parcel.writeValue(Parcel.java:1087)
E/AndroidRuntime(23898):    at android.os.Parcel.writeArray(Parcel.java:519)
E/AndroidRuntime(23898):    at android.os.Parcel.writeValue(Parcel.java:1072)
E/AndroidRuntime(23898):    at android.os.Parcel.writeMapInternal(Parcel.java:469)
E/AndroidRuntime(23898):    at android.os.Bundle.writeToParcel(Bundle.java:1445)
E/AndroidRuntime(23898):    at android.os.Parcel.writeBundle(Parcel.java:483)
E/AndroidRuntime(23898):    at android.app.ActivityManagerProxy.activityPaused(ActivityManagerNative.java:1427)
E/AndroidRuntime(23898):    at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:3106)
E/AndroidRuntime(23898):    at android.app.ActivityThread.access$2400(ActivityThread.java:119)
E/AndroidRuntime(23898):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1870)
E/AndroidRuntime(23898):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(23898):    at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(23898):    at android.app.ActivityThread.main(ActivityThread.java:4363)
E/AndroidRuntime(23898):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(23898):    at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(23898):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
E/AndroidRuntime(23898):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
E/AndroidRuntime(23898):    at dalvik.system.NativeStart.main(Native Method)

Is there any way to store custom objects in the instance state?

+1  A: 

Make your CustomObject implement Parcelable and use:

outState.putParcelable(KEY, myList);
onSaveInstanceState(outState);

Also check this tutorial.

EDIT after CommonsWare comment:

If your CustomObject doesn't implement Serializable or Parcelable I would try wrapping it inside an object of your own and add:

  • private void readObject(ObjectInputStream aStream) throws IOException, ClassNotFoundException { /*Your deserialization */ }
  • private void writeObject(ObjectOutputStream aStream) throws IOException { /*Your serialization */}
Macarse
Felix indicated that he cannot modify `CustomObject`.
CommonsWare
@CommonsWare: thanks. I didn't notice that.
Macarse
I marked this as accepted because this is the route I *would* go. However, I spoke with the authors of the library and they'll be implementing it.
Felix
+4  A: 

Have your List<CustomObject> be held by a service and make it accessible to your activities via the local binding pattern.

Not only do you not have to worry about holding onto it in your instance state, but you have a bit better control over the lifetime of those objects in memory. Instance state lifetime is controlled by Android; how long a Service holds onto the objects is controlled by you. Particularly if CustomObject might be big, or the list might be long, I would rather you have greater control over how long that RAM is consumed.

CommonsWare
You mean having a `Service` started all the time which doesn't stop?
Macarse
It doesn't stop while there are 1+ activities running. It would shut down once there are no more bound connections. You could also use a custom `Application` object, if you prefer -- you lose control but don't have a `Service` lying about.
CommonsWare
@CommonsWare: I guess he wants to `Serialize` to save it for later. Using that approach he will lose the info if the phone gets rebooted.
Macarse
@Macarse: Felix is trying to get his objects working with `onSaveInstanceState()`. I am suggesting he simply avoid `onSaveInstanceState()` with these objects. Whether he uses `Serializable` for other purposes (e.g., some sort of "save" operation), has no bearing on my suggestion. `onSaveInstanceState()` is not a persistence mechanism, in any circumstance.
CommonsWare
@CommonsWare: You are right. My bad :(
Macarse
Thanks for the idea, but I think it would be a bit cumbersome. Also, it would require a **lot** of refactoring my app.
Felix
+1  A: 

If this is primarily to handle orientation changes, could Activity#onRetainNonConfigurationInstance() do what you want?

an activity can use this API to propagate extensive state from the old to new activity instance, from loaded bitmaps, to network connections, to evenly actively running threads. Note that you should not propagate any data that may change based on the configuration, including any data loaded from resources such as strings, layouts, or drawables.

This API won't help you if you're trying to do more than persist data across configuration changes.

adamp
+1, I knew about `onRetainNonConfigurationInstance()`, but no, it's not primarily for orientation changes.
Felix
A: 

Why not just save the object to the SD Card? You can see an example of how I use this at my blog >> http://androidworkz.com/2010/07/06/source-code-imageview-flipper-sd-card-scanner/

public void saveArray(String filename, String[] output_field) {
         try {
            FileOutputStream fos = new FileOutputStream(filename);
            GZIPOutputStream gzos = new GZIPOutputStream(fos);
            ObjectOutputStream out = new ObjectOutputStream(gzos);
            out.writeObject(output_field);
            out.flush();
            out.close();
         }
         catch (IOException e) {
             e.getStackTrace(); 
         }
      }

    public String[] loadArray(String filename) {
          try {
            FileInputStream fis = new FileInputStream(filename);
            GZIPInputStream gzis = new GZIPInputStream(fis);
            ObjectInputStream in = new ObjectInputStream(gzis);
            String[] read_field = (String[])in.readObject();
            in.close();
            return read_field;
          }
          catch (Exception e) {
              e.getStackTrace();
          }
          return null;
      }
androidworkz
Because... my object is custom and can't be serialized in order to be stored on the SD card?
Felix
@android Take it easy. One downvote isn't the end of the world.
jjnguy