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
It seems like the cause was the
ParcelFileDescriptor
being an argument of the service method. If the service does return theParcelFileDescriptor
it works as expected.Sending Service (Process A)
Receiving Service Code (Process B)
The receiving service's
.aidl
:The receiving service, called by Process A:
The ParcelFileDescriptorUtil is a helper class, with a classic
java.io.
stream-to-stream copy Thread. Now we have to use thepipeTo()
method.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.