1

I'm trying to serialize an object from within it and deserialize it using this answer: Reliably convert any object to String and then back again

But I get StreamCorruptedException while deserializing.

java.io.StreamCorruptedException
W/System.err:     at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:2065)
W/System.err:     at java.io.ObjectInputStream.<init>(ObjectInputStream.java:371)
W/System.err:     at ShoppingCart.load(ShoppingCart.java:154)

Here is the Class :

public class ShoppingCart implements Serializable {

ArrayList<Item> items ;
String token ;
transient Context context ;



public ShoppingCart(Context cntx){
    context = cntx ;
    items = new ArrayList<Item>();
    SharedPreferences preferences = context.getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE);
    token = preferences.getString("login_token", null);
}

public void emptyCart(){
    items = new ArrayList<Item>();
    store();
    System.gc();
}

public boolean addToCart(Item item){

    boolean exists = false ;

    for(int i = 0 ; i < items.size() ; i++){
        if(items.get(i).productID.equals(item.productID)){
            exists = true ;
            return false ;
        }
    }

    if(!exists)
        items.add(item);

    store();
    return true ;
}

public void removeFromCart(Item item){
    items.remove(item);
    store();
}

public void store() {
    SharedPreferences.Editor editor =
            context.getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE).edit();
    // serialize the object
    try {
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream so = new ObjectOutputStream(bo);
        so.writeObject(this);
        so.flush();
        String serializedObject = bo.toString();

        editor.putString("stored_cart", serializedObject);
        editor.commit();
    } catch (Exception e) {
        e.printStackTrace();
    }


}

public ShoppingCart load() {

    SharedPreferences preferences = context.getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE);
    String serializedObject = preferences.getString("stored_cart", null);
    ShoppingCart newCart = null ;

    // deserialize the object
    try {
        byte b[] = serializedObject.getBytes();
        ByteArrayInputStream bi = new ByteArrayInputStream(b);
        ObjectInputStream si = new ObjectInputStream(bi);
        newCart = (ShoppingCart) si.readObject();
        newCart.context = context ;
    } catch (Exception e) {
        e.printStackTrace();
    }

    return newCart ;
}

}

I'm calling the load() function like this:

    cart = new ShoppingCart(getApplicationContext());
    SharedPreferences preferences =
            getApplicationContext().getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE);


    if(preferences.getString("stored_cart", null) != null) {
        cart = cart.load();
        Log.d("AppController","cart loaded");
    }

Since the Context is not serilizable, so I made it transient.

What am I doing wrong ?

Community
  • 1
  • 1
Mohsen
  • 2,121
  • 3
  • 18
  • 25

1 Answers1

1

Firstly, from what I see, you cannot put a serializable into sharedprefs.

I tried saving to a database using a string value and convert it back and to a byte array. (I can store byte arrays as a blob in a database). When I used a String, the byte array got the wrong format which triggered the same exception you have right there: StreamCorrupedException

When I got this error and did research, what I got from it was that a StreamCorruptedException means you do something "bad" with the stream, that screws up the format.

Now, for the solution.

When I tried this, saved as a string and loaded back as a byte array, the different bytes aren't necessarily loaded back in the same format as they are saved. This is when you get the exception. You try to load a byte array from a String, but when you apply the byte array to a class it ends up as a corrupted byte array.

Basically, DO NOT SAVE IT AS A STRING AND CONVERT IT TO A BYTE ARRAY! What I did to be able to save was to actually save it as a byte array, and I use a database and databases support this. But from what I see, shared prefs do not. So basically, you cannot use a String and convert it to a byte array to save and then load. You have to use internal/external storage(basically files) or use a database with mode set to blob. But Shared Prefs simply do not support byte arrays which means when you convert the string to a byte array, it changes the stream and corrupts the data.

So you have three options:

  1. Store as file (internal/external)
  2. Store in database as blob
  3. Save each individual item in the cart as an individual item in the shared prefs.

(ANd make sure item is serializable as well, to prevent exceptions there later).

TL:DR; When you save the serializable class as a String, but convert it back to a byte array will give this error.

TEST CASE

The following test works on both Android and desktop Java. This testing was done using a Gradle project, but you can go to apache commons and find a different dependency depending on your project. The dependency is used for converting back and forth between byte array and class.

Required dependency for testing:

compile 'org.apache.commons:commons-lang3:3.5'

Sample class:

public class Something implements Serializable{
    public static final long serialVersionUID = 0L;
    int x = 12;
}

Sample test:

Something instance = new Something();
String class_ = SerializationUtils.serialize(instance).toString();
byte[] bytes = class_.getBytes();
instance = (Something) SerializationUtils.deserialize(bytes);

Output:

Exception in thread "main" org.apache.commons.lang3.SerializationException: java.io.StreamCorruptedException: invalid stream header: 5B424036
    at org.apache.commons.lang3.SerializationUtils.deserialize(SerializationUtils.java:229)
    at org.apache.commons.lang3.SerializationUtils.deserialize(SerializationUtils.java:265)
    at com.core.Launcher.main(Launcher.java:22)
Caused by: java.io.StreamCorruptedException: invalid stream header: 5B424036
    at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:857)
    at java.io.ObjectInputStream.<init>(ObjectInputStream.java:349)
    at org.apache.commons.lang3.SerializationUtils.deserialize(SerializationUtils.java:221)
    ... 2 more

Process finished with exit code 1

In my testing, this throws StreamCorruptedException, which basically shows one thing: You cannot convert a class to byte a byte array, then to a String and back to byte array

Note: This test case was executed on a WIndows 10 computer and Android 7(S6 edge), and it threw the same error (stacktrace changes because of there being two different projects).


Summary:

Saving and loading a byte array works fine. Where saving and loading a byte array involves a String in the middle, it corrupts the stream.

Solution:

Don't save with a String part to be able to save on an unsupported platform. That platform in this case being Sharedprefs. Use files or a database (with a blob field) are the only ways to save a byte array locally on a device in Android. Transferring data over the internet is an entirely different topic I am not going to cover.

So in order to serialize and deserialize with bytes, you have to save it as a file, or in a database. Converting back and forth between a string is what gives you the problems.


And finally, this error has nothing to do with an unserializable field. That throws a different error(NotSerializableException).

Zoe
  • 27,060
  • 21
  • 118
  • 148
  • 1
    Thanks for the time. I knew the `NotSerializableException` I was just explaining the `transient` thing. thank you very much – Mohsen May 17 '17 at 16:05