Java UsbRequest.queue崩溃Android 3.1应用程序
我正在开发一个Android 3.1应用程序,它使用USB主机模式通过USB上的MIDI与我的键盘(Korg M3)通信。这是在安装了Android 4.0.3的Xoom上运行的。我能够通过USB毫无问题地接收MIDI消息,但将音符数据发送回键盘的成功与否参差不齐,在半秒延迟后频繁崩溃 以下是我在点击操作栏上的按钮发送便笺时不断遇到的错误: E/dalvikvm(6422):JNI错误(应用程序错误):访问了过时的全局引用0x1da0020a(大小为130的表中的索引130) 我检查/试图找出原因的内容:Java UsbRequest.queue崩溃Android 3.1应用程序,java,android,crash,Java,Android,Crash,我正在开发一个Android 3.1应用程序,它使用USB主机模式通过USB上的MIDI与我的键盘(Korg M3)通信。这是在安装了Android 4.0.3的Xoom上运行的。我能够通过USB毫无问题地接收MIDI消息,但将音符数据发送回键盘的成功与否参差不齐,在半秒延迟后频繁崩溃 以下是我在点击操作栏上的按钮发送便笺时不断遇到的错误: E/dalvikvm(6422):JNI错误(应用程序错误):访问了过时的全局引用0x1da0020a(大小为130的表中的索引130) 我检查/试图找出原
- 由于代码是多线程的,所以我有Java
块,用于访问输出请求池、输入请求池(根据Android文档中的),以及当前输出请求的自定义锁对象&关联的synchronized
对象引用。我已经构建了执行这些锁的代码,以最小化死锁发生的可能性ByteBuffer
- 从相关请求池检索可用的
对象时,我将clientData引用设置为新的UsbRequest
对象,而不是重用以前关联的ByteBuffer
对象并对其调用ByteBuffer
clear()
- 我在代码中的关键点添加了大量的日志调用(针对logCat),以尝试跟踪失败的确切位置。我发现错误最终发生在以下几点(在此之前,此代码可以正常工作几次):
public void sendMidiData() { 已同步(_outputLock) { if(_currentOutputRequest==null | | | u outputBuffer.position()<2) 返回; Log.d(_标记,“排队发送请求”); ////错误-发生在下一条语句中: _currentOutputRequest.queue(_outputBuffer,_maxPacketSize); d(_标记,“发送请求排队;重置引用…”); //初始化下一个数据包 _currentOutputRequest=null;//GetAvailablesAndRequest(); _outputBuffer=null;//(ByteBuffer)_currentOutputRequest.getClientData(); d(_标记,“输出请求和缓冲区引用集”); } }
- 我还尝试将
和\u currentOutputRequest
引用设置为\u outputBuffer
,以便仅在将下一个MIDI事件写入缓冲区时检索可用请求。如果null
被原始调用替换(如注释所示),则立即检索下一个可用请求。这没有什么区别null
干杯。好吧,我有了一点突破,发现了两个问题:
- 调用
正在传递整个缓冲区容量\u currentOutputRequest.queue(\u outputBuffer,\u maxPacketSize)
,该容量的常量值为64(字节)。显然,这只适用于批量读取,其中最多可读取64个字节;批量发送请求需要指定发送的确切字节数\u maxPacketSize
和\u currentOutputRequest.queue()
方法调用似乎不是线程安全的,特别是在\u connection.requestWait()
的实现中(这是UsbDeviceConnection
\u连接的类型)。我怀疑UsbRequest
在排队发送请求时在内部使用了UsbConnection对象\u currentOutputRequest
queue()
的调用更改为:
_currentOutputRequest.queue(_outputBuffer, _outputBuffer.position());
对于第二个问题,queue()
语句已经使用\u outputLock
对象出现在synchronized
块中。在读卡器线程的Run()
方法中,我必须使用outputLock
将对requestWait()
的调用包装在同步的块中:
UsbRequest request = null;
synchronized(_outputLock)
{
// requestWait() and request.queue() appear not to be thread-safe.
request = _connection.requestWait();
}
假设这发生在循环中,而循环和requestWait
阻塞线程,我发现的一个主要问题是,在尝试对发送请求排队时,锁会导致饥饿。结果是,当应用程序及时接收并处理传入的MIDI数据时,输出的MIDI事件会显著延迟
作为对此的部分修复,我在while
循环结束之前插入了yield
语句,以保持UI线程的畅通。(UI线程暂时被用作通过按下按钮触发的便笺事件;这最终将使用单独的播放线程。)因此,它更好,但并不完美,因为在发送第一个输出便笺之前仍有相当长的延迟
更好的解决方案:
为了解决异步读写对互锁的需求,异步queue()
和requestWait()
方法仅用于读操作,它们保留在单独的“reader”线程上。因此,不需要synchronized
块,因此可以将该段缩减为以下内容:
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
}
}
}
}
}
私有类扩展线程
{
私有布尔值_raiseStop=false;
私有对象_sendLock=新对象();
私有LinkedList_outputQueue=新LinkedList();
公共无效队列(ByteBuffer缓冲区)
{
已同步(_sendLock)
{
_添加(缓冲区);
//线程很可能会暂停(至sav
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
}
}
}
}
}
if (usb_request_queue(request)) {
request->buffer = NULL;
return false;
} else {
// save a reference to ourselves so UsbDeviceConnection.waitRequest() can find us
// we also need this to make sure our native buffer is not deallocated
// while IO is active
request->client_data = (void *)env->NewGlobalRef(thiz);
return true;
}