如何防止Android蓝牙RFCOMM连接在.connect()之后立即死亡?

Posted

技术标签:

【中文标题】如何防止Android蓝牙RFCOMM连接在.connect()之后立即死亡?【英文标题】:How to prevent Android bluetooth RFCOMM connection from dying immediately after .connect()? 【发布时间】:2011-02-09 07:09:05 【问题描述】:

这个问题已经解决了!非常感谢布拉德、丹尼斯和瘾君子!你们是英雄! :)

这是工作代码。它连接到 Zeemote 并从中读取数据。

===== 代码=====

公共类 ZeeTest 扩展 Activity @覆盖 公共无效 onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); 设置内容视图(R.layout.main); 尝试 for (int i = 0; i

===== 原始问题=====

我正在尝试从运行 2.0.1 固件的 Moto Droid 连接到 Zeemote (http://zeemote.com/) 游戏控制器。下面的测试应用程序确实连接到设备(LED 闪烁),但之后立即断开连接。

我在下面粘贴了两个测试应用程序:一个实际尝试从输入流中读取数据,另一个只是坐在那里等待设备在 5 秒后断开连接。是的,我确实有第三个版本:)它首先等待 ACL_CONNECTED,然后打开套接字,但它的行为没有什么新的。

一些背景信息: 我可以使用 bluez 工具(也附有日志)完美地从笔记本电脑连接到 Zeemote。我确信 Droid 也能够与 Zeemote 通话,因为 Market 中的“Game Pro”可以正常工作(但它是一个驱动程序/服务,所以它可能使用较低级别的 API? )。

我注意到'adb bugreport' 既不报告 Zeemote 的 UUID 也不报告 RFCOMM 通道,而它为所有其他设备(包括 Moto HS815 耳机,另一个“sdp browse”没有报告任何内容的哑设备)报告。另外,当设备启动时,Zeemote 的优先级为 0(其他的优先级为 100+)。

我在这里很茫然,我工作了很长时间,以至于我没有想法,所以非常感谢任何帮助(即使你不知道答案 :) )

谢谢, 最大

测试申请1号

此应用会尝试从设备中实际读取数据。

===== 代码=====

公共类 ZeeTest 扩展 Activity @覆盖 公共无效 onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); 设置内容视图(R.layout.main); 尝试 测试(); 捕捉(IOException e) e.printStackTrace(); 私有 BluetoothSocket 袜子; 私有输入流; 公共无效测试()抛出 IOException BluetoothDevice zee = BluetoothAdapter.getDefaultAdapter()。 getRemoteDevice("00:1C:4D:02:A6:55"); 袜子 = zee.createRfcommSocketToServiceRecord( UUID.fromString("8e1f0cf7-508f-4875-b62c-fbb67fd34812")); Log.d("ZeeTest", "++++ 连接"); 袜子.connect(); Log.d("ZeeTest", "++++ 已连接"); in = sock.getInputStream(); 字节[]缓冲区=新字节[1]; 整数字节 = 0; 诠释 x = 0; Log.d("ZeeTest", "++++ 听力..."); 而(x

===== 日志=====

04-19 22:27:01.147:调试/ZeeTest(8619):++++ 连接 04-19 22:27:04.085: INFO/usbd(1062): process_usb_uevent_message(): buffer = add@/devices/virtual/bluetooth/hci0/hci0:1 04-19 22:27:04.085: INFO/usbd(1062): main(): call select(...) 04-19 22:27:04.327: 错误/BluetoothEventLoop.cpp(4029): event_filter: 收到信号 org.bluez.Device:PropertyChanged 从 /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55 04-19 22:27:04.491:详细/BluetoothEventRedirector(7499):收到 android.bleutooth.device.action.UUID 04-19 22:27:04.905:调试/ZeeTest(8619):++++ 已连接 04-19 22:27:04.905: 调试/ZeeTest(8619): ++++ 听... 04-19 22:27:05.538: WARN/System.err(8619): java.io.IOException: 软件导致连接中止 04-19 22:27:05.600: WARN/System.err(8619): at android.bluetooth.BluetoothSocket.readNative(Native Method) ... 04-19 22:27:05.717: WARN/System.err(8619): java.io.IOException: 软件导致连接中止 04-19 22:27:05.717: WARN/System.err(8619): at android.bluetooth.BluetoothSocket.readNative(Native Method) ... 04-19 22:27:05.819:调试/ZeeTest(8619):++++ 完成:测试() 04-19 22:27:07.155:详细/BluetoothEventRedirector(7499):收到 android.bleutooth.device.action.UUID 04-19 22:27:09.077: INFO/usbd(1062): process_usb_uevent_message(): buffer = remove@/devices/virtual/bluetooth/hci0/hci0:1 04-19 22:27:09.085: INFO/usbd(1062): main(): call select(...) 04-19 22:27:09.139: 错误/BluetoothEventLoop.cpp(4029): event_filter: 收到信号 org.bluez.Device:PropertyChanged 从 /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55

测试申请2号

此测试连接并等待 - 对于显示自动断开问题很有用。

===== 代码=====

公共类 ZeeTest 扩展 Activity @覆盖 公共无效 onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); 设置内容视图(R.layout.main); getApplicationContext().registerReceiver(接收者, 新的 IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED)); getApplicationContext().registerReceiver(接收者, 新 IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); 尝试 BluetoothDevice zee = BluetoothAdapter.getDefaultAdapter()。 getRemoteDevice("00:1C:4D:02:A6:55"); 袜子 = zee.createRfcommSocketToServiceRecord( UUID.fromString("8e1f0cf7-508f-4875-b62c-fbb67fd34812")); Log.d("ZeeTest", "++++ 连接"); 袜子.connect(); Log.d("ZeeTest", "++++ 已连接"); 捕捉(IOException e) e.printStackTrace(); 私有静态最终 LogBroadcastReceiver 接收器 = new LogBroadcastReceiver(); 公共静态类 LogBroadcastReceiver 扩展 BroadcastReceiver @覆盖 公共无效 onReceive(上下文上下文,意图意图) Log.d("ZeeReceiver", intent.toString()); 捆绑附加服务 = intent.getExtras(); for (String k : extras.keySet()) Log.d("ZeeReceiver", " Extra: "+ extras.get(k).toString()); 私有 BluetoothSocket 袜子; @覆盖 公共无效 onDestroy() getApplicationContext().unregisterReceiver(receiver); 如果(袜子!= null) 尝试 袜子.close(); 捕捉(IOException e) e.printStackTrace(); super.onDestroy();

===== 日志=====

04-19 22:06:34.944: 调试/ZeeTest(7986): ++++ 连接 04-19 22:06:38.202: INFO/usbd(1062): process_usb_uevent_message(): buffer = add@/devices/virtual/bluetooth/hci0/hci0:1 04-19 22:06:38.202: INFO/usbd(1062): main(): call select(...) 04-19 22:06:38.217: 错误/BluetoothEventLoop.cpp(4029): event_filter: 收到信号 org.bluez.Device:PropertyChanged 从 /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55 04-19 22:06:38.428: VERBOSE/BluetoothEventRedirector(7499): 收到 android.bleutooth.device.action.UUID 04-19 22:06:38.968: 调试/ZeeTest(7986): ++++ 已连接 04-19 22:06:39.061: DEBUG/ZeeReceiver(7986): Intent act=android.bluetooth.device.action.ACL_CONNECTED (有附加功能) 04-19 22:06:39.108:调试/ZeeReceiver(7986):额外:00:1C:4D:02:A6:55 04-19 22:06:39.538:INFO/ActivityManager(4029):显示的活动 zee.test/.ZeeTest:5178 毫秒(总共 5178 毫秒) 04-19 22:06:41.014:详细/BluetoothEventRedirector(7499):收到 android.bleutooth.device.action.UUID 04-19 22:06:43.038: INFO/usbd(1062): process_usb_uevent_message(): buffer = remove@/devices/virtual/bluetooth/hci0/hci0:1 04-19 22:06:43.038: INFO/usbd(1062): main(): call select(...) 04-19 22:06:43.069: 错误/BluetoothEventLoop.cpp(4029): event_filter: 收到信号 org.bluez.Device:PropertyChanged from /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55 04-19 22:06:43.124: DEBUG/ZeeReceiver(7986): Intent act=android.bluetooth.device.action.ACL_DISCONNECTED (有额外) 04-19 22:06:43.124:调试/ZeeReceiver(7986):额外:00:1C:4D:02:A6:55

系统日志

===== 终端日志=====

$ sdptool 浏览 查询 ... 浏览 00:1C:4D:02:A6:55 ... $ sdptool 记录 00:1C:4D:02:A6:55 服务名称:Zeemote 服务记录句柄:0x10015 服务类 ID 列表: UUID 128:8e1f0cf7-508f-4875-b62c-fbb67fd34812 协议描述符列表: “L2CAP”(0x0100) "RFCOMM" (0x0003) 频道:1 语言库属性列表: 代码_ISO639:0x656e 编码:0x6a base_offset: 0x100 $ rfcomm 连接 /dev/tty10 00:1C:4D:02:A6:55 将 /dev/rfcomm0 连接到通道 1 上的 00:1C:4D:02:A6:55 按 CTRL-C 挂断 # rfcomm 显示 /dev/tty10 rfcomm0: 00:1F:3A:E4:C8:40 -> 00:1C:4D:02:A6:55 通道 1 已连接 [reuse-dlc release-on-hup tty-attached] # 猫 /dev/tty10 (这里没有什么) #hcidump HCI 嗅探器 - 蓝牙数据包分析器 1.42 版 设备:hci0 snap_len:1028 过滤器:0xffffffff HCI 事件:命令状态 (0x0f) plen 4 > HCI 事件:连接完成 (0x03) plen 11 HCI 事件:读取远程支持的功能 (0x0b) plen 11 HCI 事件:命令状态 (0x0f) plen 4 > HCI 事件:页面扫描重复模式更改 (0x20) plen 7 > HCI 事件:最大插槽更改 (0x1b) plen 3 HCI 事件:命令状态 (0x0f) plen 4 > ACL 数据:句柄 11 标志 0x02 dlen 16 L2CAP(s):信息 rsp:类型 2 结果 0 扩展功能掩码 0x0000 HCI 事件:已完成数据包数 (0x13) plen 5 > ACL 数据:句柄 11 标志 0x02 dlen 16 L2CAP(s):连接 rsp:dcid 0x04fb scid 0x0040 结果 1 状态 2 连接挂起 - 授权挂起 > HCI 事件:远程名称请求完成 (0x07) plen 255 > ACL 数据:句柄 11 标志 0x02 dlen 16 L2CAP(s):连接 rsp:dcid 0x04fb scid 0x0040 结果 0 状态 0 连接成功

===== adb bugreport 的一部分 =====

--已知设备-- 00:19:A1:2D:16:EA 绑定 (0) LG U830 00001105-0000-1000-8000-00805f9b34fb RFCOMM 频道 = 17 00:1C:4D:02:A6:55 绑定 (0) Zeemote JS1 00:0B:2E:6E:6F:00 绑定 (0) 摩托罗拉 HS815 00001108-0000-1000-8000-00805f9b34fb RFCOMM 频道 = 1 0000111e-0000-1000-8000-00805f9b34fb RFCOMM 通道 = 2 00:1F:3A:E4:C8:40 绑定 (0) BRCM BT4X 00001105-0000-1000-8000-00805f9b34fb RFCOMM 频道 = 9 00:18:42:EC:E2:99 绑定 (0) N95 00001105-0000-1000-8000-00805f9b34fb RFCOMM 频道 = 9

===== 启动日志摘录 =====

04-18 21:55:10.382:VERBOSE/BluetoothEventRedirector(1985):收到 android.bluetooth.adapter.action.STATE_CHANGED 04-18 21:55:10.421: 调试/BT HSHFP(1237): 加载优先级 00:19:A1:2D:16:EA = 100 04-18 21:55:10.428: 调试/BT HSHFP(1237): 加载优先级 00:1C:4D:02:A6:55 = 0 04-18 21:55:10.444: 调试/BT HSHFP(1237): 加载优先级 00:0B:2E:6E:6F:00 = 101 04-18 21:55:10.749: 调试/BT HSHFP(1237): 加载优先级 00:1F:3A:E4:C8:40 = 100 04-18 21:55:10.780: 调试/BT HSHFP(1237): 加载优先级 00:18:42:EC:E2:99 = 100

【问题讨论】:

我在 Nougat 7.0 设备中面临同样的问题(Nougat 7.1 + 工作正常)。尝试了这种解决方法,但到目前为止没有运气。任何帮助或建议都非常感谢...... 【参考方案1】:

尝试更改创建 RfcommSocket 的代码:

sock = zee.createRfcommSocketToServiceRecord(
                      UUID.fromString("8e1f0cf7-508f-4875-b62c-fbb67fd34812"));

对于此代码:

Method m = zee.getClass().getMethod("createRfcommSocket", new Class[]  int.class );
sock = (BluetoothSocket) m.invoke(device, 1);

还尝试在此 m.invoke(device, 1); 中更改 1-3 范围内的参数值; 当连接将被连接,但在您尝试阅读时中止,请在某个循环中再次调用您的方法 test()。 很简单:

for(int i=0;i<3;i++)  if(!testDone) test(); 

【讨论】:

为什么反射在这里起作用而不是原来的功能?为什么只有 1-3 的值? 为什么会这样? (createRfcommSocketToServiceRecord 的文档说使用the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB)。是否应该更改文档? 我对此有点困惑——原来的 UUID 发生了什么? android 怎么知道要连接什么? @Zainodis: createRfcommSocket() 方法被调用的 zee 对象是一个 BluetoothDevice由其设备地址标识(参见顶部的示例代码)。通道号是特定于设备的。 为什么不需要 UUID 已在 another thread on SO 中解释。【参考方案2】:

我合并了我编写的代码和来自 [android-beginners] Re: Serial over Bluetooth by XCaffeinated]1 和上述帖子的代码。

尽可能创建最简单的蓝牙程序。

此代码的主要添加是对connect() 引发的异常添加更好的处理。

搜索“@todo”以根据您的需要进行自定义。

我希望这可以为您节省一些时间!

package com.xxx; // @todo Change to your package.   

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.util.Log;

/**
 * This is the simplest bluetooth program. It sends one message to one bluetooth
 * device. The message and the bluetooth hardware id for the device are hard
 * coded. <br>
 * <br>
 * It does <b>not</b> receive any data. It does not do any thread processing. <br>
 * <br>
 * 
 * This application will be useful to communicate with a bluetooth hardware
 * device such as a bar code reader, Lego Mindstorm, a PC with a com port
 * application, a PC with a terminal program with 'listening' to a com port, a
 * second android device with a terminal program such as <a href=
 * "http://www.tec-it.com/en/software/data-acquisition/getblue/android-smartphone/Default.aspx"
 * >GetBlue</a>. It is not a full android bluetooth application but more a proof
 * of concept that the bluetooth works.
 * 
 * <br>
 * <br>
 * 
 * This code should cut and paste into the <a
 * href="http://developer.android.com/resources/tutorials/hello-world.html>
 * 'HelloAndroid' example</a>. It does not use any screen io.
 * 
 * Add to your Android Manifest.xml file: <uses-permission
 * android:name="android.permission.BLUETOOTH" /> <uses-permission
 * android:name="android.permission.BLUETOOTH_ADMIN" />
 * 
 * For a proper bluetooth example with threading and receiving data see: <a
 * href=
 * "http://developer.android.com/resources/samples/BluetoothChat/index.html"
 * >http://developer.android.com/resources/samples/BluetoothChat/index.html</a>
 * 
 * @see <a
 *      href="http://developer.android.com/guide/topics/wireless/bluetooth.html">
 *      http://developer.android.com/guide/topics/wireless/bluetooth.html</a>
 * 
 */
public class BlueToothTesterActivity extends Activity 

    /** The BluetoothAdapter is the gateway to all bluetooth functions **/
    protected BluetoothAdapter bluetoothAdapter = null;

    /** We will write our message to the socket **/
    protected BluetoothSocket socket = null;

    /** The Bluetooth is an external device, which will receive our message **/
    BluetoothDevice blueToothDevice = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Grab the BlueToothAdapter. The first line of most bluetooth programs.
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // if the BluetoothAdapter.getDefaultAdapter(); returns null then the
        // device does not have bluetooth hardware. Currently the emulator
        // does not support bluetooth so this will this condition will be true.
        // i.e. This code only runs on a hardware device an not on the emulator.
        if (bluetoothAdapter == null) 
            Log.e(this.toString(), "Bluetooth Not Available.");
            return;
        

        // This will find the remote device given the bluetooth hardware
        // address.
        // @todo: Change the address to the your device address
        blueToothDevice = bluetoothAdapter.getRemoteDevice("00:00:00:00:00:00");

        for (Integer port = 1; port <= 3; port++) 
            simpleComm(Integer.valueOf(port));
        
    

    protected void simpleComm(Integer port) 
        // byte [] inputBytes = null;

        // The documents tell us to cancel the discovery process.
        bluetoothAdapter.cancelDiscovery();

        Log.d(this.toString(), "Port = " + port);
        try 
            // This is a hack to access "createRfcommSocket which is does not
            // have public access in the current api.
            // Note: BlueToothDevice.createRfcommSocketToServiceRecord (UUID
            // uuid) does not work in this type of application. .
            Method m = blueToothDevice.getClass().getMethod(
                    "createRfcommSocket", new Class[]  int.class );
            socket = (BluetoothSocket) m.invoke(blueToothDevice, port);

            // debug check to ensure socket was set.
            assert (socket != null) : "Socket is Null";

            // attempt to connect to device
            socket.connect();
            try 
                Log.d(this.toString(),
                        "************ CONNECTION SUCCEES! *************");

                // Grab the outputStream. This stream will send bytes to the
                // external/second device. i.e it will sent it out.
                // Note: this is a Java.io.OutputStream which is used in several
                // types of Java programs such as file io, so you may be
                // familiar with it.
                OutputStream outputStream = socket.getOutputStream();

                // Create the String to send to the second device.
                // Most devices require a '\r' or '\n' or both at the end of the
                // string.
                // @todo set your message
                String message = "Data from Android and tester program!\r";

                // Convert the message to bytes and blast it through the
                // bluetooth
                // to the second device. You may want to use:
                // public byte[] getBytes (Charset charset) for proper String to
                // byte conversion.
                outputStream.write(message.getBytes());

             finally 
                // close the socket and we are done.
                socket.close();
            
            // IOExcecption is thrown if connect fails.
         catch (IOException ex) 
            Log.e(this.toString(), "IOException " + ex.getMessage());
            // NoSuchMethodException IllegalAccessException
            // InvocationTargetException
            // are reflection exceptions.
         catch (NoSuchMethodException ex) 
            Log.e(this.toString(), "NoSuchMethodException " + ex.getMessage());
         catch (IllegalAccessException ex) 
            Log.e(this.toString(), "IllegalAccessException " + ex.getMessage());
         catch (InvocationTargetException ex) 
            Log.e(this.toString(),
                    "InvocationTargetException " + ex.getMessage());
        
    


【讨论】:

感谢您的示例。你能让它与运行 2.3.3 的 Android 手机一起使用吗?我在这里遇到了***.com/questions/12432990/… 列出的问题,我尝试了您的示例但没有成功。我的 Motorolla Droid X 在尝试连接套接字时遇到连接超时 IOException。如果我手动配对然后使用我的 SSP 设备运行您的示例,我会收到 Permission denied IOException。有什么想法吗?【参考方案3】:

如果我理解正确,您的应用无法从设备中看到任何数据?

一件小事:试试你的 UUID 没有 hiphens。在我的 RFCOMM 应用程序中,我实际上将 UUID 定义为一个长整型常量。

另外,你的 test() 方法的编写方式让我相信 test() 正在建立连接,定义一个线程,告诉它开始并立即返回。换句话说,您的线程正在从线程外部引用一个变量,该变量是 test() 方法的一部分,但是当 test() 终止时,它的变量也会终止。

简而言之,尝试在线程之外进行测试并首先让它在那里工作。一种简单的方法是使用 Thread.run() 而不是 thread.start()。 .run() 在前台运行它(因此阻塞 test() 所以它不会在线程完成之前返回)。

对于长期解决方案,您可能希望将蓝牙变量定义为全局/成员变量,这样它们就不会超出范围,并且始终可供您的线程使用。

【讨论】:

嗨布拉德!谢谢您的回答。我尝试了不同的 UUID 格式,但发现它们之间没有区别 :( 如您所见,我更新了第一个测试应用程序,因此它不再使用线程(我想用线程“按书本”来做 ;))。 您绝对应该使用线程 - 但它们可能很难排除故障,因此请保持它们的简单性,并在您让代码在线程外工作后将其移动到线程中。您的 UUID 可能没问题 - 您的主要问题可能只是线程。请记住,一旦您启动线程,启动线程的方法将继续执行其业务(甚至返回),因为线程在后台启动。将您的蓝牙连接定义为类变量,以便任何方法/线程都可以访问它。然后使用可以阻塞的 I/O 线程,这样用户就不会注意到 我会尽快完成该死的工作;)实际上我认为这可能只是 Android 的 Java->Bluez 胶水代码中的一个错误。想不出任何其他解释。 虽然这是可能的,但我有几个应用程序(市场上的一个)在一段时间内可以很好地使用蓝牙。我的下一个建议是将缓冲的输入和输出流附加到套接字。您可能正在经历流量控制。使用缓冲流可以防止这种情况并显着提高吞吐量(因为数据可以到达并在缓冲区中等待直到您读取它,而不是在您的代码读取它之前被阻塞)。导入 java.io.BufferedInputStream; instream = new BufferedInputStream(mobdSock.getInputStream(),INPUT_BUFFER_SIZE);【参考方案4】:

试试众所周知的 UUID:00001101-0000-1000-8000-00805F9B34FB

【讨论】:

遗憾的是,这些都不起作用:(我尝试了很多不同的服务,但所有“通用”服务都出现了服务发现错误。但公平地说,这是我的猜测之一只是连接到错误的 UUID(而且 Android 的 API 太愚蠢了,无法理解 MAC/服务/端口寻址)。【参考方案5】:

上面的代码不适用于带有 4.0.4 的 Samsung Galaxy tab 2。 BTSocket.connect 总是触发蓝牙配对对话框然后失败,即使输入了正确的 pin。 从“createRfcommSocket”更改为“createInsecureRfcommSocket”解决了这个问题。希望这会有所帮助,我在这个问题上挣扎了 3 个多小时。

【讨论】:

以上是关于如何防止Android蓝牙RFCOMM连接在.connect()之后立即死亡?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Android 应用程序正确连接到支持蓝牙的 Arduino 微控制器上的 RFCOMM 插座?

使用不安全的 RFCOMM 蓝牙套接字时,android 何时显示配对对话框?

如何使用Android蓝牙开发

如何使用Android蓝牙开发

(Dis)连接蓝牙设备与Windows.Devices.Bluetooth.Rfcomm (WP8.1)

Android:蓝牙 UUID 如何工作?