IOException: error de lectura, el socket podría estar cerrado-Bluetooth en Android 4.3
serial-port android-4.3-jelly-bean (12)
Actualmente estoy tratando de lidiar con una excepción extraña al abrir un BluetoothSocket en mi Nexus 7 (2012), con Android 4.3 (Build JWR66Y, supongo que la segunda actualización 4.3). He visto algunas publicaciones relacionadas (por ejemplo, https://stackoverflow.com/questions/13648373/bluetoothsocket-connect-throwing-exception-read-failed ), pero ninguna parece proporcionar una solución para este problema. Además, como se sugiere en estos hilos, el emparejamiento no ayuda, y constantemente tratando de conectarse (a través de un bucle estúpido) tampoco tiene ningún efecto.
Me refiero a un dispositivo incorporado (un adaptador de coche OBD-II no idéntico, similar a http://images04.olx.com/ui/15/53/76/1316534072_254254776_2-OBD-II-BLUTOOTH-ADAPTERSCLEAR-CHECK-ENGINE-LIGHTS-WITH-YOUR-PHONE-Oceanside.jpg ). Mi teléfono Android 2.3.7 no tiene problemas de conexión, y el Xperia de un colega (Android 4.1.2) también funciona. Otro Google Nexus (no sé si ''Uno'' o ''S'', pero no ''4'') también falla con Android 4.3.
Aquí está el Fragmento del establecimiento de conexión. Se ejecuta en su propio subproceso, creado dentro de un servicio.
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;
}
}
El código está fallando en bluetoothSocket.connect()
. Recibo una java.io.IOException: read failed, socket might closed, read ret: -1
. Esta es la fuente correspondiente en GitHub: https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L504 Se llama a través de readInt (), llamado desde https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L319
Algunos volcados de metadatos del socket usado dieron como resultado la siguiente información. Estos son exactamente los mismos en Nexus 7 y mi teléfono 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
Tengo otros adaptadores OBD-II (más expansivos) y todos funcionan. ¿Hay alguna posibilidad, que me falta algo o podría ser un error en Android?
Bueno, en realidad encontré el problema.
La mayoría de las personas que intentan establecer una conexión mediante socket.Connect();
obtener una excepción llamada Java.IO.IOException: read failed, socket might closed, read ret: -1
.
En algunos casos, también depende de su dispositivo Bluetooth, ya que hay dos tipos diferentes de Bluetooth, a saber, BLE (baja energía) y Classic.
Si desea verificar el tipo de su dispositivo Bluetooth, aquí está el código:
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);
}
}
}
He intentado durante días resolver el problema, pero desde hoy he encontrado el problema. La solución de @matthes lamentablemente todavía tiene algunos problemas, como ya dijo, pero esta es mi solución.
Por el momento trabajo en Xamarin Android, pero esto también debería funcionar para otras plataformas.
SOLUCIÓN
Si hay más de un dispositivo vinculado, entonces debe eliminar los otros dispositivos emparejados. Así que guarde solo el que desea conectar (vea la imagen de la derecha).
En la imagen de la izquierda, verá que tengo dos dispositivos emparejados, a saber, "MOCUTE-032_B52-CA7E" y "Azul fácil". Ese es el problema, pero no tengo idea de por qué ocurre ese problema. Tal vez el protocolo Bluetooth esté tratando de obtener información de otro dispositivo Bluetooth.
Sin embargo, el socket.Connect();
funciona bien en este momento, sin ningún problema. Así que solo quería compartir esto, porque ese error es realmente molesto.
¡Buena suerte!
Bueno, tuve el mismo problema con mi código, y es porque desde Android 4.2 la pila de bluetooth ha cambiado. entonces mi código funcionaba bien en los dispositivos con Android <4.2, en los otros dispositivos recibía la famosa excepción "error de lectura, el socket podría estar cerrado o el tiempo de espera, read ret: -1"
El problema es con el parámetro socket.mPort
. Cuando crea su socket usando socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID);
, mPort
obtiene el valor entero " -1 ", y este valor parece no funcionar para Android> = 4.2, por lo que debe configurarlo en " 1 ". La mala noticia es que createRfcommSocketToServiceRecord
solo acepta el UUID como parámetro y no mPort
por lo que debemos utilizar otro enfoque. La respuesta publicada por @matthes también funcionó para mí, pero la simplifiqué: socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1);
. Necesitamos usar los dos attribs de socket, el segundo como una alternativa.
Entonces el código es (para conectarse a un SPP en un dispositivo 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");
}
}
En caso de que alguien tenga problemas con Kotlin, tuve que seguir la respuesta aceptada con algunas variaciones:
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()
}
}
}
}
Espero eso ayude
En las versiones más nuevas de Android, recibí este error porque el adaptador todavía estaba descubriendo cuando intenté conectarme al socket. Aunque llamé al método cancelDiscovery en el adaptador Bluetooth, tuve que esperar hasta que se llamara a la devolución de llamada al método onReceive () de BroadcastReceiver con la acción BluetoothAdapter.ACTION_DISCOVERY_FINISHED.
Una vez que esperé a que el adaptador dejara de detectarse, la llamada de conexión en el socket se realizó correctamente.
Finalmente encontré una solución. La magia está oculta bajo el capó de la clase BluetoothDevice
(ver https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothDevice.java#L1037 ).
Ahora, cuando recibo esa excepción, instancia un BluetoothSocket
alternativo, similar al código fuente a continuación. Como puede ver, invoca el método oculto createRfcommSocket
través de reflexiones. No tengo idea de por qué este método está oculto. El código fuente lo define como public
...
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()
luego no falla más. Todavía tengo algunos problemas. Básicamente, esto a veces se bloquea y falla. Reiniciar el dispositivo SPP (conectar / desconectar) ayuda en tales casos. A veces también recibo otra solicitud de vinculación después de connect()
incluso cuando el dispositivo ya está enlazado.
ACTUALIZAR:
aquí hay una clase completa, que contiene algunas clases anidadas. para una implementación real, estos podrían llevarse a cabo como clases separadas.
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);
}
}
}
Incluso tuve el mismo problema, finalmente entendí mi problema, estaba tratando de conectarme desde (fuera de rango) rango de cobertura Bluetooth.
Los dispositivos Bluetooth pueden operar tanto en modo clásico como LE al mismo tiempo. Algunas veces usan una dirección MAC diferente dependiendo de la forma en que se conecte. Llamar socket.connect()
está utilizando Bluetooth Classic, por lo que debe asegurarse de que el dispositivo que recibió cuando escaneó era realmente un dispositivo clásico.
Sin embargo, es fácil filtrar solo para dispositivos clásicos:
if(BluetoothDevice.DEVICE_TYPE_LE == device.getType()){ //socket.connect() }
Sin este control, es una condición de carrera si un escaneo híbrido le dará primero el dispositivo Classic o el dispositivo BLE. Puede aparecer como una incapacidad intermitente para conectarse, o como ciertos dispositivos pueden conectarse confiablemente mientras que otros aparentemente nunca pueden conectarse.
Primero, si necesita hablar con un dispositivo bluetooth 2.x, esta documentación establece que:
Sugerencia: si se está conectando a una placa de serie de Bluetooth, intente utilizar el conocido SPP UUID 00001101-0000-1000-8000-00805F9B34FB . Sin embargo, si se está conectando con un par de Android, genere su UUID único.
No pensé que funcionaría, pero solo reemplazando el UUID con 00001101-0000-1000-8000-00805F9B34FB
funciona. Sin embargo, este código parece manejar el problema de la versión del SDK, y usted puede simplemente reemplazar la función device.createRfcommSocketToServiceRecord(mMyUuid);
con tmp = createBluetoothSocket(mmDevice);
después de definir el siguiente método:
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);
}
El código fuente no es mío, pero proviene de este sitio web .
También me enfrenté a este problema, puedes resolverlo de 2 maneras, como mencioné antes usar reflexión para crear el socket. El segundo es que el cliente está buscando un servidor con un UUID dado y si tu servidor no se ejecuta en paralelo al cliente, entonces este sucede. Cree un servidor con el UUID del cliente dado y luego escuche y acepte al cliente desde el lado del servidor. Funcionará.
También recibí la misma IOException
, pero encuentro la demostración del sistema Android: el proyecto "BluetoothChat" está funcionando. Determiné que el problema es el UUID.
Así que reemplazo mi UUID.fromString("00001001-0000-1000-8000-00805F9B34FB")
a UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
y funcionó la mayoría de las escenas, solo a veces es necesario reiniciar el Bluetooth dispositivo;
Tuve los mismos síntomas que se describen aquí. Pude conectarme una vez a una impresora bluetooth pero los conectores posteriores fallaron con "socket cerrado" sin importar lo que hice.
Me pareció un poco extraño que las soluciones alternativas descritas aquí fueran necesarias. Después de revisar mi código, descubrí que había olvidado cerrar el InputStream y el OutputSteram del socket y no terminé ConnectedThreads correctamente.
El ConnectedThread que uso es el mismo que en el ejemplo aquí:
http://developer.android.com/guide/topics/connectivity/bluetooth.html
Tenga en cuenta que ConnectThread y ConnectedThread son dos clases diferentes.
Cualquier clase que inicie ConnectedThread debe llamar a interrupt () y cancel () en el hilo. Agregué mmInStream.close () y mmOutStream.close () en el método ConnectedTread.cancel ().
Después de cerrar correctamente los hilos / corrientes / tomas de corriente, pude crear nuevas tomas sin ningún problema.
Usted pone registerReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID"));
con "bluetooth" deletreado "bleutooth".