草庐IT

android - 使用 ParcelFileDescriptor.createPipe() 将 InputStream 传输到另一个服务(跨进程边界)失败,返回 "EBADF (Bad file number)"

coder 2023-11-19 原文

我想通过使用 ParcelFileDescriptor.createPipe() 将 InputStream 从一个 Android 服务“发送”到在不同进程中运行的另一个服务。 ,一个流到流的复制线程和一个 ParcelFileDescriptor,代表管道的读取端,它通过 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. 流到流复制线程:

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 创建了一个类似的 project using a ContentProvider (相关 SO 问题 12 )。这是我从中获得大部分方法想法的地方

最佳答案

看起来原因是 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 时间。

关于android - 使用 ParcelFileDescriptor.createPipe() 将 InputStream 传输到另一个服务(跨进程边界)失败,返回 "EBADF (Bad file number)",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18212152/

有关android - 使用 ParcelFileDescriptor.createPipe() 将 InputStream 传输到另一个服务(跨进程边界)失败,返回 "EBADF (Bad file number)"的更多相关文章

  1. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  2. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  5. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  6. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  7. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  8. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  9. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  10. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

随机推荐