android bluetooth connection fails after 489 successful connections
Asked Answered
J

4

9

unfortunately, I have some problems with android's bluetooth. For my test environment I use a Nexus 4 with Android 4.4.2.

I have a Java Application on my PC, which uses bluecove in order to make a SPP connection as client. The programme is looking for a special service name and connects with my android phone. Afterwards it sends 72 bytes to my android phone and waits for an answer. When getting that answer the programme sleeps for 3 seconds and than starts again.

On my android phone I have an application with background bluetooth listener which starts at boot. This application is based on BluetoothChat sample demo. When receiving bluetooth data I check incoming data and send back an answer.

All that is working fine. But after 489 bluetooth connections the android app fails with following error snippet while PC-java-app is going on:

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)

So the app has a problem with the ParcelFileDescriptor, which is suddenly null. But why?

All the described above also happens when changing pause-time on PC-java-app, using various data sizes for transmitting and using different smartphones. When using reflection "listenUsingRfcommWithServiceRecord" the same happens after 505 transmissions. Also using wakelock changes nothing.

By the way, I got the same behaviour when using BluetoothChat sample.

So, has anybody a hint, what happens?

Update:

BluetoothServerSocket is closed after each connection and BluetoothSocket if bluetooth state is 3.

Janejanean answered 25/2, 2014 at 17:43 Comment(7)
I wonder if perhaps your old sockets are not getting closed, and as a result you eventually exceed some system or process limit and are unable to create new ones.Conventional
Is this related to #21166722 ?Alyciaalyda
Any idea why it fails?Greenberg
Would be interesting to know if 4.4.3 fixes this. There were a slew of fixes related to BTUreide
@ifor I already have 4.4.3. How can I find out when I installed it?Greenberg
The null seem to come out of native code see here and follow where fd comes from. Not really helpful but maybe a hintRois
Seems your device crossing maximum number of fd allowed. check here #13262839 . Are you sure your app closing all the resources ?Elissa
R
14

The problem seems connected to the limit of file descriptors on your device. There is a report for that issue here

During the creation of a Bluetooth socket a new fd is two new FDs are acquired from the system. It seems you are not closing your previous BT connections correctly so the number of used FDs steadily increases until you hit the limit.

To avoid this you will at least have to call close() on the BluetoothServerSocket you receive from the listenUsingRfcommWithServiceRecord() call after finishing the operations for it. You should also check if you are holding on to other resources connected to the BT connection and free them if possible.


As it was requested here is how to force the closing of the ParcelFileDescriptor of the BluetoothServerSocket. Beware: it may break things!

You will have to access the mSocket field of the BluetoothServerSocket to access the underlying BluetoothSocket. This BluetoothSocket holds the ParcelFileDescriptor in the field mPfd. And on that you can call close(). As both fields are not visible you will have to use 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();
}

This will close the BluetoothServerSocket. If you want to close just the BluetoothSocket from the BTServerSockets accept method you can leave out the part of getting mSocket as seen in jitain sharmas answer.

Rois answered 24/6, 2014 at 10:34 Comment(7)
Thanks. I realized meanwhile that we use 3rd party library so I will report a bug to them.Greenberg
Could we reset the ParcelFileDescriptor, by anyway?Womanhood
@jitainsharma This should be handeled by the system during closing and disposing connections/streams.... If you get hold of a reference to a FD you could use its close() method but this will create bugs if it still is used somewhere else.Rois
I have resolved the file descriptors goes beyond the limit, is by using the reflection method to close the file descriptors. It seems that Android Bluetooth Library has some leeks in it.Womanhood
@jitainsharma please teach me how to fix this problem. Very thank youUric
@wangzhengyi, i am giving the answer below, for how i have fixed it.Womanhood
This does the trick. It's important to note that this only solves the leak if you do it before attempting BluetoothSocket.close().Beggary
W
4

My fix for the question i have asked:

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.");
        }
    }

So above is the method i have written to close the filedescriptor, and the below when i have used this code: Whenever, socket has to be close:

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.");
            }
        }
    }

I have also tried with the clearLocalSocket() method i have written, but no used for my problem. So i tried to close the FileDescriptor. Hope it will help you and others facing the same issue.

Womanhood answered 27/11, 2014 at 6:1 Comment(1)
That solves the problem! It should be noted that you need to always do the mPfd stuff (ie clearFileDescriptor() ) before you try socket.close(). The PFD leak will remain if socket.close() is called first. You may also want to do the LocalSocket (mSocket) cleanup before that mPfd stuff. I was seeing some weird things happen until I added that and synchronize()'ed it on the BluetoothSocket obj. Basically replicating the Android 5.0 code: grepcode.com/file/repository.grepcode.com/java/ext/…Beggary
B
1

This is a BluetoothSocket.close() bug in Android 4.2 - 4.4.4 (and 4.4w & L Preview)... Internally, it's calling mPfd.detachFd() and then not actually releasing the underlying file descriptor. The fix is to instead call mPfd.close() and set mPfd=null, which is exactly how Android 5.0 now does it.

You can mostly fix this with the other solutions posted here that call mPfd.close() and then calling your platform's BluetoothSocket.close(), but I found that wasn't quite enough for me and some weird things could happen. I went a bit further and also clean up mSocket first and set that null, then call mPfd.close() and set that null, and finally call BluetoothSocket.close() which is needed to set mSocketState.

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
}
Beggary answered 10/1, 2015 at 6:55 Comment(0)
F
0

If you are using bluetooth as a server in you app, I came across with the same problem as this, except my issue is I am getting null on a file descriptor mPfd when I do reflection. I was able to close the file using this method, hope this can help.

I get the file through a id/index from LoaclSocket in BluetoothSocket using ParceFileDescriptor. int mfd = 0; Field 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());
        }
    }
Ferrel answered 14/1, 2016 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.