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)
我已经检查/尝试以找出原因的内容:
- 由于代码是多线程的,因此我在
synchronized
访问输出请求池,输入请求池(根据Android文档中的ADB示例)以及用于当前输出请求和相关ByteBuffer
对象引用的自定义锁定对象方面具有Java 块。我已经构造了执行这些锁定的代码,以最大程度地减少发生死锁的可能性。 - 当
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.");
}
}
- 我还尝试将
_currentOutputRequest
和_outputBuffer
引用设置为,null
以便仅在将下一个MIDI事件写入缓冲区时才检索可用请求。如果null
被原始调用替换(如注释掉所示),则立即检索下一个可用请求。这没有什么区别。
有什么想法会导致此问题吗?这可能是Android中以前未发现的错误吗?搜索返回的错误不会带来太多收益。主要参考了NDK编程(我没有这样做)。
干杯。
回答:
好的,我取得了一些突破,发现了两个问题:
- 的调用
_currentOutputRequest.queue(_outputBuffer, _maxPacketSize);
传递了整个缓冲区容量_maxPacketSize
,该容量的常量值为64(字节)。显然,这仅适用于批量读取,最多可以读取64个字节。批量发送请求需要指定要发送的确切字节数。 - 的
_currentOutputRequest.queue()
和_connection.requestWait()
方法调用似乎不是线程安全的,特别是在执行UsbDeviceConnection
(其是所述类型的_connection
)。我怀疑UsbRequest_currentOutputRequest
在排队发送请求时在内部使用UsbConnection对象。
的呼叫queue()
已更改为:
_currentOutputRequest.queue(_outputBuffer, _outputBuffer.position());
对于第二个问题,该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();
}
鉴于这种情况发生在while
循环中并requestWait
阻塞了线程,我发现的一个主要问题是,当试图将发送请求排队时,锁会导致饥饿。结果是,当应用程序及时接收传入的MIDI数据并对其进行操作时,输出的MIDI事件会大大延迟。
作为此问题的部分解决方案,我yield
在while
循环结束前插入了一条语句以使UI线程保持畅通。(UI线程被暂时用作注释事件,因为它是通过按下按钮触发的;它最终将使用单独的播放线程。)因此,它更好,但并不完美,因为在第一次输出注释之前还有相当长的延迟已发送。
为了解决对异步读取和写入的互锁的需求,异步queue()
和requestWait()
方法仅用于读取操作,这些操作保留在单独的“读取器”线程上。因此,该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
}
}
}
}
}
起初,我担心异步代码与上述代码冲突(因为它们共享相同的UsbDeviceConnection
),但这似乎不是问题,因为它们使用的是完全不同的UsbEndpoint
实例。
好消息是,在我弹奏键盘的同时发送笔记时,应用程序运行更流畅且不会崩溃。
因此,一般而言(对于在单独端点上的双向USB通信),异步方法似乎最适合于读/输入操作,在这种情况下,我们无需担心定义轮询行为(无论这是否在内部发生)
),而同步bulkTransfer()
方法可以更好地服务于输出/发送操作。
以上是 UsbRequest.queue导致Android 3.1应用程序崩溃 的全部内容, 来源链接: utcz.com/qa/403396.html