114 votes

IOException : read failed, socket might closed - Bluetooth sur Android 4.3

Actuellement, j'essaie de faire face à une exception étrange lors de l'ouverture d'un BluetoothSocket sur ma Nexus 7 (2012), avec Android 4.3 (Build JWR66Y, je suppose la deuxième mise à jour 4.3). J'ai vu quelques messages connexes (par ex. https://stackoverflow.com/questions/13648373/bluetoothsocket-connect-throwing-exception-read-failed ), mais aucun ne semble fournir une solution de contournement pour ce problème. De plus, comme suggéré dans ces fils de discussion, le réappairage n'aide pas, et le fait d'essayer constamment de se connecter (par une boucle stupide) n'a également aucun effet.

J'ai affaire à un dispositif intégré (un adaptateur de voiture OBD-II non nominatif, similaire à l'adaptateur de voiture OBD-II). http://images04.olx.com/ui/15/53/76/1316534072_254254776_2-OBD-II-BLUTOOTH-ADAPTERSCLEAR-CHECK-ENGINE-LIGHTS-WITH-YOUR-PHONE-Oceanside.jpg ). Mon téléphone Android 2.3.7 n'a aucun 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 c'est un 'One' ou un 'S', mais pas un '4') ne fonctionne pas non plus avec Android 4.3.

Voici un extrait de l'établissement de la 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 . Voici la source correspondante sur GitHub : https://github.com/Android/platform_frameworks_base/blob/Android-4.3_r2/core/java/Android/bluetooth/BluetoothSocket.java#L504 Il est appelé par readInt(), appelé à partir de https://github.com/Android/platform_frameworks_base/blob/Android-4.3_r2/core/java/Android/bluetooth/BluetoothSocket.java#L319

Un vidage des métadonnées de la socket utilisée a donné les informations suivantes. Ce sont exactement les mêmes sur la 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

Je possède d'autres adaptateurs OBD-II (plus étendus) et ils fonctionnent tous. Y a-t-il une chance que je rate quelque chose ou s'agit-il d'un bug dans Android ?

140voto

matthes Points 535

J'ai finalement trouvé une solution de contournement. La magie se cache sous le capot de l'application 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 une solution de repli. BluetoothSocket semblable au code source ci-dessous. Comme vous pouvez le voir, l'invocation de la méthode cachée createRfcommSocket via des réflexions. Je n'ai aucune idée de la raison pour laquelle cette méthode est cachée. Le code source la définit comme public cependant...

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() puis n'échoue plus. J'ai encore rencontré quelques problèmes. En gros, cela bloque et échoue parfois. Le redémarrage de l'appareil SPP (débrancher / brancher) aide dans ces cas-là. Parfois, j'obtiens également une autre demande d'appairage après l'échec de l'opération. connect() même si l'appareil est déjà collé.

UPDATE :

voici une classe complète, contenant quelques classes imbriquées. pour une implémentation réelle, celles-ci pourraient être tenues comme des 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);
        }

    }
}

112voto

George Dima Points 71

J'ai eu le même problème avec mon code, et c'est parce que depuis Android 4.2 la pile bluetooth a changé. donc mon code fonctionnait bien sur les appareils avec Android < 4.2, sur les autres appareils j'obtenais la fameuse exception "lecture échouée, socket peut-être fermé ou timeout, read ret : -1"

Le problème se situe au niveau de la socket.mPort paramètre. Lorsque vous créez votre socket en utilisant socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID); le mPort obtient une valeur entière " -1 ", et cette valeur ne semble pas fonctionner pour Android >=4.2 , vous devez donc la définir sur " . 1 ". La mauvaise nouvelle est que createRfcommSocketToServiceRecord n'accepte que l'UUID comme paramètre et non mPort Nous devons donc utiliser une autre approche. La réponse publiée par @matthes ça a aussi marché pour moi, mais je l'ai simplifié : 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 le suivant (pour se connecter à un SPP sur un dispositif 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");
        }
    }

19voto

tobiasBora Points 309

D'abord, si vous avez besoin de parler à un appareil Bluetooth 2.x, cette documentation déclare que :

Conseil : Si vous vous connectez à une carte série Bluetooth, essayez d'utiliser la fonction le site UUID SPP bien connu 00001101-0000-1000-8000-00805F9B34FB . Cependant si vous vous connectez à un peer 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 ça marche. Cependant, ce code semble gérer le problème de la version SDK, et vous pouvez simplement remplacer la fonction device.createRfcommSocketToServiceRecord(mMyUuid); con 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 de moi, mais provient de ce site web .

9voto

Daniel T Points 81

J'ai eu les mêmes symptômes que ceux décrits ici. Je pouvais me connecter une fois à une imprimante Bluetooth, mais les connexions suivantes échouaient avec "socket closed", quoi que je fasse.

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 découvert que j'avais oublié de fermer l'InputStream et l'OutputSteram de la socket et que je n'avais pas terminé les ConnectedThreads correctement.

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.

La classe qui lance le 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/streams/sockets, j'ai pu créer de nouveaux sockets sans aucun problème.

9voto

kmac.mcfarlane Points 97

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X