To pass large amounts of data over binder, we create a pipe, then pass the read end of the pipe over the binder as a ParcelFileDescriptor
, and start a thread to write data to the write end of the pipe. It basically looks like this:
public void writeToParcel(Parcel out, int flags) {
ParcelFileDescriptor[] fds;
try {
fds = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
throw new RuntimeException(e);
}
out.writeParcelable(fds[0], 0);
byte[] bytes = ...; // Marshall object data to bytes
write(bytes, fds[1]); // Starts a thread to write the data
}
The receiving end reads the data from the read end of the pipe. It looks like this:
ParcelFileDescriptor readFd = in.readFileDescriptor();
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(readFd);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] b = new byte[16 * 1024];
int n;
try {
while ((n = fis.read(b)) != -1) {
out.write(b, 0, n);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
Log.i(TAG, "Closing read file descriptor..."); // I see this
fis.close();
Log.i(TAG, "Closed read file descriptor"); // And I see this
} catch (IOException e) {
e.printStackTrace();
}
}
This works, but when strictmode is enabled, we crash with this:
01-03 14:26:48.099 E/StrictMode(25346): A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
01-03 14:26:48.099 E/StrictMode(25346): java.lang.Throwable: Explicit termination method 'close' not called
01-03 14:26:48.099 E/StrictMode(25346): at dalvik.system.CloseGuard.open(CloseGuard.java:223)
01-03 14:26:48.099 E/StrictMode(25346): at android.os.ParcelFileDescriptor.<init>(ParcelFileDescriptor.java:192)
01-03 14:26:48.099 E/StrictMode(25346): at android.os.ParcelFileDescriptor.<init>(ParcelFileDescriptor.java:181)
01-03 14:26:48.099 E/StrictMode(25346): at android.os.ParcelFileDescriptor.createPipe(ParcelFileDescriptor.java:425)
01-03 14:26:48.099 E/StrictMode(25346): at com.clover.sdk.FdParcelable.writeToParcel(FdParcelable.java:118)
Line 118 is the creation of the pipe (ParcelFileDescriptor.createPipe()).
So it seems the sender needs to close the read end as well as the write end. My problem is that I don't know when I can close the read end, as I don't know when the reader will finish reading.
What am I missing?
As soon as you're finished writing close the output stream.
The consumer is always responsible to close their input stream as soon as they're done reading. This is not your responsibility (unless you act as the consumer as well).
What you're describing is analogous to me opening a FileOutputStream
(I'm consuming the API) and expecting the runtime to close it for me just because I didn't close it explicitly when I was done using it.
So send the FD in a Parcel
and whoever uses it is responsible for closing it. They could use something like this:
val fd = parcel.readFileDescriptor()
val input = ParcelFileDescriptor.AutoCloseInputStream(fd)
// Use input. When #close() is called it will also close the FD.
See: https://developer.android.com/reference/android/os/ParcelFileDescriptor.AutoCloseInputStream
You can hide this implementation in your client SDK library. In any case, document it well for the consumers.