IOException: не удалось прочитать, возможно, сокет закрыт - Bluetooth на Android 4.3
В настоящее время я пытаюсь справиться со странным исключением при открытии BluetoothSocket на моем Nexus 7 (2012) с Android 4.3 (сборка JWR66Y, я думаю, второе обновление 4.3). Я видел некоторые похожие сообщения (например, /questions/41018039/bluetoothsocketconnect-vyibrasyivaet-isklyuchenie-ne-udalos-prochitat), но ни одна из них не предлагает обходной путь для этой проблемы. Кроме того, как предлагается в этих потоках, повторное сопряжение не помогает, и постоянная попытка подключения (через тупой цикл) также не имеет никакого эффекта.
Я имею дело со встроенным устройством (автомобильный адаптер без названия 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). Мой телефон Android 2.3.7 не имеет проблем с подключением, и Xperia коллеги (Android 4.1.2) также работает. Другой Google Nexus (я не знаю, "Один" или "S", но не "4") также не работает с Android 4.3.
Вот фрагмент установки соединения. Он работает в своем собственном потоке, созданном внутри службы.
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;
}
}
Код не работает на bluetoothSocket.connect()
, Я получаю java.io.IOException: read failed, socket might closed, read ret: -1
, Это соответствующий источник на GitHub: https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L504 вызывается через readInt(), вызывается из https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L319
Некоторый дамп метаданных используемого сокета привел к следующей информации. Они точно такие же на Nexus 7 и моем телефоне 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
У меня есть некоторые другие адаптеры OBD-II (больше расширений), и все они работают. Есть ли шанс, что я что-то упустил или это может быть ошибка в Android?
12 ответов
Я наконец нашел обходной путь. Магия скрыта под капотом BluetoothDevice
класс (см. https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothDevice.java#L1037).
Теперь, когда я получаю это исключение, я создаю резервную копию BluetoothSocket
, аналогично исходному коду ниже. Как видите, вызывая скрытый метод createRfcommSocket
через размышления. Я понятия не имею, почему этот метод скрыт. Исходный код определяет его как 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()
тогда больше не подведет. Я испытал еще несколько проблем. По сути, это иногда блокирует и дает сбой. В таких случаях помогает перезагрузка SPP-устройства (отключение / подключение). Иногда я также получаю еще один запрос на сопряжение после connect()
даже когда устройство уже связано.
ОБНОВИТЬ:
Вот полный класс, содержащий несколько вложенных классов. для реальной реализации их можно рассматривать как отдельные классы.
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);
}
}
}
Ну, у меня была та же проблема с моим кодом, и это потому, что с тех пор как Android 4.2 стек Bluetooth изменился. так что мой код работал нормально на устройствах с Android < 4.2, на других устройствах я получал известное исключение "сбой чтения, сокет может закрыться или тайм-аут, чтение ret: -1"
Проблема с socket.mPort
параметр. Когда вы создаете свой сокет, используя socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID);
, mPort
получает целочисленное значение "-1", и это значение, похоже, не работает для Android >=4.2, поэтому вам нужно установить его на "1". Плохая новость в том, что createRfcommSocketToServiceRecord
принимает только UUID в качестве параметра, а не mPort
поэтому мы должны использовать другой подход. Ответ от @matthes также работал для меня, но я упростил его: socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1);
, Нам нужно использовать оба атрибута сокета, второй как запасной вариант.
Итак, код (для подключения к SPP на устройстве 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");
}
}
Во-первых, если вам нужно поговорить с устройством Bluetooth 2.x, эта документация гласит:
Подсказка: если вы подключаетесь к последовательной плате Bluetooth, попробуйте использовать хорошо известный SPP UUID 00001101-0000-1000-8000-00805F9B34FB. Однако, если вы подключаетесь к одноранговому устройству Android, сгенерируйте свой уникальный UUID.
Я не думал, что это сработает, но только заменив UUID на 00001101-0000-1000-8000-00805F9B34FB
оно работает. Однако этот код, похоже, решает проблему версии SDK, и вы можете просто заменить функцию device.createRfcommSocketToServiceRecord(mMyUuid);
с tmp = createBluetoothSocket(mmDevice);
после определения следующего метода:
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);
}
Исходный код не мой, но взят с этого сайта.
В новых версиях Android я получал эту ошибку, потому что адаптер все еще обнаруживал, когда я пытался подключиться к сокету. Несмотря на то, что я вызвал метод cancelDiscovery на адаптере Bluetooth, мне пришлось ждать, пока не будет вызван обратный вызов метода onReceive() BroadcastReceiver с действием BluetoothAdapter.ACTION_DISCOVERY_FINISHED.
Как только я дождался, пока адаптер прекратит обнаружение, вызов подключения к сокету завершился успешно.
У меня были те же симптомы, как описано здесь. Я мог подключиться к Bluetooth-принтеру один раз, но последующие подключения не были выполнены с "закрытой розеткой", что бы я ни делал.
Мне показалось немного странным, что описанные здесь обходные пути будут необходимы. Пройдя через мой код, я обнаружил, что забыл закрыть InputStream и OutputSteram сокета и неправильно завершил ConnectedThreads.
ConnectedThread, который я использую, такой же, как в примере здесь:
http://developer.android.com/guide/topics/connectivity/bluetooth.html
Обратите внимание, что ConnectThread и ConnectedThread - это два разных класса.
Любой класс, который запускает ConnectedThread, должен вызывать interrupt() и cancel() в потоке. Я добавил mmInStream.close() и mmOutStream.close() в методе ConnectedTread.cancel().
После правильного закрытия потоков / потоков / сокетов я мог без проблем создавать новые сокеты.
В случае, если у кого-то возникли проблемы с Kotlin, я должен был следовать принятому ответу с некоторыми вариациями:
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()
}
}
}
}
Надеюсь, поможет
Ну, я на самом деле нашел проблему.
Большинство людей, которые пытаются установить соединение, используя socket.Connect();
получить исключение под названием Java.IO.IOException: read failed, socket might closed, read ret: -1
,
В некоторых случаях это также зависит от вашего устройства Bluetooth, потому что есть два разных типа Bluetooth, а именно BLE (низкое энергопотребление) и Classic.
Если вы хотите проверить тип вашего устройства Bluetooth, вот код:
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);
}
}
}
Я несколько дней пытался решить проблему, но с сегодняшнего дня я нашел проблему. У решения @matthes, к сожалению, есть еще несколько проблем, как он уже сказал, но вот мое решение.
На данный момент я работаю в Android Xamarin, но это должно работать и на других платформах.
РЕШЕНИЕ
Если существует более одного сопряженного устройства, вам следует удалить другие сопряженные устройства. Поэтому оставьте только тот, который вы хотите подключить (см. Изображение справа).
На левом изображении вы видите, что у меня есть два сопряженных устройства, а именно "MOCUTE-032_B52-CA7E" и "Blue Easy". Это проблема, но я понятия не имею, почему эта проблема возникает. Возможно, протокол Bluetooth пытается получить некоторую информацию от другого устройства Bluetooth.
Тем не менее socket.Connect();
отлично работает прямо сейчас, без проблем. Так что я просто хотел поделиться этим, потому что эта ошибка действительно раздражает.
Удачи!
Вы положилиregisterReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID"));
с "bluetooth" пишется "bleutooth".
Устройства Bluetooth могут работать как в классическом, так и в режиме LE одновременно. Иногда они используют разные MAC-адреса в зависимости от того, каким образом вы подключаетесь. призвание socket.connect()
использует Bluetooth Classic, поэтому вы должны убедиться, что устройство, которое вы получили при сканировании, действительно было классическим устройством.
Однако легко фильтровать только классические устройства:
if(BluetoothDevice.DEVICE_TYPE_LE == device.getType()){
//socket.connect()
}
Без этой проверки, это состязание относительно того, даст ли гибридное сканирование сначала классическое устройство или устройство BLE. Это может выглядеть как прерывистая неспособность соединиться, или как определенные устройства, способные надежно соединяться, в то время как другие, по-видимому, никогда не могут.
Я также столкнулся с этой проблемой, вы могли бы решить ее двумя способами, как упоминалось ранее, использовать отражение для создания сокета. Во-вторых, клиент ищет сервер с заданным UUID, и если ваш сервер не работает параллельно клиенту, тогда это случается. Создайте сервер с указанным UUID клиента, а затем прослушайте и примите клиента со стороны сервера. Это будет работать.
Я столкнулся с этой проблемой и исправил ее, закрыв входной и выходной потоки перед закрытием сокета. Теперь я могу отключиться и подключиться снова без проблем.
/questions/24412097/otklyuchite-razem-bluetooth-v-android/24412119#24412119
В Котлине:
fun disconnect() {
bluetoothSocket.inputStream.close()
bluetoothSocket.outputStream.close()
bluetoothSocket.close()
}
Даже у меня была та же проблема, наконец, поняв свою проблему, я пытался подключиться из (вне диапазона) зоны действия Bluetooth.
Если другая часть вашего кода уже установила соединение с тем же сокетом и UUID, вы получите эту ошибку.
У меня была эта проблема, и я решил использовать специальный волшебный GUID.
val id: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") // Any other GUID doesn't work.
val device: BluetoothDevice = bta!!.bondedDevices.first { z -> z.name == deviceName }
bts = device.createRfcommSocketToServiceRecord(id) // mPort is -1
bts?.connect()
// Start processing thread.
Я подозреваю, что это те UUID, которые работают:
var did: Array<ParcelUuid?> = device.uuids
Однако я не пробовал их все.
Добавив действие фильтра, моя проблема решена
// Register for broadcasts when a device is discovered
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, intentFilter);
Я также получил то же самое IOException
, но я нахожу демо системы Android: проект "BluetoothChat" работает. Я определил, что проблема заключается в UUID.
Так что я заменяю свой UUID.fromString("00001001-0000-1000-8000-00805F9B34FB")
в UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
и это работало большинство сцен, только иногда нужно перезагрузить устройство Bluetooth;