目前我正试图在我的Nexus 7(2012)上使用Android 4.3(构建JWR66Y,我猜第二次4.3更新)打开BluetoothSocket时遇到一个奇怪的异常。我看过一些相关的帖子(例如https://stackoverflow.com/questions/13648373/bluetoothsocket-connect-throwing-exception-read-failed),但似乎都没有为此问题提供解决方法。另外,正如这些线程中所建议的那样,重新配对没有帮助,并且不断尝试连接(通过一个愚蠢的循环)也没有任何效果。
我正在处理嵌入式设备(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而失败。
这是连接建立的片段。它在自己的Thread中运行,在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;
}
}
代码在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中的错误?
答案 0 :(得分:122)
我终于找到了解决方法。魔术隐藏在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);
}
}
}
答案 1 :(得分:86)
问题在于socket.mPort
参数。当您使用socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID);
创建套接字时,mPort
会获得整数值“ -1 ”,并且此值似乎不适用于android&gt; = 4.2,所以你需要将其设置为“ 1 ”。坏消息是createRfcommSocketToServiceRecord
只接受UUID作为参数而不是mPort
所以我们必须使用其他方法。 @matthes 发布的答案对我有用,但我简化了它:socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1);
。我们需要使用两个套接字attribs,第二个作为后备。
所以代码是(用于连接到ELM327设备上的SPP):
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");
}
}
答案 2 :(得分:10)
首先,如果您需要与蓝牙2.x设备通信,this documentation说明:
提示:如果您要连接蓝牙串行板,请尝试使用 着名的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);
}
源代码不是我的,但来自this website。
答案 3 :(得分:6)
我有与此处描述的症状相同的症状。我可以连接一次到蓝牙打印机,但随后的连接失败了&#34; socket closed&#34;无论我做了什么。
我发现这里描述的解决方法是必要的,这有点奇怪。在完成我的代码后,我发现我忘记关闭套接字的InputStream和OutputSteram,并且没有正确终止ConnectedThreads。
我使用的ConnectedThread与此处的示例相同:
http://developer.android.com/guide/topics/connectivity/bluetooth.html
请注意,ConnectThread和ConnectedThread是两个不同的类。
启动ConnectedThread的类必须在线程上调用interrupt()和cancel()。 我在ConnectedTread.cancel()方法中添加了mmInStream.close()和mmOutStream.close()。
正确关闭线程/流/套接字后,我可以毫无问题地创建新的套接字。
答案 4 :(得分:6)
registerReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID"));
用&#34; bluetooth&#34;拼写&#34; bleutooth&#34;。
答案 5 :(得分:6)
好吧,我确实找到了问题。
尝试使用socket.Connect();
建立连接的大多数人都会收到名为Java.IO.IOException: read failed, socket might closed, read ret: -1
的异常。
在某些情况下,它还取决于您的蓝牙设备,因为有两种不同类型的蓝牙,即BLE(低能耗)和经典。
如果您想检查蓝牙设备的类型,请输入以下代码:
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的解决方案仍然存在一些问题,正如他已经说过的那样,但这是我的解决方案。
目前我在Xamarin Android工作,但这也适用于其他平台。
<强>解强>
如果有多个配对设备,则应删除其他配对设备。因此,请仅保留您要连接的那个(请参阅右图)。
在左图中,您看到我有两个配对设备,即“MOCUTE-032_B52-CA7E”和“Blue Easy”。这是问题,但我不知道为什么会出现这个问题。也许蓝牙协议试图从另一台蓝牙设备获取一些信息。
然而,socket.Connect();
现在很好用,没有任何问题。所以我只是想分享一下,因为这个错误真的很烦人。
答案 6 :(得分:4)
在较新版本的Android上,我收到此错误,因为当我尝试连接到套接字时,适配器仍在发现。即使我在蓝牙适配器上调用了cancelDiscovery方法,我也不得不等到使用BluetoothAdapter.ACTION_DISCOVERY_FINISHED操作调用BroadcastReceiver的onReceive()方法回调。
等待适配器停止发现后,套接字上的connect调用成功。
答案 7 :(得分:2)
如果有人与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()
}
}
}
}
希望有所帮助
答案 8 :(得分:1)
我也遇到过这个问题,你可以通过两种方式解决它,如前面提到的那样使用反射来创建套接字 第二个是, 客户端正在寻找具有给定UUID的服务器,如果您的服务器没有与客户端并行运行,则会发生这种情况。 使用给定的客户端UUID创建服务器,然后从服务器端侦听并接受客户端。它将工作。
答案 9 :(得分:1)
蓝牙设备可以同时在经典模式和LE模式下运行。有时他们使用不同的MAC地址,具体取决于您连接的方式。拨打socket.connect()
正在使用蓝牙经典版,因此您必须确保扫描时获得的设备确实是一款经典设备。
只有经典设备才能轻松过滤:
if(BluetoothDevice.DEVICE_TYPE_LE == device.getType()){
//socket.connect()
}
如果没有这项检查,混合扫描是否会首先为您提供Classic设备或BLE设备是一种竞争条件。它可能表现为间歇性无法连接,或者某些设备能够可靠地连接,而其他设备似乎永远无法连接。
答案 10 :(得分:0)
即使我遇到了同样的问题,也终于了解了我的问题,我正在尝试连接(超出范围)蓝牙覆盖范围。
答案 11 :(得分:0)
我遇到了这个问题,并通过在关闭套接字之前关闭输入和输出流来解决此问题。现在,我可以断开连接并再次连接,没有任何问题。
https://stackoverflow.com/a/3039807/5688612
在科特林:
fun disconnect() {
bluetoothSocket.inputStream.close()
bluetoothSocket.outputStream.close()
bluetoothSocket.close()
}
答案 12 :(得分:0)
如果代码的另一部分已经使用相同的套接字和UUID建立了连接,则会出现此错误。
答案 13 :(得分:0)
我遇到了这个问题,解决方案是使用特殊的魔术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
但是,我还没有尝试全部。
答案 14 :(得分:-1)
通过添加过滤器操作,我的问题得以解决
// 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 :(得分:-3)
我也收到相同的IOException
,但我找到了Android系统演示:&#34; BluetoothChat&#34;项目工作。我确定问题是UUID。
所以我将我的UUID.fromString("00001001-0000-1000-8000-00805F9B34FB")
替换为UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
,它在大多数情况下工作,有时只需要重启蓝牙设备;