使用 ParcelFileDescriptor.createPipe() 将输入流传输到另一个服务(跨进程边界)失败,并"EBADF (Bad file number)"



我想通过使用ParcelFileDescriptor.createPipe()、流到流复制线程和ParcelFileDescriptor将InputStream从一个Android服务"发送"到另一个在不同进程中运行的服务,ParcelFile描述符表示管道的读取端,通过Binder IPC将其提供给另一个服务。

发送代码(进程A)

我想将给定的InputStream发送到接收服务:

public sendInputStream() {
    InputStream is = ...; // that's the stream for process/service B
    ParcelFileDescriptor pdf = ParcelFileDescriptorUtil.pipeFrom(is);
    inputStreamService.inputStream(pdf);
}

ParcelFileDescriptorUtil是一个助手类,具有经典的java.io.流到流复制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) {
                }
            }
        }
    }
}

接收服务代码(流程B)

接收服务的.aidl:

package org.exmaple;
interface IInputStreamService {
    void inputStream(in ParcelFileDescriptor pfd);
}

接收服务,由进程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
        }
    }
};

inputStream()中的in.read()总是抛出一个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)

当文件描述符关闭时,EBADF errno似乎是由read()设置的。但我不知道是什么原因造成的,也不知道如何修复

是的,我知道ConentProvider也有可能。但它不应该也适用于我的方法吗?有没有其他方法可以将InputStream流交给Android中的不同服务?

附带说明:CommonsWare使用ContentProvider创建了一个类似的项目(相关SO问题1、2)。这是我从

获得方法大部分想法的地方

原因似乎是ParcelFileDescriptor是服务方法的参数。如果服务确实返回ParcelFileDescriptor,它将按预期工作。

发送服务(进程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) {}
    }
}

接收服务代码(流程B)

接收服务的.aidl:

package org.exmaple;
interface IInputStreamService {
    ParcelFileDescriptor inputStream();
}

接收服务,由进程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;
    }
};

ParcelFileDescriptorUtil是一个助手类,具有经典的java.io.流到流复制线程现在我们必须使用pipeTo()方法

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) {
                }
            }
        }
    }
}

这允许您跨进程边界传输InputStreams,一个缺点是流到流拷贝中涉及一些CPU时间。

最新更新