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.
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) {
}
}
}
}
}
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 the ParcelFileDescriptor
it works as expected.
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) {}
}
}
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.