489次成功连接后android蓝牙连接失败

Posted

技术标签:

【中文标题】489次成功连接后android蓝牙连接失败【英文标题】:android bluetooth connection fails after 489 successful connections 【发布时间】:2014-03-28 03:16:53 【问题描述】:

不幸的是,我在使用 android 的蓝牙时遇到了一些问题。对于我的测试环境,我使用带有 Android 4.4.2 的 Nexus 4。

我的 PC 上有一个 Java 应用程序,它使用 bluecove 作为客户端建立 SPP 连接。该程序正在寻找一个特殊的服务名称并与我的安卓手机连接。之后它会向我的安卓手机发送 72 个字节并等待回复。当得到那个答案时,程序会休眠 3 秒,然后重新启动。

在我的安卓手机上,我有一个带有后台蓝牙监听器的应用程序,它在启动时启动。此应用程序基于 BluetoothChat 示例演示。接收蓝牙数据时,我会检查传入数据并发送回答案。

一切正常。但是在 489 个蓝牙连接之后,android 应用程序失败,并在 PC-java-app 运行时出现以下错误 sn-p:

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。

【问题讨论】:

我想知道您的旧套接字是否没有关闭,因此您最终超出了某些系统或进程限制并且无法创建新的。 这与***.com/questions/21166222/…有关吗? 知道失败的原因吗? 想知道 4.4.3 是否解决了这个问题。有大量与 BT 相关的修复 您的设备似乎超过了允许的最大 fd 数。在这里查看***.com/questions/13262339/…。你确定你的应用关闭了所有资源吗? 【参考方案1】:

如果您在应用程序中使用蓝牙作为服务器,我遇到了与此相同的问题,除了我的问题是当我进行反射时我的文件描述符 mPfd 为空。我能够使用这种方法关闭文件,希望这可以帮助。

我使用 ParceFileDescriptor 从 BluetoothSocket 中的 LoaclSocket 的 id/index 获取文件。 诠释 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());
        
    

【讨论】:

【参考方案2】:

这是 Android 4.2 - 4.4.4(和 4.4w & 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】:

问题似乎与您设备上文件描述符的限制有关。有针对该问题的报告here

在蓝牙套接字创建期间一个新的 fd 是two new FDs 从系统中获取。似乎您没有正确关闭之前的 BT 连接,因此使用的 FD 数量稳步增加,直到您达到限制。

为避免这种情况,您至少必须在完成操作后从listenUsingRfcommWithServiceRecord() 调用收到的BluetoothServerSocket 上调用close()。您还应该检查您是否保留了连接到 BT 连接的其他资源,并尽可能释放它们。


正如这里所要求的,如何强制关闭 BluetoothServerSocket 的 ParcelFileDescriptor。 小心:它可能会破坏一切!

您必须访问 BluetoothServerSocketmSocket 字段才能访问底层 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,您可以省略获取 mSocket 的部分,如 jitain sharmas answer 所示。

【讨论】:

谢谢。同时我意识到我们使用的是 3rd 方库,所以我会向他们报告一个错误。 我们可以重置 ParcelFileDescriptor 吗? @jitainsharma 这应该由系统在关闭和处理连接/流期间处理......如果您获得对 FD 的引用,您可以使用它的 close() 方法,但这会创建如果它仍在其他地方使用,则会出现错误。 我已经解决了文件描述符超出限制,是通过使用反射的方法关闭文件描述符。看来Android蓝牙库里面有一些韭菜。 这可以解决问题。需要注意的是,只有在尝试 BluetoothSocket.close() 之前这样做才能解决泄漏问题。【参考方案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.");
        
    

上面是我编写的关闭文件描述符的方法,下面是我使用此代码时的方法: 无论何时,套接字都必须关闭:

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。 希望它能帮助您和其他面临同样问题的人。

【讨论】:

解决问题!应该注意的是,在尝试 socket.close() 之前,您需要始终执行 mPfd 操作(即 clearFileDescriptor() )。如果首先调用 socket.close(),PFD 泄漏将继续存在。您可能还想在 mPfd 内容之前进行 LocalSocket (mSocket) 清理。我看到一些奇怪的事情发生了,直到我在 BluetoothSocket obj 上添加并同步()它。基本复制Android 5.0代码:grepcode.com/file/repository.grepcode.com/java/ext/…

以上是关于489次成功连接后android蓝牙连接失败的主要内容,如果未能解决你的问题,请参考以下文章

Android BLE为啥首次连接蓝牙设备比较慢

熊晨沣蓝牙实战--小程序蓝牙连接2.0

android adb 支持蓝牙连接吗

android的蓝牙匹配连接

小程序 蓝牙连接

多次成功连接后,Android GATT 服务返回 Null