UsbRequest.queue崩溃了Android 3.1应用程序

d_m*_*mcg 2 java crash android

我正在开发一个Android 3.1应用程序,它使用USB主机模式通过USB over USB与我的键盘(Korg M3)进行通信.这是在安装了Android 4.0.3的Xoom上运行的.我能够通过USB接收MIDI信息而没有任何问题,但将笔记数据发送回键盘却取得了不同的成功,在半秒延迟后频繁崩溃.

这是我在操作栏上点按按钮发送注释时遇到的错误:

E/dalvikvm(6422):JNI ERROR(app bug):访问过时的全局引用0x1da0020a(大小为130的表中的索引130)

我检查/试图追查原因:

  • 由于代码是多线程的,我有Java synchronized块,包括对输出请求池,输入请求池(根据Android文档中的ADB示例)的访问,以及当前输出请求和关联ByteBuffer对象引用的自定义锁对象.我已经构造了执行这些锁的代码,以最大限度地减少发生死锁的可能性.
  • UsbRequest从相关请求池中检索可用对象时,我将clientData引用设置为新ByteBuffer对象,而不是重用以前关联的ByteBuffer对象并对其进行调用clear().
  • 我已经在代码的关键点添加了大量的日志记录调用(对于logCat),以尝试跟踪其确切失败的位置.我发现错误最终发生在以下几点(此代码在此之前可以正常运行几次):

    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.");
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 我还尝试设置_currentOutputRequest_outputBuffer引用,null以便仅在将下一个MIDI事件写入缓冲区时检索可用请求.如果null被原始调用替换(如注释所示),则立即检索下一个可用请求.这没有任何区别.

有什么想法会导致这个问题吗?这可能是Android中以前未被发现的错误吗?对返回的错误的搜索不会带来太多影响; 主要参考NDK编程(我不做).

干杯.

d_m*_*mcg 6

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

  • 调用_currentOutputRequest.queue(_outputBuffer, _maxPacketSize);是传递整个缓冲区容量_maxPacketSize,其常量值为64(字节).显然,这仅适用于批量读取,最多可读取64个字节; 批量发送请求需要指定发送的确切字节数.
  • _currentOutputRequest.queue()_connection.requestWait()方法调用似乎不是线程安全的,特别是在执行UsbDeviceConnection(其是所述类型的_connection).我怀疑UsbRequest _currentOutputRequest在排队发送请求时在内部使用UsbConnection对象.

我是如何解决的:

呼叫queue()更改为:

_currentOutputRequest.queue(_outputBuffer, _outputBuffer.position());
Run Code Online (Sandbox Code Playgroud)

对于第二个问题,queue()语句已经在synchronized块内发生,使用该_outputLock对象.在Run()读者线程的方法中,我不得不使用以下方法将调用包装requestWait()在一个synchronized块中outputLock:

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

鉴于这发生在while循环中并requestWait阻塞线程,我发现的一个主要问题是在尝试排队发送请求时锁会导致饥饿.结果是,当应用程序及时接收并作用于传入的MIDI数据时,输出的MIDI事件被显着延迟.

作为对此的部分修复,我yieldwhile循环结束之前插入了一个语句,以保持UI线程不被阻塞.(UI线程暂时被用作音符事件由按钮按下触发;这最终将使用单独的播放线程.)因此它更好,但不完美,因为在第一个输出音符之前仍有相当长的延迟已发送.

更好的解决方案:

为了解决异步读取和写入的相互锁定的需求,异步queue()requestWait()方法仅用于读取操作,这些操作保留在单独的"读取器"线程上.因此,该synchronized块是不必要的,因此该段可以简化为以下内容:

UsbRequest request = _connection.requestWait();
Run Code Online (Sandbox Code Playgroud)

至于写/发送操作,其核心被移动到一个单独的线程来执行同步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
                }
            }

        }           
    }
}
Run Code Online (Sandbox Code Playgroud)

起初我担心异步代码与上面的内容冲突(因为它们共享相同的内容UsbDeviceConnection),但这似乎不是问题,因为它们使用完全不同的UsbEndpoint实例.

好消息是应用程序运行更顺畅,并且在我弹奏键盘的同时发送笔记时不会崩溃.

所以一般情况下(对于不同端点上的双向USB通信),似乎异步方法最适合读/输入操作,我们不需要担心定义轮询行为(这是否是内部发生的事情) ),同步bulkTransfer()方法更好地服务于输出/发送操作.