9

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

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

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

一切正常。但是在 489 个蓝牙连接之后,当 PC-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 关闭。

4

4 回答 4

14

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

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

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


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

您必须访问 BluetoothServerSocket 的mSocket字段才能访问底层的BluetoothSocket。此BluetoothSocket在 mPfd 字段中保存 ParcelFileDescriptor。并且您可以调用. 由于这两个字段都不可见,您将不得不使用Reflectionsclose()

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中所示。

于 2014-06-24T10:34:59.783 回答
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。希望它能帮助你和其他面临同样问题的人。

于 2014-11-27T06:01:46.613 回答
1

这是 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
}
于 2015-01-10T06:55:52.953 回答
0

如果您在应用程序中使用蓝牙作为服务器,我遇到了与此相同的问题,除了我的问题是当我进行反射时我在文件描述符 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());
        }
    }
于 2016-01-14T20:27:17.720 回答