【问题标题】:Java StreamCorruptedException attempting deserialize the object read from SharedPreferences AndroidJava StreamCorruptedException 试图反序列化从 SharedPreferences Android 读取的对象
【发布时间】:2017-10-17 02:57:09
【问题描述】:

我正在尝试从其中序列化一个对象并使用以下答案反序列化它:Reliably convert any object to String and then back again

但我在反序列化时收到 StreamCorruptedException。

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)

这是类:

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 ;
}

}

我正在像这样调用load() 函数:

    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");
    }

由于Context 不可序列化,所以我将其设为transient

我做错了什么?

【问题讨论】:

    标签: java android serialization sharedpreferences deserialization


    【解决方案1】:

    首先,据我所知,您不能将可序列化放入 sharedprefs 中。

    我尝试使用字符串值保存到数据库并将其转换回字节数组。 (我可以将字节数组作为 blob 存储在数据库中)。当我使用字符串时,字节数组的格式错误,触发了您在此处遇到的相同异常:StreamCorrupedException

    当我收到此错误并进行研究时,我从中得到的是 StreamCorruptedException 意味着您对流做了“坏”的事情,这会破坏格式。

    现在,解决方案。

    当我尝试此操作时,将其保存为字符串并作为字节数组加载回,不同的字节不一定以与保存时相同的格式加载回。这是你得到异常的时候。您尝试从 String 加载字节数组,但是当您将字节数组应用于类时,它最终会成为损坏的字节数组。

    基本上,不要将其保存为字符串并将其转换为字节数组!为了能够保存,我所做的是将其实际保存为字节数组,并且我使用数据库并且数据库支持这一点。但据我所知,共享首选项没有。所以基本上,您不能使用字符串并将其转换为字节数组来保存然后加载。您必须使用内部/外部存储(基本上是文件)或使用模式设置为 blob 的数据库。但是共享首选项根本不支持字节数组,这意味着当您将字符串转换为字节数组时,它会更改流并破坏数据。

    所以你有三个选择:

    1. 存储为文件(内部/外部)
    2. 以 blob 形式存储在数据库中
    3. 将购物车中的每个单独项目保存为共享首选项中的单独项目。

    (并确保项目也是可序列化的,以防止以后出现异常)。

    TL:DR;当你将可序列化类保存为字符串,但将其转换回字节数组时会出现此错误。

    测试用例

    以下测试适用于 Android 和桌面 Java。此测试是使用 Gradle 项目完成的,但您可以转到 apache commons 并根据您的项目找到不同的依赖项。该依赖用于字节数组和类之间的来回转换。

    测试所需的依赖项:

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

    示例类:

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

    样本测试:

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

    输出:

    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
    

    在我的测试中,这会引发 StreamCorruptedException,它基本上表明了一件事:您不能将类转换为字节数组,然后转换为字符串并返回字节数组

    注意:此测试用例是在 WIndows 10 计算机和 Android 7(S6 edge) 上执行的,它抛出了相同的错误(由于有两个不同的项目,堆栈跟踪发生了变化)。


    总结:

    保存和加载字节数组可以正常工作。在保存和加载字节数组的地方涉及中间的字符串,它会破坏流。

    解决方案:

    不要使用字符串部分保存以便能够在不受支持的平台上保存。在这种情况下,该平台是 Sharedprefs。使用文件或数据库(带有 blob 字段)是在 Android 设备上本地保存字节数组的唯一方法。通过 Internet 传输数据是一个完全不同的主题,我不打算讨论。

    因此,为了使用字节进行序列化和反序列化,您必须将其保存为文件或数据库。在字符串之间来回转换会给您带来问题。


    最后,这个错误没有与不可序列化的字段有关。这会引发不同的错误(NotSerializableException)。

    【讨论】:

    • 感谢您的宝贵时间。我知道NotSerializableException 我只是在解释transient 的事情。非常感谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-14
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    • 2014-05-11
    相关资源
    最近更新 更多