Перенесите InputStream на другую службу (через границы процесса) с помощью параметра ParcelFileDescriptor.createPipe() с "EBADF (Bad file number)",

Я хочу "отправить" InputStream из одной службы Android в другую службу, запущенную в другом процессе, используя ParcelFileDescriptor.createPipe(), a потоковый поток потока и ParcelFileDescriptor, представляющий сторону чтения канала, которая передается другой службе с помощью Binder IPC.

Отправка кода (процесс A)

Я хочу отправить данный InputStream в приемную службу:

public sendInputStream() {
    InputStream is = ...; // that 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
        }
    }
};

Но in.read() в inputStream() всегда выдает 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)

Кажется, что errno EBADF устанавливается read(), когда дескриптор файла закрыт. Но я не знаю, что вызывает его и как его исправить.

И да, я знаю, что ConentProvider также будет возможностью. Но разве это не должно работать с моим подходом? Есть ли другие способы передать поток InputStream для другой службы на Android?

На стороне примечания: CommonsWare создал аналогичный проект 1, 2). Это где я получил большинство идей для моего подхода от

Ответ 1

Похоже, причиной было ParcelFileDescriptor, являющийся аргументом метода службы. Если служба возвращает ParcelFileDescriptor, она работает должным образом.

Служба отправки (процесс A)

public sendInputStream() {
    InputStream is = ...; // that 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 InputStream
                // from the following OutputStream
                OutputStream os = ...;
                ParcelFileDescriptor pfd = ParcelFileDescriptorUtil.pipeTo(os);
                return pfd;
    }
};

ParcelFileDescriptorUtil - это вспомогательный класс, с классическим потоком копирования java.io. stream-to-stream. Теперь мы должны использовать метод 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 через границы процесса, один из недостатков заключается в том, что в копиях потокового потока присутствует некоторое время процессора.