UsbRequest.queue崩溃的Andr​​oid 3.1应用程序应用程序、queue、UsbRequest、Andr

2023-09-07 09:22:45 作者:上课玩手机玩的就是心跳

我正在开发一个使用USB主机模式,我的键盘(Korg的M3)通过MIDI通过USB通信的的Andr​​oid 3.1应用程序。这是对我的Xoom运行与安装的Andr​​oid 4.0.3。我可以通过USB接收MIDI信息没有任何问题,但发送的音符数据回我的键盘是有成败参半,频繁死机半秒的延迟之后。

I'm developing an Android 3.1 application that uses USB Host mode to communicate with my keyboard (Korg M3) via MIDI over USB. This is running on my Xoom with Android 4.0.3 installed. I'm able to receive MIDI messages over USB without any problems, but sending note data back to my keyboard is having mixed success, with frequent crashes after a half-second delay.

下面是我不断收到我一敲操作栏的按钮发送记录错误:

Here's the error I keep getting as I tap the button on the Action Bar to send a note:

E / dalvikvm(6422):JNI错误(应用程序错误):访问过时的全球参考0x1da0020a(指数130大小130表)

E/dalvikvm(6422): JNI ERROR (app bug): accessed stale global reference 0x1da0020a (index 130 in a table of size 130)

我查了一下/设法追查原因:

What I've checked/tried to track down the cause:

由于code是多线程的,我有Java的同步块周围的访问到输出请求池,输入请求池(根据的 Android的文档中的亚样本),以及当前输出请求和放大器的自定义锁对象;相关的ByteBuffer 对象引用。我已经结构化了code,执行这些锁来减少发生死锁的可能性。 当从相关请求池中获取可用的 UsbRequest 的对象,我设置了clientData提到一个新的的ByteBuffer 对象,而不是重复使用previously相关的ByteBuffer 对象并调用明确()就可以了。 在该code中的关键点

我添加了大量的日志记录电话(logcat中),试图追查确切位置在哪里它的失败。什么我发现是错误最终会发生在以下点(这code正常工作数次直到那时): Since the code is multithreaded, I have Java synchronized blocks surrounding accesses to the output request pool, input request pool (as per the ADB sample in the Android documentation), and a custom lock object for the current output request & associated ByteBuffer object references. I have structured the code that performs these locks to minimise the likelihood of deadlocks occurring. When retrieving an available UsbRequest object from the relevant request pool, I am setting the clientData reference to a new ByteBuffer object, rather than reusing the previously-associated ByteBuffer object and calling clear() on it.

I have added extensive logging calls (for logCat) at the critical points in the code to try and track down where exactly it's failing. What I've found is that the error eventually occurs at the following point (this code works fine for a few times up until then):

public void sendMidiData()
{
    synchronized(_outputLock)
    {
        if(_currentOutputRequest == null || _outputBuffer.position() < 2)
            return;

        Log.d(_tag, "Queuing Send request");
        //// ERROR - happens in this next statement:
        _currentOutputRequest.queue(_outputBuffer, _maxPacketSize);
        Log.d(_tag, "Send request queued; resetting references...");
        //Initialise for next packet
        _currentOutputRequest = null; //getAvailableSendRequest();
        _outputBuffer = null; //(ByteBuffer)_currentOutputRequest.getClientData();
        Log.d(_tag, "Output request & buffer references set.");
    }
}

我自己也尝试设置 _currentOutputRequest _outputBuffer 引用使得可用的请求检索只有在下一MIDI事件是要被写入到缓冲器。如果由原始调用替换(如图所示注释掉),下一个可用的请求立即检索。这并没有区别。

I have also tried setting the _currentOutputRequest and _outputBuffer references to null so that the available request is retrieved only when the next MIDI event is to be written to the buffer. If null is replaced by the original calls (as shown commented out), the next available request is retrieved immediately. This has made no difference.

有没有什么想法,什么是造成这个问题?难道这是Android的一个previously,未被发现的错误?在返回的不带回来多少该错误的搜索;主要参考NDK编程(这我不这样做)。

Is there any idea as to what would be causing this issue? Could this be a previously-undiscovered bug in Android? A search on the error returned doesn't bring back much; mainly references to NDK programming (which I'm not doing).

干杯。

推荐答案

好吧,我有一点突破,并发现了两个问题:

Okay, I had a bit of a breakthrough and found two issues:

_currentOutputRequest.queue调用(_outputBuffer,_maxPacketSize); 路过整个缓冲区容量 _maxPacketSize ,其中具有64(字节)的恒定值。显然,这仅适用于批量读取,有多达64个字节将被读取;批量发送的请求需要指定发送的字节的确切人数。 的 _currentOutputRequest.queue() _connection.requestWait()方法调用似乎不是线程安全的,特别是在 UsbDeviceConnection (即 _connection 的类型)的实施。我怀疑UsbRequest _currentOutputRequest 内部排队发送请求时使用的UsbConnection对象。 The call to _currentOutputRequest.queue(_outputBuffer, _maxPacketSize); was passing the entire buffer capacity _maxPacketSize, which has a constant value of 64 (bytes). Apparently this only works for bulk reads, where up to 64 bytes will be read; bulk Send requests need to specify the exact number of bytes being sent. The _currentOutputRequest.queue() and _connection.requestWait() method calls appear not to be thread-safe, particularly in the implementation of UsbDeviceConnection (which is the type of _connection). I suspect the UsbRequest _currentOutputRequest internally uses the UsbConnection object when queuing a Send request.

我该如何解决了这个问题:

在调用队列()更改为:

_currentOutputRequest.queue(_outputBuffer, _outputBuffer.position());

对于第二个问题,队列()语句已经发生同步块内,使用 _outputLock 对象。在读线程的运行()的方法,我有包装在一个调用 requestWait() 同步模块也使用 outputLock

For the second issue, the queue() statement already occurs inside a synchronized block, using the _outputLock object. In the Run() method of the reader thread, I have had to wrap the call to requestWait() in a synchronized block also using outputLock:

UsbRequest request = null;
synchronized(_outputLock)
{
    // requestWait() and request.queue() appear not to be thread-safe.
    request = _connection.requestWait();
}

由于这发生在,而循环和 requestWait 阻塞线程,我发现了一个重要的问题是,试图排队一个发送请求时,锁会导致饥饿。其结果是,虽然应用程序及时接收和作用于输入的MIDI数据,输出MIDI事件被显著延迟。

Given that this occurs in a while loop and requestWait blocks the thread, one major issue I found is that the lock causes starvation when trying to queue a Send request. The result is that while the application receives and acts on incoming MIDI data in a timely fashion, outputted MIDI events are significantly delayed.

对于这部分修复,我只是,而循环结束之前插入一个收益率语句保持UI线程畅通。 (UI线程暂时被用作一个音符事件是由一个按钮preSS触发,这最终将使用一个单独的播放线程。)因此,它的好,但并不完美,因为还是有之前相当延迟第一输出注发送。

As a partial fix for this, I inserted a yield statement just before the end of the while loop to keep the UI thread unblocked. (The UI thread is temporarily being used as a note event is triggered by a button press; this will eventually use a separate playback thread.) As a result it's better, but not perfect, since there's still quite a delay before the first output note is sent.

更好的解决方案:

要解决的相互锁定的需求,用于读取和写入异步,异步队列() requestWait()方法只用于读操作,它仍保留在单独的阅读器的线程。正因为如此,在同步块是不必要的,所以这部分可以简化为以下内容:

To resolve the demand on a mutual lock for reading and writing asynchronously, the asynchronous queue() and requestWait() methods are only used for read operations, which remain on the separate 'reader' thread. Because of this, the synchronized block is unnecessary, so this segment can be reduced to the following:

UsbRequest request = _connection.requestWait();

至于写/发送操作,这样做的核心移动到一个单独的线程来执行同步 bulkTransfer()语句:

private class MidiSender extends Thread
{
    private boolean _raiseStop = false;
    private Object _sendLock = new Object();

    private LinkedList<ByteBuffer> _outputQueue = new LinkedList<ByteBuffer>();

    public void queue(ByteBuffer buffer)
    {
        synchronized(_sendLock)
        {
            _outputQueue.add(buffer);
            // Thread will most likely be paused (to save CPU); need to wake it
            _sendLock.notify();
        }
    }

    public void raiseStop()
    {
        synchronized (this)
        {
            _raiseStop = true;
        }

        //Thread may be blocked waiting for a send
        synchronized(_sendLock)
        {
            _sendLock.notify();
        }
    }

    public void run()
    {
        while (true)
        {
            synchronized (this)
            {
                if (_raiseStop) 
                    return;
            }

            ByteBuffer currentBuffer = null;
            synchronized(_sendLock)
            {
                if(!_outputQueue.isEmpty())
                    currentBuffer =_outputQueue.removeFirst(); 
            }

            while(currentBuffer != null)
            {
                // Here's the synchronous equivalent (timeout is a reasonable 0.1s):
                int transferred = _connection.bulkTransfer(_outPort, currentBuffer.array(), currentBuffer.position(), 100);

                if(transferred < 0)
                    Log.w(_tag, "Failed to send MIDI packet");

                //Process any remaining packets on the queue
                synchronized(_sendLock)
                {
                    if(!_outputQueue.isEmpty())
                        currentBuffer =_outputQueue.removeFirst();
                    else
                        currentBuffer = null;
                }
            }

            synchronized(_sendLock)
            {
                try
                {
                    //Sleep; save unnecessary processing
                    _sendLock.wait();
                }
                catch(InterruptedException e)
                {
                    //Don't care about being interrupted
                }
            }

        }           
    }
}

起初我担心异步code与上述(因为它们共享相同的 UsbDeviceConnection )发生冲突,但是这似乎不是一个问题,因为他们使用的是完全不同的 UsbEndpoint 实例。

At first I was concerned about the asynchronous code conflicting with the above (since they share the same UsbDeviceConnection), but this seems not to be an issue since they're using completely different UsbEndpoint instances.

好消息是,应用程序运行更顺畅,同时发送笔记时,因为我打的键盘不会崩溃。

The good news is that the application runs more smoothly and doesn't crash when sending notes at the same time as I'm playing the keyboard.

所以一般(有关独立终端的双向USB通信),似乎异步方法最适合读/输入操作,我们并不需要担心定义轮询行为(这是否是是什么在内部发生),而输出/发送操作更好地同步 bulkTransfer()的方法提供服务。

So in general (for bi-directional USB communication on separate endpoints), it seems that the asynchronous methods are best suited for read/input operations where we don't need to worry about defining polling behaviour (whether or not this is what happens internally), while output/send operations are better served by the synchronous bulkTransfer() method.