0
votes

I have a class which contains a reference to file uri. This file is supposed to be transferred over the network, can be big (a video, for instance) and I'd like to do it transparently during instance (de)serialization:

public class NetworkAttachment implements Serializable {
    private static final long serialVersionUID = 1L;

    private final String name;  // file name
    private final long length;  // file length
    private final long lastModified;    // last modification time
    private final String type;  // mime type

    private transient final Context context;
    private transient final Uri uri;

    [...]

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();   // write all non-transient fields

        ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
        if (parcelFileDescriptor != null) {
            FileInputStream fis = new FileInputStream(parcelFileDescriptor.getFileDescriptor());

            byte[] buffer = new byte[1024 * 1024];  // 1 MB temp buffer
            int len;
            while ((len = fis.read(buffer)) != -1) {
                oos.write(buffer, 0, len);
                // reset the cache else OOM exception
                oos.reset();
            }
        }
    }
}

On the other side the instance is read by equivalent code:

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
        ois.defaultReadObject();    // read all non-transient fields

        File file = new File(name);

        FileOutputStream fos = new FileOutputStream(file);
        byte[] buffer = new byte[1024 * 1024];  // 1 MB temp buffer
        int len;
        while ((len = ois.read(buffer)) != -1) {  // <--- StreamCorruptedException
            fos.write(buffer, 0, len);
        }
    }

The reset() call is there because without it the amount of cached data is way too much for the poor Android device and the whole app crashes on an OOM exception. There are several posts here on SO which recommend a fix like this.

The problem is that if I put several instances of NetworkAttachment into an array and try to serialize it over the network, it doesn't work in all cases.

If two Android devices are streaming the serialized data to each other, no problem whatsoever, I can transfer 10s of MB and everything is fine.

However when one Android device is uploading the file to a Java server app (JDK 7), I'm getting:

java.io.StreamCorruptedException: unexpected reset; recursion depth: 2 at java.io.ObjectInputStream.handleReset(ObjectInputStream.java:2028) at java.io.ObjectInputStream.access$600(ObjectInputStream.java:206) at java.io.ObjectInputStream$BlockDataInputStream.readBlockHeader(ObjectInputStream.java:2510) at java.io.ObjectInputStream$BlockDataInputStream.refill(ObjectInputStream.java:2550) at java.io.ObjectInputStream$BlockDataInputStream.read(ObjectInputStream.java:2709) at java.io.ObjectInputStream.read(ObjectInputStream.java:865) at java.io.InputStream.read(InputStream.java:101) at com.croconaut.network.NetworkAttachment.readObject(NetworkAttachment.java:186) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1897) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370) at java.util.ArrayList.readObject(ArrayList.java:771) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1897) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370) at com.croconaut.CptServer$2$1.run(CptServer.java:148)

If I remove the reset() call, it works (naturally). However, I desperately need the reset() call to keep the memory consumption down, so what are my options here?

2

2 Answers

0
votes

You should not call reset() inside a writeObject() method. Call it outside, in the calling code. It won't have much effect where it is anyway, and calling it inside the write loop is quite pointless.

-1
votes

In the end, I had implemented it in a different way (not using ObjectOutputStream) but the problem was in JDK incompatibility -- the Android side running JDK 6 (by Google) and the server running JDK 7 (by Oracle). Perhaps there were some code changes in the streaming code and reset() compatibility wasn't kept.