web-dev-qa-db-fra.com

IOException: échec de la lecture, socket peut-être fermé - Bluetooth sous Android 4.3

Actuellement, j'essaie de gérer une exception étrange lors de l'ouverture d'une prise Bluetooth sur mon Nexus 7 (2012), avec Android 4.3 (Build JWR66Y, je suppose la deuxième mise à jour 4.3). J'ai vu certaines publications liées (par exemple, https://stackoverflow.com/questions/13648373/bluetoothsocket-connect-throwing-exception-read-failed ), mais aucune solution ne semble permettre de contourner ce problème. . De plus, comme suggéré dans ces discussions, le réappariement n'aide pas et essayer constamment de se connecter (via une boucle stupide) n'a également aucun effet.

Je traite avec un appareil intégré (un adaptateur voiture noname OBD-II, similaire à http://images04.olx.com/ui/15/53/76/1316534072_254254776_2-OBD-II-BLUTOOTH-ADAPTERSCLEAR- VÉRIFICATION-LUMIÈRE-MOTEUR-AVEC-VOTRE TÉLÉPHONE-Oceanside.jpg ). Mon téléphone Android 2.3.7 n'a pas de problème de connexion et le Xperia d'un collègue (Android 4.1.2) fonctionne également. Un autre Google Nexus (je ne sais pas si "One" ou "S", mais pas "4") échoue également avec Android 4.3.

Voici l'extrait de l'établissement de connexion. Il s'exécute dans son propre thread, créé au sein d'un service.

private class ConnectThread extends Thread {

    private static final UUID EMBEDDED_BOARD_SPP = UUID
        .fromString("00001101-0000-1000-8000-00805F9B34FB");

    private BluetoothAdapter adapter;
    private boolean secure;
    private BluetoothDevice device;
    private List<UUID> uuidCandidates;
    private int candidate;
    protected boolean started;

    public ConnectThread(BluetoothDevice device, boolean secure) {
        logger.info("initiliasing connection to device "+device.getName() +" / "+ device.getAddress());
        adapter = BluetoothAdapter.getDefaultAdapter();
        this.secure = secure;
        this.device = device;

        setName("BluetoothConnectThread");

        if (!startQueryingForUUIDs()) {
            this.uuidCandidates = Collections.singletonList(EMBEDDED_BOARD_SPP);
            this.start();
        } else{
            logger.info("Using UUID discovery mechanism.");
        }
        /*
         * it will start upon the broadcast receive otherwise
         */
    }

    private boolean startQueryingForUUIDs() {
        Class<?> cl = BluetoothDevice.class;

        Class<?>[] par = {};
        Method fetchUuidsWithSdpMethod;
        try {
            fetchUuidsWithSdpMethod = cl.getMethod("fetchUuidsWithSdp", par);
        } catch (NoSuchMethodException e) {
            logger.warn(e.getMessage());
            return false;
        }

        Object[] args = {};
        try {
            BroadcastReceiver receiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    BluetoothDevice deviceExtra = intent.getParcelableExtra("Android.bluetooth.device.extra.DEVICE");
                    Parcelable[] uuidExtra = intent.getParcelableArrayExtra("Android.bluetooth.device.extra.UUID");

                    uuidCandidates = new ArrayList<UUID>();
                    for (Parcelable uuid : uuidExtra) {
                        uuidCandidates.add(UUID.fromString(uuid.toString()));
                    }

                    synchronized (ConnectThread.this) {
                        if (!ConnectThread.this.started) {
                            ConnectThread.this.start();
                            ConnectThread.this.started = true;
                            unregisterReceiver(this);
                        }

                    }
                }

            };
            registerReceiver(receiver, new IntentFilter("Android.bleutooth.device.action.UUID"));
            registerReceiver(receiver, new IntentFilter("Android.bluetooth.device.action.UUID"));

            fetchUuidsWithSdpMethod.invoke(device, args);
        } catch (IllegalArgumentException e) {
            logger.warn(e.getMessage());
            return false;
        } catch (IllegalAccessException e) {
            logger.warn(e.getMessage());
            return false;
        } catch (InvocationTargetException e) {
            logger.warn(e.getMessage());
            return false;
        }           

        return true;
    }

    public void run() {
        boolean success = false;
        while (selectSocket()) {

            if (bluetoothSocket == null) {
                logger.warn("Socket is null! Cancelling!");
                deviceDisconnected();
                openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);
            }

            // Always cancel discovery because it will slow down a connection
            adapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                bluetoothSocket.connect();
                success = true;
                break;

            } catch (IOException e) {
                // Close the socket
                try {
                    shutdownSocket();
                } catch (IOException e2) {
                    logger.warn(e2.getMessage(), e2);
                }
            }
        }

        if (success) {
            deviceConnected();
        } else {
            deviceDisconnected();
            openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);
        }
    }

    private boolean selectSocket() {
        if (candidate >= uuidCandidates.size()) {
            return false;
        }

        BluetoothSocket tmp;
        UUID uuid = uuidCandidates.get(candidate++);
        logger.info("Attempting to connect to SDP "+ uuid);
        try {
            if (secure) {
                tmp = device.createRfcommSocketToServiceRecord(
                        uuid);
            } else {
                tmp = device.createInsecureRfcommSocketToServiceRecord(
                        uuid);
            }
            bluetoothSocket = tmp;
            return true;
        } catch (IOException e) {
            logger.warn(e.getMessage() ,e);
        }

        return false;
    }

}

Le code échoue à bluetoothSocket.connect(). Je reçois un Java.io.IOException: read failed, socket might closed, read ret: -1. C'est la source correspondante sur GitHub: https://github.com/Android/platform_frameworks_base/blob/Android-4.3_r2/core/Java/Android/bluetooth/BluetoothSocket.Java#L504 Son appelé par readInt (), appelé depuis https://github.com/Android/platform_frameworks_base/blob/Android-4.3_r2/core/Java/Android/bluetooth/BluetoothSocket.Java#L319

Certains vidages de métadonnées du socket utilisé ont abouti aux informations suivantes. Ce sont exactement les mêmes sur Nexus 7 et mon téléphone 2.3.7.

Bluetooth Device 'OBDII'
Address: 11:22:33:DD:EE:FF
Bond state: 12 (bonded)
Type: 1
Class major version: 7936
Class minor version: 7936
Class Contents: 0
Contents: 0

J'ai quelques autres adaptateurs OBD-II (plus expansifs) et ils fonctionnent tous. Y at-il une chance, quelque chose me manque ou est-ce que cela pourrait être un bug dans Android?

92
matthes

J'ai enfin trouvé une solution de contournement. La magie est cachée sous le capot de la classe BluetoothDevice (voir https://github.com/Android/platform_frameworks_base/blob/Android-4.3_r2/core/Java/Android/bluetooth/BluetoothDevice. Java # L1037 ).

Maintenant, lorsque je reçois cette exception, j'instancie un repli BluetoothSocket, similaire au code source ci-dessous. Comme vous pouvez le constater, l'invocation de la méthode masquée createRfcommSocket via des réflexions. Je ne sais pas pourquoi cette méthode est cachée. Le code source le définit comme public bien que ...

Class<?> clazz = tmp.getRemoteDevice().getClass();
Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};

Method m = clazz.getMethod("createRfcommSocket", paramTypes);
Object[] params = new Object[] {Integer.valueOf(1)};

fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);
fallbackSocket.connect();

connect() n'échoue donc plus. J'ai encore rencontré quelques problèmes. En gros, cela bloque parfois et échoue. Le redémarrage du périphérique SPP (plug off/plug in) aide dans de tels cas. Parfois, je reçois également une autre demande de couplage après connect() même lorsque le périphérique est déjà lié.

MISE À JOUR:

voici une classe complète, contenant quelques classes imbriquées. pour une implémentation réelle, elles pourraient être organisées en tant que classes séparées.

import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.lang.reflect.Method;
import Java.util.List;
import Java.util.UUID;

import Android.bluetooth.BluetoothAdapter;
import Android.bluetooth.BluetoothDevice;
import Android.bluetooth.BluetoothSocket;
import Android.util.Log;

public class BluetoothConnector {

    private BluetoothSocketWrapper bluetoothSocket;
    private BluetoothDevice device;
    private boolean secure;
    private BluetoothAdapter adapter;
    private List<UUID> uuidCandidates;
    private int candidate;


    /**
     * @param device the device
     * @param secure if connection should be done via a secure socket
     * @param adapter the Android BT adapter
     * @param uuidCandidates a list of UUIDs. if null or empty, the Serial PP id is used
     */
    public BluetoothConnector(BluetoothDevice device, boolean secure, BluetoothAdapter adapter,
            List<UUID> uuidCandidates) {
        this.device = device;
        this.secure = secure;
        this.adapter = adapter;
        this.uuidCandidates = uuidCandidates;

        if (this.uuidCandidates == null || this.uuidCandidates.isEmpty()) {
            this.uuidCandidates = new ArrayList<UUID>();
            this.uuidCandidates.add(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
        }
    }

    public BluetoothSocketWrapper connect() throws IOException {
        boolean success = false;
        while (selectSocket()) {
            adapter.cancelDiscovery();

            try {
                bluetoothSocket.connect();
                success = true;
                break;
            } catch (IOException e) {
                //try the fallback
                try {
                    bluetoothSocket = new FallbackBluetoothSocket(bluetoothSocket.getUnderlyingSocket());
                    Thread.sleep(500);                  
                    bluetoothSocket.connect();
                    success = true;
                    break;  
                } catch (FallbackException e1) {
                    Log.w("BT", "Could not initialize FallbackBluetoothSocket classes.", e);
                } catch (InterruptedException e1) {
                    Log.w("BT", e1.getMessage(), e1);
                } catch (IOException e1) {
                    Log.w("BT", "Fallback failed. Cancelling.", e1);
                }
            }
        }

        if (!success) {
            throw new IOException("Could not connect to device: "+ device.getAddress());
        }

        return bluetoothSocket;
    }

    private boolean selectSocket() throws IOException {
        if (candidate >= uuidCandidates.size()) {
            return false;
        }

        BluetoothSocket tmp;
        UUID uuid = uuidCandidates.get(candidate++);

        Log.i("BT", "Attempting to connect to Protocol: "+ uuid);
        if (secure) {
            tmp = device.createRfcommSocketToServiceRecord(uuid);
        } else {
            tmp = device.createInsecureRfcommSocketToServiceRecord(uuid);
        }
        bluetoothSocket = new NativeBluetoothSocket(tmp);

        return true;
    }

    public static interface BluetoothSocketWrapper {

        InputStream getInputStream() throws IOException;

        OutputStream getOutputStream() throws IOException;

        String getRemoteDeviceName();

        void connect() throws IOException;

        String getRemoteDeviceAddress();

        void close() throws IOException;

        BluetoothSocket getUnderlyingSocket();

    }


    public static class NativeBluetoothSocket implements BluetoothSocketWrapper {

        private BluetoothSocket socket;

        public NativeBluetoothSocket(BluetoothSocket tmp) {
            this.socket = tmp;
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return socket.getInputStream();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            return socket.getOutputStream();
        }

        @Override
        public String getRemoteDeviceName() {
            return socket.getRemoteDevice().getName();
        }

        @Override
        public void connect() throws IOException {
            socket.connect();
        }

        @Override
        public String getRemoteDeviceAddress() {
            return socket.getRemoteDevice().getAddress();
        }

        @Override
        public void close() throws IOException {
            socket.close();
        }

        @Override
        public BluetoothSocket getUnderlyingSocket() {
            return socket;
        }

    }

    public class FallbackBluetoothSocket extends NativeBluetoothSocket {

        private BluetoothSocket fallbackSocket;

        public FallbackBluetoothSocket(BluetoothSocket tmp) throws FallbackException {
            super(tmp);
            try
            {
              Class<?> clazz = tmp.getRemoteDevice().getClass();
              Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};
              Method m = clazz.getMethod("createRfcommSocket", paramTypes);
              Object[] params = new Object[] {Integer.valueOf(1)};
              fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);
            }
            catch (Exception e)
            {
                throw new FallbackException(e);
            }
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return fallbackSocket.getInputStream();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            return fallbackSocket.getOutputStream();
        }


        @Override
        public void connect() throws IOException {
            fallbackSocket.connect();
        }


        @Override
        public void close() throws IOException {
            fallbackSocket.close();
        }

    }

    public static class FallbackException extends Exception {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public FallbackException(Exception e) {
            super(e);
        }

    }
}
123
matthes

eh bien, j'ai eu le même problème avec mon code, et c'est parce que depuis Android 4.2 pile Bluetooth a changé. donc mon code fonctionnait correctement sur les appareils avec Android <4.2, sur les autres appareils, je recevais la fameuse exception "échec de la lecture, fermeture de la socket ou dépassement du délai d'attente, read ret: -1"

Le problème concerne le paramètre socket.mPort. Lorsque vous créez votre socket en utilisant socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID);, le mPort obtient la valeur entière "- 1", et cette valeur semble ne pas fonctionner pour Android> = 4.2, vous devez donc le définir sur "1". La mauvaise nouvelle est que createRfcommSocketToServiceRecord accepte uniquement UUID en tant que paramètre et non pas mPort. Nous devons donc utiliser une autre approche. La réponse publiée par @ matthes a également fonctionné pour moi, mais je l’ai simplifiée: socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1);. Nous devons utiliser les deux attributs de socket, le second comme solution de secours.

Le code est donc (pour se connecter à un SPP sur un périphérique Elm327):

BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();

    if (btAdapter.isEnabled()) {
        SharedPreferences prefs_btdev = getSharedPreferences("btdev", 0);
        String btdevaddr=prefs_btdev.getString("btdevaddr","?");

        if (btdevaddr != "?")
        {
            BluetoothDevice device = btAdapter.getRemoteDevice(btdevaddr);

            UUID SERIAL_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); // bluetooth serial port service
            //UUID SERIAL_UUID = device.getUuids()[0].getUuid(); //if you don't know the UUID of the bluetooth device service, you can get it like this from Android cache

            BluetoothSocket socket = null;

            try {
                socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID);
            } catch (Exception e) {Log.e("","Error creating socket");}

            try {
                socket.connect();
                Log.e("","Connected");
            } catch (IOException e) {
                Log.e("",e.getMessage());
                try {
                    Log.e("","trying fallback...");

                    socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1);
                    socket.connect();

                    Log.e("","Connected");
                }
             catch (Exception e2) {
                 Log.e("", "Couldn't establish Bluetooth connection!");
              }
            }
        }
        else
        {
            Log.e("","BT device not selected");
        }
    }
89
George Dima

Tout d’abord, si vous devez parler à un périphérique Bluetooth 2.x, cette documentation indique ce qui suit:

Astuce: Si vous vous connectez à une carte série Bluetooth, essayez d’utiliser le bien connu SPP UUID 00001101-0000-1000-8000-00805F9B34FB . Toutefois, si vous vous connectez à un pair Android, veuillez générer votre propre UUID unique.

Je ne pensais pas que cela fonctionnerait, mais seulement en remplaçant l'UUID par 00001101-0000-1000-8000-00805F9B34FB cela fonctionne. Cependant, ce code semble résoudre le problème de la version du SDK et vous pouvez simplement remplacer la fonction device.createRfcommSocketToServiceRecord(mMyUuid); par tmp = createBluetoothSocket(mmDevice); après avoir défini la méthode suivante:

private BluetoothSocket createBluetoothSocket(BluetoothDevice device)
    throws IOException {
    if(Build.VERSION.SDK_INT >= 10){
        try {
            final Method m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[] { UUID.class });
            return (BluetoothSocket) m.invoke(device, mMyUuid);
        } catch (Exception e) {
            Log.e(TAG, "Could not create Insecure RFComm Connection",e);
        }
    }
    return  device.createRfcommSocketToServiceRecord(mMyUuid);
}

Le code source n'est pas le mien, mais provient de ce site .

11
tobiasBora

Eh bien, j'ai effectivement trouvé le problème.

La plupart des personnes qui tentent de se connecter à l'aide de socket.Connect(); obtiennent une exception appelée Java.IO.IOException: read failed, socket might closed, read ret: -1.

Dans certains cas, cela dépend également de votre périphérique Bluetooth, car il existe deux types de Bluetooth, à savoir BLE (faible consommation d'énergie) et Classique.

Si vous voulez vérifier le type de votre périphérique Bluetooth, voici le code:

        String checkType;
        var listDevices = BluetoothAdapter.BondedDevices;
        if (listDevices.Count > 0)
        {
            foreach (var btDevice in listDevices)
            {
                if(btDevice.Name == "MOCUTE-032_B52-CA7E")
                {
                    checkType = btDevice.Type.ToString();
                    Console.WriteLine(checkType);
                }
            }
        }

Cela fait des jours que j'essaye de résoudre le problème, mais depuis aujourd'hui, j'ai trouvé le problème. La solution de @matthes a malheureusement toujours quelques problèmes, comme il l'a déjà dit, mais voici ma solution.

Pour le moment, je travaille sous Xamarin Android, mais cela devrait également fonctionner pour d'autres plates-formes.

SOLUTION

S'il y a plus d'un appareil couplé, vous devez supprimer les autres appareils couplés. Ne gardez donc que celui que vous souhaitez connecter (voir la bonne image).

enter image description hereenter image description here

Dans l'image de gauche, vous voyez que j'ai deux appareils associés, à savoir "MOCUTE-032_B52-CA7E" et "Blue Easy". C'est le problème, mais je ne sais pas pourquoi ce problème se pose. Peut-être que le protocole Bluetooth essaie d'obtenir des informations d'un autre périphérique Bluetooth.

Cependant, la socket.Connect(); fonctionne très bien en ce moment, sans aucun problème. Je voulais donc simplement partager ceci, car cette erreur est vraiment gênante.

Bonne chance!

7
Jamie

J'ai eu les mêmes symptômes que décrits ici. Je pouvais me connecter une fois à une imprimante Bluetooth, mais les connexions suivantes ont échoué avec "socket fermé", peu importe ce que j'ai fait.

J'ai trouvé un peu étrange que les solutions de contournement décrites ici soient nécessaires. Après avoir parcouru mon code, j'ai constaté que j'avais oublié de fermer InputStream et OutputSteram du socket et que les ConnectedThreads n'étaient pas correctement terminés.

Le ConnectedThread que j'utilise est le même que dans l'exemple ici:

http://developer.Android.com/guide/topics/connectivity/bluetooth.html

Notez que ConnectThread et ConnectedThread sont deux classes différentes.

Quelle que soit la classe qui démarre, ConnectedThread doit appeler interrupt () et cancel () sur le thread. J'ai ajouté mmInStream.close () et mmOutStream.close () dans la méthode ConnectedTread.cancel ().

Après avoir fermé correctement les threads/flux/sockets, je pouvais créer de nouveaux sockets sans aucun problème.

7
Daniel T

Vous mettez registerReceiver(receiver, new IntentFilter("Android.bleutooth.device.action.UUID")); avec "bluetooth" orthographié "bluetooth".

6
Fizz Binn

Sur les versions plus récentes d'Android, je recevais cette erreur car l'adaptateur découvrait toujours lorsque j'ai tenté de me connecter à la prise. Même si j'ai appelé la méthode cancelDiscovery sur l'adaptateur Bluetooth, j'ai dû attendre que le rappel de la méthode onReceive () de BroadcastReceiver soit appelé avec l'action BluetoothAdapter.ACTION_DISCOVERY_FINISHED.

Une fois que j'ai attendu que l'adaptateur arrête la découverte, l'appel de connexion sur le socket a réussi.

5
kmac.mcfarlane

Au cas où quelqu'un aurait des problèmes avec Kotlin, je devais suivre la réponse acceptée avec quelques variantes:

fun print(view: View, text: String) {
    var adapter = BluetoothAdapter.getDefaultAdapter();
    var pairedDevices = adapter.getBondedDevices()
    var uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
    if (pairedDevices.size > 0) {
        for (device in pairedDevices) {
            var s = device.name
            if (device.getName().equals(printerName, ignoreCase = true)) {
                Thread {
                    var socket = device.createInsecureRfcommSocketToServiceRecord(uuid)
                    var clazz = socket.remoteDevice.javaClass
                    var paramTypes = arrayOf<Class<*>>(Integer.TYPE)
                    var m = clazz.getMethod("createRfcommSocket", *paramTypes)
                    var fallbackSocket = m.invoke(socket.remoteDevice, Integer.valueOf(1)) as BluetoothSocket
                    try {
                        fallbackSocket.connect()
                        var stream = fallbackSocket.outputStream
                        stream.write(text.toByteArray(Charset.forName("UTF-8")))
                    } catch (e: Exception) {
                        e.printStackTrace()
                        Snackbar.make(view, "An error occurred", Snackbar.LENGTH_SHORT).show()
                    }
                }.start()
            }
        }
    }
}

J'espère que ça aide

Les périphériques Bluetooth peuvent fonctionner en mode classique et en mode LE simultanément. Parfois, ils utilisent une adresse MAC différente selon la manière dont vous vous connectez. L'appel socket.connect() utilise Bluetooth Classic. Vous devez donc vous assurer que le périphérique que vous avez obtenu lors de la numérisation était vraiment un périphérique classique.

Il est facile de filtrer uniquement les périphériques classiques, cependant:

if(BluetoothDevice.DEVICE_TYPE_LE == device.getType()){ //socket.connect() }

Sans cette vérification, il est difficile de déterminer si un balayage hybride vous donnera d'abord le périphérique Classic ou le périphérique BLE. Cela peut apparaître comme une incapacité intermittente de se connecter, ou que certains appareils peuvent se connecter de manière fiable alors que d’autres ne le peuvent apparemment jamais.

2
kmac.mcfarlane

j'ai également fait face à ce problème, vous pouvez le résoudre de deux manières, comme mentionné précédemment, utilisez la réflexion pour créer le socket. La seconde est que le client recherche un serveur avec un UUID donné et si votre serveur ne tourne pas parallèlement au client, alors ceci arrive. Créez un serveur avec un UUID client donné, puis écoutez et acceptez le client du côté serveur. Cela fonctionnera.

1
ireshika piyumalie

J'ai rencontré ce problème et je l'ai corrigé en fermant les flux d'entrée et de sortie avant de fermer le socket. Maintenant, je peux me déconnecter et me reconnecter sans aucun problème.

https://stackoverflow.com/a/3039807/5688612

À Kotlin:

fun disconnect() {
    bluetoothSocket.inputStream.close()
    bluetoothSocket.outputStream.close()
    bluetoothSocket.close()
}
1
TheoKanning

Si une autre partie de votre code a déjà établi une connexion avec le même socket et le même UUID, vous obtenez cette erreur.

0
S List

Même si j’avais le même problème, enfin, comprenez mon problème, j’essayais de me connecter à partir de la portée de la couverture Bluetooth (hors de portée).

0
krishnamurthy