如何防止Android蓝牙RFCOMM连接在.connect()后立即中断?

49

这个问题已经解决了!非常感谢Brad、Denis和junkie!你们是英雄!:)

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

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

public class ZeeTest extends Activity {
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
       try {
           for (int i = 0; i < 3; i++) {
               test();
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
private boolean connected = false; private BluetoothSocket sock; private InputStream in; public void test() throws Exception { if (connected) { return; } BluetoothDevice zee = BluetoothAdapter.getDefaultAdapter(). getRemoteDevice("00:1C:4D:02:A6:55"); Method m = zee.getClass().getMethod("createRfcommSocket", new Class[] { int.class }); sock = (BluetoothSocket)m.invoke(zee, Integer.valueOf(1)); Log.d("ZeeTest", "++++ 连接中"); sock.connect(); Log.d("ZeeTest", "++++ 已连接"); in = sock.getInputStream(); byte[] buffer = new byte[50]; int read = 0; Log.d("ZeeTest", "++++ 监听..."); try { while (true) { read = in.read(buffer); connected = true; StringBuilder buf = new StringBuilder(); for (int i = 0; i < read; i++) { int b = buffer[i] & 0xff; if (b < 0x10) { buf.append("0"); } buf.append(Integer.toHexString(b)).append(" "); } Log.d("ZeeTest", "++++ 读取了 "+ read +" 字节: "+ buf.toString()); } } catch (IOException e) {} Log.d("ZeeTest", "++++ 完成:test()"); } @Override public void onDestroy() { try { if (in != null) { in.close(); } if (sock != null) { sock.close(); } } catch (IOException e) { e.printStackTrace(); } super.onDestroy(); } }

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

我正在尝试从运行2.0.1固件的Moto Droid连接到Zeemote (http://zeemote.com/) 游戏控制器。下面贴出两个测试应用程序:一个实际上尝试从输入流中读取数据,另一个则只是坐在那里,等待设备在5秒后断开连接。是的,我有第三个版本 :),它首先等待ACL_CONNECTED,然后打开套接字,但行为没有任何变化。

一些背景信息: 使用bluez工具,我可以完美地从我的笔记本电脑连接到Zeemote(日志也附在此处)。我确信Droid也可以与Zeemote通信,因为市场上的“Game Pro”可以很好地与它配合使用(但它可能使用了较低级别的API驱动程序/服务)。

我注意到,“adb bugreport”对于所有其他设备(包括Moto HS815耳机和另一个哑设备,“sdp browse”报告为空)都会报告UUID和RFCOMM通道,但对于Zeemote则不会这样做。此外,当设备启动时,Zeemote的优先级为0(其他设备的优先级为100+)。

我非常困惑,已经努力调试了很长时间,现在没有更多的想法了,所以任何帮助都将不胜感激(即使您不知道答案 :))

谢谢, Max

测试应用程序1

此应用程序尝试实际从设备中读取内容。

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

public class ZeeTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        try {
            test();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
private BluetoothSocket sock; private InputStream in; public void test() throws IOException { BluetoothDevice zee = BluetoothAdapter.getDefaultAdapter(). getRemoteDevice("00:1C:4D:02:A6:55"); sock = zee.createRfcommSocketToServiceRecord( UUID.fromString("8e1f0cf7-508f-4875-b62c-fbb67fd34812")); Log.d("ZeeTest", "++++ 正在连接"); sock.connect(); Log.d("ZeeTest", "++++ 已连接"); in = sock.getInputStream(); byte[] buffer = new byte[1]; int bytes = 0; int x = 0; Log.d("ZeeTest", "++++ 正在监听..."); while (x < 2) { x++; try { bytes = in.read(buffer); Log.d("ZeeTest", "++++ 读取 "+ bytes +" 字节"); } catch (IOException e) { e.printStackTrace(); try { Thread.sleep(100); } catch (InterruptedException ie) {} } } Log.d("ZeeTest", "++++ 完成:test()"); } @Override public void onDestroy() { try { if (in != null) { in.close(); } if (sock != null) { sock.close(); } } catch (IOException e) { e.printStackTrace(); } super.onDestroy(); } }

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

04-19 22:27:01.147: DEBUG/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: ERROR/BluetoothEventLoop.cpp(4029): event_filter: 从 /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55 接收到信号 org.bluez.Device:PropertyChanged
04-19 22:27:04.491: VERBOSE/BluetoothEventRedirector(7499): 接收到 android.bleutooth.device.action.UUID
04-19 22:27:04.905: DEBUG/ZeeTest(8619): ++++ 已连接
04-19 22:27:04.905: DEBUG/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: DEBUG/ZeeTest(8619): ++++ 完成:test()
04-19 22:27:07.155: VERBOSE/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: ERROR/BluetoothEventLoop.cpp(4029): event_filter: 从 /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55 接收到信号 org.bluez.Device:PropertyChanged

测试应用程序No. 2

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

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

public class ZeeTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        getApplicationContext().registerReceiver(receiver,
                    new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
        getApplicationContext().registerReceiver(receiver,
                    new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
        try {
            BluetoothDevice zee = BluetoothAdapter.getDefaultAdapter().
                            getRemoteDevice("00:1C:4D:02:A6:55");
            sock = zee.createRfcommSocketToServiceRecord(
                            UUID.fromString("8e1f0cf7-508f-4875-b62c-fbb67fd34812"));
Log.d("ZeeTest", "++++ 正在连接"); sock.connect(); Log.d("ZeeTest", "++++ 连接成功"); } catch (IOException e) { e.printStackTrace(); } }
private static final LogBroadcastReceiver receiver = new LogBroadcastReceiver(); public static class LogBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("ZeeReceiver", intent.toString()); Bundle extras = intent.getExtras(); for (String k : extras.keySet()) { Log.d("ZeeReceiver", " 额外信息: "+ extras.get(k).toString()); } } }
private BluetoothSocket sock; @Override public void onDestroy() { getApplicationContext().unregisterReceiver(receiver); if (sock != null) { try { sock.close(); } catch (IOException e) { e.printStackTrace(); } } super.onDestroy(); } }

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

04-19 22:06:34.944: DEBUG/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(): 调用 select(...)
04-19 22:06:38.217: ERROR/BluetoothEventLoop.cpp(4029): event_filter: 从 /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55 接收到信号 org.bluez.Device:PropertyChanged
04-19 22:06:38.428: VERBOSE/BluetoothEventRedirector(7499): 收到 android.bluetooth.device.action.UUID
04-19 22:06:38.968: DEBUG/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: DEBUG/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: VERBOSE/BluetoothEventRedirector(7499): 收到 android.bluetooth.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(): 调用 select(...)
04-19 22:06:43.069: ERROR/BluetoothEventLoop.cpp(4029): event_filter: 从 /org/bluez/4121/hci0/dev_00_1C_4D_02_A6_55 接收到信号 org.bluez.Device:PropertyChanged
04-19 22:06:43.124: DEBUG/ZeeReceiver(7986): Intent { act=android.bluetooth.device.action.ACL_DISCONNECTED (有额外信息) }
04-19 22:06:43.124: DEBUG/ZeeReceiver(7986):     额外信息: 00:1C:4D:02:A6:55

系统日志

=====


在Nougat 7.0设备上遇到了同样的问题(Nougat **7.1 +**正常工作)。尝试了这些解决方法,但迄今为止没有运气。非常感谢任何帮助或建议... - KH_AJU
5个回答

27
尝试更改创建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);

尝试将1-3范围内的参数值更改为m.invoke(device, 1)。当连接处于Connected状态,但在尝试读取时被中止时,请在某个循环中再次调用您的test()方法。

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

4
为什么这个方法有效?(createRfcommSocketToServiceRecord的文档说要使用“众所周知的SPP UUID 00001101-0000-1000-8000-00805F9B34FB”)。文档该被更改吗? - jacknad
4
我有点困惑 - 原来的UUID 发生了什么事?安卓如何知道要连接到哪里? - ekatz
2
@Zainodis:在 createRfcommSocket() 方法被调用的 zee 对象上,是一个已经通过设备地址(请参见顶部的示例代码)识别出来的 _BluetoothDevice_。通道号是特定于设备的。 - Gilead
@Denis L 如何创建应用程序 UUID? - appukrb
1
为什么不需要UUID已在SO的另一篇帖子中解释过了。 - user740006
显示剩余2条评论

9
我合并了我写的代码和来自[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
谢谢提供示例。您能否在运行Android 2.3.3的手机上成功运行此示例?我在这里遇到了问题:http://stackoverflow.com/questions/12432990/android-rfcomm-connection-issues-on-releases-prior-to-2-3-5,我尝试了您的示例但没有成功。我的Motorolla Droid X在尝试连接套接字时遇到“连接超时”的IOException。如果我手动配对,然后使用我的SSP设备运行您的示例,我会得到“权限被拒绝”的IOException。有什么想法吗? - c12

5
如果我理解正确,您的应用程序完全无法看到设备中的任何数据?
一个小建议是:尝试不使用连字符输入UUID。在我的RFCOMM应用程序中,我实际上将UUID定义为长整型常量。
此外,您的test()方法编写方式让我相信test()正在建立连接、定义线程、告诉它启动并立即返回。换句话说,您的线程正在引用来自test()方法的外部变量,但当test()结束时,其变量也随之消失。
简而言之,请尝试在线程之外进行测试,并首先使其正常工作。一种简单的方法是使用Thread.run()而不是thread.start()。.run()在前台运行它(因此阻止test()在线程完成之前返回)。
对于长期解决方案,您可能希望将蓝牙变量定义为全局/成员变量,以便它们不会超出范围,并始终可供您的线程使用。

嗨,Brad!感谢你的回答。我尝试了不同的UUID格式,但是发现它们之间没有区别:(正如你所看到的,我更新了第一个测试应用程序,所以它不再使用线程(我想按照书本上的方法来做;))。 - Gilead
1
你一定要使用线程 - 但是它们可能很难进行故障排除,因此保持简单,并在将代码移入线程之前先在线程外部使其正常工作。你的UUID可能没问题 - 你主要的问题可能只是线程。请记住,一旦启动线程,启动线程的方法将继续进行其业务(甚至返回),而线程将在后台启动。将蓝牙连接定义为类变量,以便任何方法/线程都可以访问它。然后使用I/O线程进行阻塞,以便用户不会注意到。 - Brad Hein
我会在这该死的东西能正常工作之后立即处理 ;) 实际上,我认为这可能只是 Android 的 Java->Bluez 粘合代码中的一个 bug。想不出其他解释了。 - Gilead
1
虽然这是可能的,但我的几个应用程序(其中一个在市场上)现在已经使用蓝牙工作了一段时间表现出色。我的下一个建议是将缓冲输入输出流附加到套接字上。 您可能正在经历流量控制。 使用缓冲流可以防止此情况,并显着增加吞吐量(因为数据可以到达并等待在缓冲区中,直到您读取它,而不是被阻塞直到您的代码读取它)。import java.io.BufferedInputStream;instream = new BufferedInputStream(mobdSock.getInputStream(), INPUT_BUFFER_SIZE); - Brad Hein

0
尝试使用众所周知的UUID:00001101-0000-1000-8000-00805F9B34FB

很遗憾,这些都不起作用 :( 我尝试了许多不同的服务,但对于所有“通用”的服务都遇到了服务发现错误。但说得好,那是我猜测之一,我只是连接到错误的UUID(而且安卓的API太蠢了,无法理解MAC/服务/端口寻址)。 - Gilead

0

上述代码在安卓4.0.4的三星Galaxy Tab 2上无法正常工作。BTSocket.connect总是会触发蓝牙配对对话框,即使输入了正确的PIN码也会失败。 将"createRfcommSocket"改为"createInsecureRfcommSocket"可以解决这个问题。希望这能帮到你,我曾经为此问题苦苦挣扎了三个多小时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接