不幸的是,我的android蓝牙有一些问题。对于我的测试环境,我使用的是Nexus 4和Android 4.4.2。
我的PC上有一个Java应用程序,它使用bluecove作为客户端进行SPP连接。该程序正在寻找一个特殊的服务名称,并与我的Android手机连接。然后它发送72个字节到我的Android手机,并等待答案。得到答案后,程序会睡3秒钟而不是重新开始。
在我的Android手机上,我有一个带有后台蓝牙监听器的应用程序,它在启动时启动。此应用程序基于BluetoothChat示例演示。接收蓝牙数据时,我会检查传入的数据并发回答案。
一切正常。但是在489蓝牙连接之后,当app-java-app正在进行时,android应用程序失败并显示以下错误代码:
getBluetoothService() called with no BluetoothManagerCallback
Shutting down VM
threadid=1: thread exiting with uncaught exception (group=0x41b34ba8)
FATAL EXCEPTION: main
Process: de.tum.lme.diamantum:remote_blue, PID: 21567
java.lang.NullPointerException: FileDescriptor must not be null
at android.os.ParcelFileDescriptor.<init>(ParcelFileDescriptor.java:174)
at android.os.ParcelFileDescriptor$1.createFromParcel(ParcelFileDescriptor.java:905)
at android.os.ParcelFileDescriptor$1.createFromParcel(ParcelFileDescriptor.java:897)
at android.bluetooth.IBluetooth$Stub$Proxy.createSocketChannel(IBluetooth.java:1355)
at android.bluetooth.BluetoothSocket.bindListen(BluetoothSocket.java:349)
at android.bluetooth.BluetoothAdapter.createNewRfcommSocketAndRecord(BluetoothAdapter.java:1055)
at android.bluetooth.BluetoothAdapter.listenUsingRfcommWithServiceRecord(BluetoothAdapter.java:976)
at com.test.btconn.BluetoothHandling$AcceptThread.<init>(BluetoothHandling.java:449)
at com.test.btconn.BluetoothHandling.start(BluetoothHandling.java:216)
at com.test.btconn.BluetoothListenerService.setupBtSockets(BluetoothListenerService.java:330)
at com.test.btconn.BluetoothListenerService.manageBtState(BluetoothListenerService.java:249)
at com.test.btconn.BluetoothListenerService.setBtStateDisconnected(BluetoothListenerService.java:383)
at com.test.btconn.BluetoothListenerService.access$5(BluetoothListenerService.java:378)
at com.test.btconn.BluetoothListenerService$2.handleMessage(BluetoothListenerService.java:421)
因此,应用程序ParcelFileDescriptor存在问题,该问题突然变为空。但为什么呢?
当改变PC-java-app上的暂停时间时,使用各种数据大小来传输和使用不同的智能手机时,也会发生上述所有情况。当使用反射“listenUsingRfcommWithServiceRecord”时,505传输后也会发生相同的情况。使用唤醒锁也没有任何改变。
顺便说一下,使用BluetoothChat样本时我也有同样的行为。
所以,有人提示,会发生什么?
更新
BluetoothServerSocket在每次连接后关闭,如果蓝牙状态为3,则关闭BluetoothSocket。
答案 0 :(得分:14)
问题似乎与设备上文件描述符的限制有关。有一个针对该问题的报告here
在创建蓝牙套接字期间,从系统获取新的fd two new FDs。您似乎没有正确关闭以前的BT连接,因此在您达到限制之前,已使用的FD数量会稳步增加。
为避免这种情况,您至少必须在完成操作后从close()
电话上的 BluetoothServerSocket 上拨打listenUsingRfcommWithServiceRecord()
。您还应该检查是否要保留连接到BT连接的其他资源,并在可能的情况下释放它们。
这里要求的是如何强制关闭BluetoothServerSocket的ParcelFileDescriptor。 注意:它可能会破坏事情!
您必须访问 BluetoothServerSocket 的 mSocket 字段才能访问基础 BluetoothSocket 。此 BluetoothSocket 在字段mPfd中保存ParcelFileDescriptor。在那你可以打电话给close()
。由于两个字段都不可见,您必须使用 Reflections :
public void closePFD(BluetoothServerSocket closeMe) throws AllKindOfExceptionsThatYouHaveToHandle
{
Field mSocketFld = closeMe.getClass().getDeclaredField("mSocket");
mSocketFld.setAccessible(true);
BluetoothSocket btsock = (BluetoothSocket)mSocketFld.get(closeMe);
Field mPfdFld = btsock.getClass().getDeclaredField("mPfd");
mPfdFld.setAccessible(true);
ParcelFileDescriptor pfd = (ParcelFileDescriptor)mPfdFld.get(btsock);
pfd.close();
}
这将关闭 BluetoothServerSocket 。如果你想从BTServerSockets接受方法中只关闭 BluetoothSocket ,你就可以省去jitain sharmas answer中 mSocket 的部分。
答案 1 :(得分:4)
我对我提出的问题的解决方法:
private synchronized void clearFileDescriptor(){
try{
Field field = BluetoothSocket.class.getDeclaredField("mPfd");
field.setAccessible(true);
ParcelFileDescriptor mPfd = (ParcelFileDescriptor)field.get(socket);
if(null == mPfd){
return;
}
mPfd.close();
}catch(Exception e){
Log.w(SensorTicker.TAG, "ParcelFileDescriptor could not be cleanly closed.");
}
}
以上是我编写的关闭filedescriptor的方法,以及下面我使用此代码的方法: 每当插座必须关闭时:
private synchronized void cleanClose() {
if (socket != null) {
try {
clearFileDescriptor();
//clearLocalSocket();
socket.close();
}
catch (IOException e) {
Log.w(SensorTicker.TAG, "Socket could not be cleanly closed.");
}
}
}
我也尝试过我编写的clearLocalSocket()方法,但没有用于我的问题。所以我试图关闭FileDescriptor。 希望它能帮助你和其他人面对同样的问题。
答案 2 :(得分:1)
这是Android 4.2 - 4.4.4(和4.4w&amp; L预览版)中的BluetoothSocket.close()错误......在内部,它调用mPfd.detachFd()然后实际上没有释放底层文件描述符。修复是调用mPfd.close()并设置mPfd = null,这正是Android 5.0现在所做的。
您可以使用此处发布的其他解决方案来解决此问题,这些解决方案调用mPfd.close()然后调用您的平台的BluetoothSocket.close(),但我发现这对我来说还不够可能会发生一些奇怪的事情。我更进一步,先清理mSocket并设置为null,然后调用mPfd.close()并设置为null,最后调用设置mSocketState所需的BluetoothSocket.close()。
public static void cleanClose(BluetoothSocket btSocket)
{
if(btSocket == null)
return;
if(Build.VERSION.SDK_INT >= 17 && Build.VERSION.SDK_INT <= 20)
{
try { cleanCloseFix(btSocket); }
catch (Exception e)
{
Log.d(sLogName, "Exception during BluetoothSocket close bug fix: " + e.toString());
}
//Go on to call BluetoothSocket.close() too, because our code didn't do quite everything
}
//Call BluetoothSocket.close()
try { btSocket.close(); }
catch (Exception e)
{
Log.d(sLogName, "Exception during BluetoothSocket close: " + e.toString());
}
}
private static void cleanCloseFix(BluetoothSocket btSocket) throws IOException
{
synchronized(btSocket)
{
Field socketField = null;
LocalSocket mSocket = null;
try
{
socketField = btSocket.getClass().getDeclaredField("mSocket");
socketField.setAccessible(true);
mSocket = (LocalSocket)socketField.get(btSocket);
}
catch(Exception e)
{
Log.d(sLogName, "Exception getting mSocket in cleanCloseFix(): " + e.toString());
}
if(mSocket != null)
{
mSocket.shutdownInput();
mSocket.shutdownOutput();
mSocket.close();
mSocket = null;
try { socketField.set(btSocket, mSocket); }
catch(Exception e)
{
Log.d(sLogName, "Exception setting mSocket = null in cleanCloseFix(): " + e.toString());
}
}
Field pfdField = null;
ParcelFileDescriptor mPfd = null;
try
{
pfdField = btSocket.getClass().getDeclaredField("mPfd");
pfdField.setAccessible(true);
mPfd = (ParcelFileDescriptor)pfdField.get(btSocket);
}
catch(Exception e)
{
Log.d(sLogName, "Exception getting mPfd in cleanCloseFix(): " + e.toString());
}
if(mPfd != null)
{
mPfd.close();
mPfd = null;
try { pfdField.set(btSocket, mPfd); }
catch(Exception e)
{
Log.d(sLogName, "Exception setting mPfd = null in cleanCloseFix(): " + e.toString());
}
}
} //synchronized
}
答案 3 :(得分:0)
如果您在应用程序中使用蓝牙作为服务器,我遇到了与此相同的问题,除了我的问题是当我进行反射时我在文件描述符mPfd上变为null。我能够使用这种方法关闭文件,希望这可以帮助。
我使用ParceFileDescriptor从BluetoothSocket中的LoaclSocket通过id / index获取文件。 int mfd = 0; 字段socketField = null; LocalSocket mSocket = null;
try
{
socketField = btSocket.getClass().getDeclaredField("mSocket");
socketField.setAccessible(true);
mSocket = (LocalSocket)socketField.get(btSocket);
}
catch(Exception e)
{
Log ( "Exception getting mSocket in cleanCloseFix(): " + e.toString());
}
if(mSocket != null)
{
FileDescriptor fileDescriptor =
mSocket.getFileDescriptor();
String in = fileDescriptor.toString();
//regular expression to get filedescriptor index id
Pattern p = Pattern.compile("\\[(.*?)\\]");
Matcher m = p.matcher(in);
while(m.find()) {
Log ( "File Descriptor " + m.group(1));
mfd = Integer.parseInt(m.group(1));
break;
}
//Shutdown the socket properly
mSocket.shutdownInput();
mSocket.shutdownOutput();
mSocket.close();
mSocket = null;
try { socketField.set(btSocket, mSocket); }
catch(Exception e)
{
Log ("Exception setting mSocket = null in cleanCloseFix(): " + e.toString());
}
//Close the file descriptor when we have it from the Local Socket
try {
ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.adoptFd(mfd);
if (parcelFileDescriptor != null) {
parcelFileDescriptor.close();
Log ( "File descriptor close succeed : FD = " + mfd);
}
} catch (Exception ex) {
Log ( "File descriptor close exception " + ex.getMessage());
}
}