Java.io.IOException, "bad file number" USB 连接

11
我正在为我的安卓手机和另一台设备建立USB附件连接,目前只是发送字节进行测试。一开始通讯很正常,但是大约一秒后就会因为 Java.io.IOException: write failed: EBADF (Bad file number)" 而停止。有时候读取仍然活着,但写入已经停止;其他情况两者都死了。
我没有做任何特别复杂的事情,像Google文档上所说一样读取和写入:
初始连接(在广播接收器中,至少最初我知道这部分可以工作):
if (action.equals(ACTION_USB_PERMISSION))
{
    ParcelFileDescriptor pfd = manager.openAccessory(accessory);
    if (pfd != null) {
        FileDescriptor fd = pfd.getFileDescriptor();
        mIn = new FileInputStream(fd);
        mOut = new FileOutputStream(fd);
    }
}

阅读:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        byte[] buf = new byte[BUF_SIZE];
        while (true)
        {
            try {
                int recvd = mIn.read(buf);
                if (recvd > 0) {
                    byte[] b = new byte[recvd];
                    System.arraycopy(buf, 0, b, 0, recvd);
                    //Parse message
                }
            }
            catch (IOException e) {
                Log.e("read error", "failed to read from stream");
                e.printStackTrace();
            }
        }
    }
});
thread.start();

写作:

synchronized(mWriteLock) {
    if (mOut !=null && byteArray.length>0) {
        try {
            //mOut.flush();
            mOut.write(byteArray, 0, byteArray.length);
        }
        catch (IOException e) {
            Log.e("error", "error writing");
            e.printStackTrace();
            return false;
        }
    }
    else {
        Log.e(TAG, "Can't send data, serial stream is null");
        return false;
    }
}

错误堆栈跟踪:

java.io.IOException: write failed: EBADF (Bad file number)
W/System.err(14028):     at libcore.io.IoBridge.write(IoBridge.java:452)
W/System.err(14028):     at java.io.FileOutputStream.write(FileOutputStream.java:187)
W/System.err(14028):     at com.my.android.transport.MyUSBService$5.send(MyUSBService.java:468)
W/System.err(14028):     at com.my.android.transport.MyUSBService$3.onReceive(MyUSBService.java:164)
W/System.err(14028):     at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:781)
W/System.err(14028):     at android.os.Handler.handleCallback(Handler.java:608)
W/System.err(14028):     at android.os.Handler.dispatchMessage(Handler.java:92)
W/System.err(14028):     at android.os.Looper.loop(Looper.java:156)
W/System.err(14028):     at android.app.ActivityThread.main(ActivityThread.java:5045)
W/System.err(14028):     at java.lang.reflect.Method.invokeNative(Native Method)
W/System.err(14028):     at java.lang.reflect.Method.invoke(Method.java:511)
W/System.err(14028):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
W/System.err(14028):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
W/System.err(14028):     at dalvik.system.NativeStart.main(Native Method)
W/System.err(14028): Caused by: libcore.io.ErrnoException: write failed: EBADF (Bad file number)
W/System.err(14028):     at libcore.io.Posix.writeBytes(Native Method)
W/System.err(14028):     at libcore.io.Posix.write(Posix.java:178)
W/System.err(14028):     at libcore.io.BlockGuardOs.write(BlockGuardOs.java:191)
W/System.err(14028):     at libcore.io.IoBridge.write(IoBridge.java:447)
W/System.err(14028):     ... 13 more

我在各个地方都有日志记录,因此我知道它不是任何太明显的问题,例如收到另一个权限请求(因此文件流在读取过程中重新初始化)。流也没有关闭,因为我从来没有在我的代码中发生这种情况(目前为止)。我也没有收到任何已分离或已连接事件(如果发生,我会记录下来)。似乎没有什么异常的情况;它只是停止了。
我想也许这是一个并发问题,所以我尝试了锁定和睡眠,但我尝试的一切都没有起作用。我认为这也不是吞吐量问题,因为当我每次读取时都睡眠(两端都是如此),并且一次只读取一个数据包(超慢的比特率)时,它仍然会发生。有可能缓冲区在另一端被溢出吗?我应该如何清除这个问题?我可以访问另一端的代码,它也是使用主机模式的Android设备。如果需要,我可以发布那段代码-标准的批量传输。
这个错误在Android上写入或读取USB时是什么原因引起的呢?

你使用的是哪个版本的Android?另外,你尝试着要用什么进行通信?我认为如果你在使用Android Open Accessory Mode,你需要一个兼容的主机设备,例如Arduino或FT311D。 - TronicZomB
Android 4.0.4。我可以很好地建立初始连接,并且没有所谓的“兼容”设备 - 如果您发送正确的控制请求,则手机会进入支持它的附件模式。我的手机上弹出一个对话框,说明它看到了一个附件 - 只是在保持连接方面遇到了问题。 - user155407
哦,好的,请等一下,它看到了一个配件,这意味着您的手机处于主机模式而不是从机模式,对吧?提供电源的设备是哪个,是电话提供给设备还是设备提供给电话? - TronicZomB
不,Android文档中的术语有点令人困惑。它将其视为配件,意味着它进入了配件模式。当您在手机上处于配件模式时,可以遍历UsbAccessory对象-您在配件模式下看到它们。该设备有点像Android平板电脑(我认为也是4.0.4);它正在为我的手机提供电源。它通过主机模式进行通信。最初所有这些都很正常,只是最终出现了“坏文件号”错误。 - user155407
好的,我主要使用开放附件模式进行工作,所以我正在尝试找出更多具体信息,以了解我能够帮助您多少... 所以听起来您有两个 Android 设备通过 USB 相互通信,一个是主机,一个是附件?是这样吗?并且您在附件 Android 设备上遇到了“坏文件号”的问题? - TronicZomB
没错。主机设备似乎没有任何问题。 - user155407
3个回答

8

我在我的代码中遇到了同样的问题,发现这是因为FileDescriptor对象被垃圾回收了。

我通过在Activity(或Service)中添加ParcelFileDescriptor字段来解决此问题。

我检查了您的第一个代码片段和您基于的代码,后者在Thread中有ParcelFileDescriptor字段。

我认为如果您按照以下方式编辑您的代码,它将正常工作。

ParcelFileDescriptor mPfd;
...

if (action.equals(ACTION_USB_PERMISSION))
{
    mPfd = manager.openAccessory(accessory);
    if (mPfd != null) {
        FileDescriptor fd = mPfd.getFileDescriptor();
        mIn = new FileInputStream(fd);
        mOut = new FileOutputStream(fd);
    }
} 

1
感谢您为此做出的贡献。我已经多年没有做过这样的事了,但希望能对人们有所帮助。 - user155407
1
这个回答绝对值得更多的赞,ParcelFileDescriptor被GC'ed的事实并不明显,我有一个返回new FileOutputStream(mPfd.getFileDescriptor())的方法,在几次传输后看到了这样的错误。我在这上面花费了很多时间,谢谢伙计! - Miro Kropacek

7

最终问题是线程相关的。我需要更好地区分读写,而不仅仅是读取。

最终我使用了这段代码作为基础。


1
嗨,我遇到了同样的问题,我看了上面的链接,但对我没有用。请分享一下是什么线程问题。 - ashokk

1

好的,我注意到一些与我在使用开放附件模式时不同的地方,我主要是遵循USB附件的文档,所以应该非常相似,就是你的mIn.read(buf);应该改成mIn.read(buf, 0, 64);,据我所知。

另外,在你的类声明中,你应该声明thread myThread;。然后在你的BroadcastReceiver中,在创建新的FileInput/OutputStream之后,有myThread = new thread(myHandler, myInputStream);,然后是myThread.start();

现在我注意到你直接从你的线程与UI进行通信。从我所读到的内容来看,你应该使用一个处理程序,线程将与其通信,然后再将其传回到你的UI。

这是我的处理程序和线程的示例:

final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg){

    }
};

private class USB_Thread extends Thread {
    Handler thisHandler;
    FileInputStream thisInputStream;

    USB_Thread(Handler handler, FileInputStream instream){
        thisHandler = handler;
        thisInputStream = instream;
    }
    @Override
    public void run(){
        while(true) {
            try{
                if((thisInputStream != null) && (dataReceived == false)) {
                    Message msg = thisHandler.obtainMessage();
                    int bytesRead = thisInputStream.read(USB_Data_In, 0, 63);
                    if (bytesRead > 0){
                        dataReceived = true;
                        thisHandler.sendMessage(msg);
                    }
                }
            }
            catch(IOException e){

            }
        }
    }
}

此外,这里还有一些演示开放式附件应用程序在这里。它们可能有助于您理解附件模式。

还有一个已知问题,即应用程序不能通过编程方式接收ACTION_USB_ACCESSORY/DEVICE_ATTACHED的广播接收器。 它只能通过清单文件接收。 您可以在这里这里找到更多信息。

我之前并没有测试将dataReceived变量放在处理程序中,直到最近才更改了我的代码。我进行了测试,但它没有起作用,所以我试图记住我读过的内容,我认为这不是关于线程内变量通信的问题,而是尝试使用像.setText()这样的东西。我已经更新了我的代码,包括在线程中加入了dataReceived=true。然后处理程序将用于更新UI上的项目,例如TextView等。

线程

FileDescriptor fd = mFileDescriptor.getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
usbThread = new USB_Thread(mHandler, mInputStream);
usbThread.start();

谢谢你的建议,但我并没有直接与用户界面进行通信;你是从哪里得到这个信息的呢?mRead 的重载确实是个好建议,我应该总是指定一个最大值,但它并没有改变太多事情。我觉得这可能是硬件方面的问题……很难说。 - user155407
是的,UI的读取错误可能是硬件问题。我已经在三个不同的平板电脑上测试了开放式附件代码,一个完全不起作用的非品牌产品、Nexus 7 在启动时工作良好,但从关闭状态下重新启动时存在问题,以及 Galaxy Tab 2 7",它的表现类似于 Nexus,但没有启动问题。不过你的读线程从哪里开始呢? - TronicZomB
我已经编辑了我的答案。在你的输入/输出流声明之后尝试类似的代码,也许那会起作用... - TronicZomB
我刚刚注意到的另一件事是,尝试将synchronized()命令移动到BroadcastReceiver中ParcelFileDescriptor之前。 - TronicZomB
再次感谢您的建议,我尝试了一些您的建议以及其他方法,但仍然失败。然而,我进一步深入挖掘,唯一看到的问题是StrictMode$InstanceCountViolation,显然您可以关闭严格模式。我尝试了这个方法,但还是没有成功。我将尝试在硬件中明确声明我正在使用的手机的权限,看看是否有所帮助。 - user155407
不客气。你的清单文件中已经声明了USB权限,对吧? - TronicZomB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接