Transfer InputStream to another Service (across process boundaries) with ParcelFileDescriptor.createPipe() failes with "EBADF (Bad file number)"
android, android-binder, android-service, ipc, pipe
Solution
It seems like the cause was the `ParcelFileDescriptor` being an argument of the service method. If the service does return the `ParcelFileDescriptor` it works as expected.
Sending Service (Process A)
public void sendInputStream() {
InputStream is = ...; // that's the stream for process/service B
ParcelFileDescriptor pfd = inputStreamService.inputStream();
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
int len;
byte[] buf = new byte[1024];
try {
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
} catch (IOException e) {
} finally {
try { is.close(); } catch (IOException e1) {}
try { os.close(); } catch (IOException e1) {}
}
}
Receiving Service Code (Process B)
The receiving service's `.aidl`:
package org.exmaple;
interface IInputStreamService {
ParcelFileDescriptor inputStream();
}
The receiving service, called by Process A:
public class InputStreamService extends Service {
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IInputStreamService.Stub mBinder = new IInputStreamService.Stub() {
@Override
public void ParcelFileDescriptor inputStream() throws RemoteException {
// one can read the contents of the Processes A's InputStream
// from the following OutputStream
OutputStream os = ...;
ParcelFileDescriptor pfd = ParcelFileDescriptorUtil.pipeTo(os);
return pfd;
}
};
The ParcelFileDescriptorUtil is a helper class, with a classic `java.io.` stream-to-stream copy Thread. Now we have to use the `pipeTo()` method.
public class ParcelFileDescriptorUtil {
public static ParcelFileDescriptor pipeTo(OutputStream outputStream) throws IOException {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
ParcelFileDescriptor readSide = pipe[0];
ParcelFileDescriptor writeSide = pipe[1];
// start the transfer thread
new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(readSide), outputStream).start();
return writeSide;
}
static class TransferThread extends Thread {
final InputStream mIn;
final OutputStream mOut;
TransferThread(InputStream in, OutputStream out) {
super("ParcelFileDescriptor Transfer Thread");
mIn = in;
mOut = out;
setDaemon(true);
}
@Override
public void run() {
byte[] buf = new byte[1024];
int len;
try {
while ((len = mIn.read(buf)) > 0) {
mOut.write(buf, 0, len);
}
mOut.flush(); // just to be safe
} catch (IOException e) {
LOG.e("TransferThread", e);
}
finally {
try {
mIn.close();
} catch (IOException e) {
}
try {
mOut.close();
} catch (IOException e) {
}
}
}
}
}
This allows you to transfer InputStreams across process boundaries, one drawback is that there is some CPU time involved in the stream-to-stream copies.
Problem
I want to "send" an InputStream from one Android Service to another service running within a different process by using `ParcelFileDescriptor.createPipe()`, a stream-to-stream copy thread and a ParcelFileDescriptor, representing the read side of the pipe, which is given to the other service with means of Binder IPC. Sending Code (Process A) I want to send a given InputStream to the receiving service: ``` public sendInputStream() { InputStream is = ...; // that's the stream for process/service B ParcelFileDescriptor pdf = ParcelFileDescriptorUtil.pipeFrom(is); inputStreamService.inputStream(pdf); } ``` The ParcelFileDescriptorUtil is a helper class, with a classic `java.io.` stream-to-stream copy Thread: ``` public class ParcelFileDescriptorUtil { public static ParcelFileDescriptor pipeFrom(InputStream inputStream) throws IOException { ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); ParcelFileDescriptor readSide = pipe[0]; ParcelFileDescriptor writeSide = pipe[1]; // start the transfer thread new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)).start(); return readSide; } static class TransferThread extends Thread { final InputStream mIn; final OutputStream mOut; TransferThread(InputStream in, OutputStream out) { super("ParcelFileDescriptor Transfer Thread"); mIn = in; mOut = out; setDaemon(true); } @Override public void run() { byte[] buf = new byte[1024]; int len; try { while ((len = mIn.read(buf)) > 0) { mOut.write(buf, 0, len); } mOut.flush(); // just to be safe } catch (IOException e) { LOG.e("TransferThread", e); } finally { try { mIn.close(); } catch (IOException e) { } try { mOut.close(); } catch (IOException e) { } } } } } ``` Receiving Service Code (Process B) The receiving service's `.aidl`: ``` package org.exmaple; interface IInputStreamService { void inputStream(in ParcelFileDescriptor pfd); } ``` The receiving service, called by Process A: ``` public class InputStreamService extends Service { @Override public IBinder onBind(Intent intent) { return mBinder; } private final IInputStreamService.Stub mBinder = new IInputStreamService.Stub() { @Override public void inputStream(ParcelFileDescriptor pfd) throws RemoteException { InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd); OutputStream os = ...; int len; byte[] buf = new byte[1024]; try { while ((len = is.read(buf)) > 0) { os.write(buf, 0, len); } } catch (IOException e) { // this catches the exception shown below } } }; ``` But `in.read()` in `inputStream()` always throws a `IOException` ``` java.io.IOException: read failed: EBADF (Bad file number) at libcore.io.IoBridge.read(IoBridge.java:442) at java.io.FileInputStream.read(FileInputStream.java:179) at java.io.InputStream.read(InputStream.java:163) ``` It seems like the EBADF errno is set by `read()` when the file descriptor is closed. But I don't know what is causing it and how to fix it. And yes, I know that a ConentProvider would also be a possibility. But shouldn't it also work with my approach? Are there any other ways to hand an InputStream stream to a different service in Android? On a side note: CommonsWare created a similar project using a ContentProvider (related SO questions 1, 2). It's where I got most of the ideas for my approach from