要在两台设备之间创建连接,您必须同时实现
服务器端和客户端机制,因为一台设备必须打开服务器
另一个必须使用服务器设备的
MAC 地址。服务器设备和客户端设备各自获得所需的
BluetoothSocket
- 不同
方法。收到传入连接后,服务器会收到套接字信息
接受。客户端在打开 RFCOMM 通道时提供套接字信息
发送到服务器。
当服务器和客户端
同一 RFCOMM 通道上已连接的 BluetoothSocket
。此时,每个
设备可以获取输入和输出流,并且数据可以开始传输,
请参阅传输蓝牙
数据。此部分
介绍了如何在两台设备之间发起连接。
请确保您拥有适当的 蓝牙权限和 请提前对应用进行蓝牙设置 尝试查找蓝牙设备
连接技术
一种实现方法是自动将每台设备准备为服务器 以便每台设备都打开一个服务器套接字并监听连接。在 在这种情况下,任一设备都可以发起与另一台设备的连接,并成为 客户端。或者,一台设备可以显式托管连接并打开一个 按需服务器套接字,由另一台设备发起连接。
图 1. 蓝牙配对对话框。
作为服务器连接
当您要连接两台设备时,其中一台设备必须保持
开放
BluetoothServerSocket
。
服务器套接字的用途是监听传入的连接请求
并在请求被接受后提供关联的 BluetoothSocket
。当
BluetoothSocket
是从 BluetoothServerSocket
(即
除非您愿意,否则可以(也应该)舍弃 BluetoothServerSocket
以接受更多连接。
如需设置服务器套接字并接受连接,请完成以下步骤 步骤顺序:
通过调用以下方法来获取
BluetoothServerSocket
:listenUsingRfcommWithServiceRecord(String, UUID)
。该字符串是服务的可识别名称, 自动写入新的服务发现协议 (SDP) 数据库条目 。该名称没有限制,可以直接使用您的应用名称。 SDP 条目中也包含通用唯一标识符 (UUID) 并构成与客户端设备的连接协议的依据。这样 就是,当客户端尝试连接此设备时,它会携带一个 UUID 用于标识自己想要连接的服务的唯一标识符。这些 UUID 必须匹配,连接才会被接受。
UUID 是 128 位的标准化字符串 ID 格式,用于在 身份信息UUID 用于标识需要 因为 UUID 的概率 等于零。它是独立生成的,不使用 中央权威机构的运作机制在这种情况下,它用于唯一地标识您的 应用的蓝牙服务。要获取 UUID 以用于您的应用,您可以使用一个 随机返回一个字词,
UUID
生成器,然后初始化 使用 UUIDfromString(String)
。通过调用
accept()
。这是阻塞调用。它在连接成功后返回, 或出现例外情况。只有当 远程设备发送的连接请求包含匹配的 UUID 已向此监听服务器套接字注册的标识符。成功后
accept()
会返回已连接的BluetoothSocket
。除非您希望接受更多连接,否则请致电
close()
。此方法调用会释放服务器套接字及其所有资源,但是 不会关闭由
BluetoothSocket
accept()
。与 TCP/IP 不同,RFCOMM 仅允许每个连接一个客户端 因此在大多数情况下,对每个渠道调用close()
会很合理。 接受已连接的套接字后立即执行BluetoothServerSocket
。
由于 accept()
调用是阻塞调用,因此请勿在主线程中执行它
activity 界面线程。在另一个线程中执行它可确保您的应用
仍会响应其他用户交互。通常有必要处理所有工作
在新线程中涉及 BluetoothServerSocket
或 BluetoothSocket
由您的应用管理如需取消阻塞的调用(例如 accept()
),请调用 close()
在其他线程中的 BluetoothServerSocket
或 BluetoothSocket
上。注意事项
BluetoothServerSocket
或 BluetoothSocket
上的所有方法
是线程安全的
示例
以下是用于接受 传入的连接数:
Kotlin
private inner class AcceptThread : Thread() { private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) { bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID) } override fun run() { // Keep listening until exception occurs or a socket is returned. var shouldLoop = true while (shouldLoop) { val socket: BluetoothSocket? = try { mmServerSocket?.accept() } catch (e: IOException) { Log.e(TAG, "Socket's accept() method failed", e) shouldLoop = false null } socket?.also { manageMyConnectedSocket(it) mmServerSocket?.close() shouldLoop = false } } } // Closes the connect socket and causes the thread to finish. fun cancel() { try { mmServerSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the connect socket", e) } } }
Java
private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // Use a temporary object that is later assigned to mmServerSocket // because mmServerSocket is final. BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code. tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { Log.e(TAG, "Socket's listen() method failed", e); } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned. while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { Log.e(TAG, "Socket's accept() method failed", e); break; } if (socket != null) { // A connection was accepted. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(socket); mmServerSocket.close(); break; } } } // Closes the connect socket and causes the thread to finish. public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { Log.e(TAG, "Could not close the connect socket", e); } } }
在本例中,只需要一个传入连接,因此
且 BluetoothSocket
被接受后,应用会将
获取 BluetoothSocket
到单独的线程,关闭
BluetoothServerSocket
,并打破循环。
请注意,当 accept()
返回 BluetoothSocket
时,套接字已经
已连接。因此,您不应调用
connect()
,就像您一样
从客户端请求代码
特定于应用的 manageMyConnectedSocket()
方法旨在启动
有关传输数据的线程,我们将在本课程的 主题
传输蓝牙
数据。
通常,您应在完成操作后立即关闭 BluetoothServerSocket
并监听传入的连接在此示例中,close()
会在
因为 BluetoothSocket
已获取。您可能还希望提供一个
线程中可用于关闭事件中的私有 BluetoothSocket
的方法
您需要停止监听该服务器套接字。
作为客户端连接
为了发起与接受的远程设备的连接
进行连接,您必须先获取 BluetoothDevice
该对象表示远程设备。要了解如何创建
BluetoothDevice
,请参阅查找蓝牙
设备。您必须
然后使用 BluetoothDevice
获取 BluetoothSocket
并启动
连接。
基本步骤如下所示:
使用
BluetoothDevice
,调用BluetoothSocket
,createRfcommSocketToServiceRecord(UUID)
。此方法会初始化一个
BluetoothSocket
对象,以允许客户端 连接到BluetoothDevice
。此处传递的 UUID 必须与使用的 UUID 一致 由服务器设备调用listenUsingRfcommWithServiceRecord(String, UUID)
以打开其BluetoothServerSocket
。要使用匹配的 UUID,请对 将 UUID 字符串导入您的应用,然后从服务器引用该字符串 和客户端代码通过调用
connect()
发起连接。请注意,此方法 屏蔽调用。客户端调用此方法后,系统会执行 SDP 查找,以查找 具有匹配的 UUID 的远程设备。如果查询成功, 远程设备接受该连接,它共享 RFCOMM 通道以供使用 并且
connect()
方法会返回。如果连接 失败,或者如果connect()
方法超时(大约 12 秒后),则 该方法会抛出IOException
。
由于 connect()
是阻塞调用,因此您应始终执行此操作
与主 activity (UI) 分开的线程中的连接过程
线程。
示例
以下是启动蓝牙的客户端线程的基本示例 连接:
Kotlin
private inner class ConnectThread(device: BluetoothDevice) : Thread() { private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) { device.createRfcommSocketToServiceRecord(MY_UUID) } public override fun run() { // Cancel discovery because it otherwise slows down the connection. bluetoothAdapter?.cancelDiscovery() mmSocket?.let { socket -> // Connect to the remote device through the socket. This call blocks // until it succeeds or throws an exception. socket.connect() // The connection attempt succeeded. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(socket) } } // Closes the client socket and causes the thread to finish. fun cancel() { try { mmSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the client socket", e) } } }
Java
private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { // Use a temporary object that is later assigned to mmSocket // because mmSocket is final. BluetoothSocket tmp = null; mmDevice = device; try { // Get a BluetoothSocket to connect with the given BluetoothDevice. // MY_UUID is the app's UUID string, also used in the server code. tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { Log.e(TAG, "Socket's create() method failed", e); } mmSocket = tmp; } public void run() { // Cancel discovery because it otherwise slows down the connection. bluetoothAdapter.cancelDiscovery(); try { // Connect to the remote device through the socket. This call blocks // until it succeeds or throws an exception. mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and return. try { mmSocket.close(); } catch (IOException closeException) { Log.e(TAG, "Could not close the client socket", closeException); } return; } // The connection attempt succeeded. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(mmSocket); } // Closes the client socket and causes the thread to finish. public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "Could not close the client socket", e); } } }
请注意,在此代码段中,系统会在连接之前调用 cancelDiscovery()
错误。您应始终在 connect()
之前调用 cancelDiscovery()
。
特别是因为无论设备是否正常,cancelDiscovery()
都会成功
正在进行发现。如果您的应用需要确定
正在发现设备,您可以使用
isDiscovering()
。
特定于应用的 manageMyConnectedSocket()
方法旨在启动
用于传输数据的线程,我们将在本课程的
传输蓝牙数据。
使用完 BluetoothSocket
后,请务必调用 close()
。执行此操作
立即关闭已连接的套接字,并释放所有相关的内部
资源。