IOException: сбой чтения, сокет может быть закрыт - Bluetooth на Android 4.3
В настоящее время я пытаюсь разобраться с странным Исключением при открытии BluetoothSocket на моем Nexus 7 (2012), с Android 4.3 (Build JWR66Y, я думаю, второе обновление 4.3). Я видел некоторые связанные записи (например, qaru.site/info/91696/...), но ни один из них не создает обходной путь для этой проблемы. Кроме того, как предложено в этих потоках, повторное спаривание не помогает, и постоянная попытка соединения (через глупый цикл) также не имеет эффекта.
Я имею дело со встроенным устройством (автомобильный адаптер noname 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 (я не знаю, если "One" или "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?
Ответы
Ответ 1
Наконец-то я нашел обходное решение. Магия скрыта под капотом класса 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-устройства (plug off/plug in). Иногда я также получаю еще один запрос на спаривание после connect()
, даже когда устройство уже связано.
UPDATE:
вот полный класс, содержащий некоторые вложенные классы. для реальной реализации они могут быть проведены как отдельные классы.
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);
}
}
}
Ответ 2
Хорошо, у меня была такая же проблема с моим кодом, и это потому, что, поскольку пакет bluetooth Android Android изменился. поэтому мой код работал нормально на устройствах с android < 4.2, на других устройствах я получал известное исключение ", не удалось выполнить сбой, сокет мог закрыться или таймаут, прочитать ret: -1"
Проблема заключается в параметре socket.mPort
. Когда вы создаете свой сокет с помощью socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID);
, mPort
получает целочисленное значение -1 ", и это значение кажется не работает для android >= 4.2, поэтому вам нужно установить его" 1". Плохая новость заключается в том, что createRfcommSocketToServiceRecord
принимает только UUID как параметр, а не mPort
, поэтому нам нужно использовать другой aproach. Ответ, отправленный @matthes, также работал у меня, но я упростил его: socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1);
. Нам нужно использовать оба атрибута socket, второй - как резерв.
Итак, код (для подключения к 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");
}
}
Ответ 3
Во-первых, если вам нужно поговорить с устройством 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);
}
Исходный код не мой, но он исходит из этого веб-сайта.
Ответ 4
У меня были те же симптомы, как описано здесь. Я мог подключиться к 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().
После правильного закрытия потоков/потоков/сокетов я мог без проблем создавать новые сокеты.
Ответ 5
Ну, я действительно нашел проблему.
Большинство людей, которые пытаются установить соединение с помощью 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();
отлично работает прямо сейчас, без проблем. Я просто хотел поделиться этим, потому что эта ошибка действительно раздражает.
Удачи!
Ответ 6
Вы ставите
registerReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID"));
с "bluetooth" написано "bleutooth".
Ответ 7
В новых версиях Android я получал эту ошибку, потому что адаптер все еще обнаруживал, когда я пытался подключиться к соке. Несмотря на то что я назвал метод cancelDiscovery на адаптере Bluetooth, мне пришлось подождать, пока не будет вызван обратный вызов метода BroadcastReceiver onReceive() с действием BluetoothAdapter.ACTION_DISCOVERY_FINISHED.
Как только я дождался, когда адаптер остановит обнаружение, вызов сокета на сокете преуспел.
Ответ 8
В случае, если у кого-то возникли проблемы с 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()
}
}
}
}
Надеюсь, поможет
Ответ 9
Устройства Bluetooth могут работать как в классическом, так и в режиме LE в одно и то же время. Иногда они используют другой MAC-адрес в зависимости от того, каким образом вы подключаетесь. Вызов socket.connect()
использует Bluetooth Classic, поэтому вы должны убедиться, что устройство, которое вы получили, когда вы сканировали, было действительно классическим устройством.
Легко фильтровать только для классических устройств:
if(BluetoothDevice.DEVICE_TYPE_LE == device.getType()){
//socket.connect()
}
Без этой проверки это условие гонки относительно того, даст ли гибридное сканирование классическое устройство или устройство BLE. Он может появляться как прерывистая неспособность подключаться или как определенные устройства могут надежно подключаться, в то время как другие, казалось бы, никогда не могут.
Ответ 10
Я также столкнулся с этой проблемой, вы можете решить ее двумя способами, как упоминалось ранее, использовать отражение для создания сокета
Второй,
клиент ищет сервер с заданным UUID, и если ваш сервер не работает параллельно клиенту, это происходит.
Создайте сервер с данным клиентским UUID, а затем слушайте и принимайте клиента со стороны сервера. Он будет работать.
Ответ 11
Я столкнулся с этой проблемой и исправил ее, закрыв входной и выходной потоки перед закрытием сокета. Теперь я могу отключиться и подключиться снова без проблем.
fooobar.com/questions/141849/...
В Котлине:
fun disconnect() {
bluetoothSocket.inputStream.close()
bluetoothSocket.outputStream.close()
bluetoothSocket.close()
}
Ответ 12
Даже у меня была та же проблема, наконец, поняв свою проблему, я пытался подключиться из (вне диапазона) зоны действия Bluetooth.
Ответ 13
Если другая часть вашего кода уже установила соединение с тем же сокетом и UUID, вы получите эту ошибку.
Ответ 14
Добавив действие фильтра, моя проблема решена
// 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);
Ответ 15
Я также получаю тот же IOException
, но я нахожу демонстрацию системы Android: работает проект "BluetoothChat". Я решил, что проблема заключается в UUID.
Итак, я заменил my UUID.fromString("00001001-0000-1000-8000-00805F9B34FB")
на UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
, и он работал в большинстве сцен, иногда иногда нужно перезапустить устройство Bluetooth;