Content
概述
- Android应用可通过Bluetooth API执行以下操作
- 扫描其他蓝牙设备
- 查询本地蓝牙适配器的配对蓝牙设备
- 建立 RFCOMM 通道
- 通过服务发现连接到其他设备
- 与其他设备进行双向数据传输
- 管理多个连接
- 蓝牙进行通信的四大必须任务
- 设置蓝牙
- 查找局部区域内的配对设备或可用设备
- 连接设备
- 在设备之间传输数据
BLUETOOTH
: 执行任何蓝牙通信,例如请求连接、接受连接和传输数据等。ACCESS_FINE_LOCATION
: 蓝牙扫描可用于手机用户信息ACCESS_COARSE_LOCATION
BLUETOOTH_ADMIN
: (optional) 如果您想让应用启动设备发现或操纵蓝牙设置,则除了 BLUETOOTH
权限以外,您还必须声明 BLUETOOTH_ADMIN
权限。大多数应用只是需利用此权限发现本地蓝牙设备。除非应用是根据用户请求修改蓝牙设置的“超级管理员”,否则不应使用此权限所授予的其他功能。蓝牙配置文件 是适用于设备间蓝牙通信的无线接口规范。
BluetoothHeadset
类 — 用于控制蓝牙耳机服务的代理BluetoothA2dp
类 — 用于控制蓝牙A2DP服务的代理BluetoothProfile.ServiceListener
。此侦听器会在 BluetoothProfile
客户端连接到服务或断开服务连接时向其发送通知。getProfileProxy()
与配置文件所关联的配置文件代理对象建立连接。在以下示例中,配置文件代理对象是一个 BluetoothHeadset
实例。onServiceConnected()
中,获取配置文件代理对象的句柄。从 Android 3.0(API 级别 11)开始,应用可注册接收耳机发送的预定义供应商特定 AT 命令(例如 Plantronics +XEVENT 命令)的系统广播。例如,应用可接收指示所连接设备电池电量的广播,并根据需要通知用户或采取其他操作。为 ACTION_VENDOR_SPECIFIC_HEADSET_EVENT
Intent 创建广播接收器,以处理耳机的供应商特定 AT 命令。
BluetoothAdapter
,分两步:BluetoothAdatpter
BluetoothAdatpter
。BluetoothAdatpter
,调用静态的 getDefaultAdapter()
方法,返回一个 BluetoothAdatpter
对象,表示设备自身的蓝牙适配器。getDefaultAdapter()
返回 null
,则表示设备不支持蓝牙。isEnabled()
,以检查当前是否启用蓝牙。若返回 false
,则表示处于停用状态。startActivityForResult()
,传入一个 ACTION_REQUEST_ENABLE
Intent操作,此调用会发出系统设置启用蓝牙的请求(无需停止应用)startActivityForResult()
的 REQUEST_ENABLE_BT
常量为局部定义的整型数(必须大于 0)。系统会以 onActivityResult()
实现中的 requestCode
参数形式,向您传回该常量。onActivityResult()
回调中收到 RESULT_OK
结果代码。如果由于某个错误(或用户响应“No”)未成功启用蓝牙,则结果代码为 RESULT_CANCELED
。ACTION_STATE_CHANGEED
广播Intent,每当蓝牙状态发生变化时,系统都会广播此Intent。EXTRA_STATE
和 EXTRA_PREVIOUS_STATE
,二者分别包含新的和旧的蓝牙状态。STATE_TURNING_ON
和STATE_ON
和STATE_TURNING_OFF
和STATE_OFF
提示:启用可检测性即可自动启用蓝牙。如果您计划在执行蓝牙 Activity 之前一直启用设备的可检测性,则可以跳过上述步骤 2。如需了解详情,请阅读的启用可检测性部分。
getBondedDevices()
,会返回一组表示已配对设备的 BluetoothDevice
对象。BluetoothDevice
对象中获取MAC地址,可的通过调用 getAddress()
检索此地址。有关创建连接的详情,请参阅连接设备部分。注意:
ACTION_REQUEST_DISCOVERABLE
Intent调用 startActivityForResult(Intent, int)
,这样便可发出启用系统可检测到模式的请求。EXTRA_DISCOVERABLE_DURATION
Extra属性,定义不同的持续时间,最长可达3600s。若设置为0,则设备始终处于可检测到模式(安全性低,不建议使用)onActivityResult()
回调的调用。RESULT_CANCELED
注意:如果尚未在设备上启用蓝牙,则启用设备可检测性会自动启用蓝牙。
ACTION_SCAN_MODE_CHANGED
Intent注册BroadcastReceiver。EXTRA_SCAN_MODE
和 EXTRA_PREVIOUT_SCAN_MODE
二者分别提供新的和旧的扫描模式。每个Extra属性可能拥有以下值:SCAN_MODE_CONNECTABLE_DISCOVERABLE
设备处于可检测到模式。SCAN_MODE_CONNECTABLE
设备未处于可检测到模式,但仍能收到连接。SCAN_MODE_NONE
设备未处于可检测到模式,且无法收到连接。注意: 如果两台设备之前尚未配对,则在连接过程中,Android 框架会自动向用户显示配对请求通知或对话框。因此,在尝试连接设备时,您的应用无需担心设备是否已配对。
BluetoothServerSocket
,从而充当服务器。服务器套接字涌入是监听传入的连接请求,并在接受请求后提供已连接的 BluetoothSocket
。从BluetoothServerSocket
中获取BluetoothSocket
后,应该丢弃 BluetoothServerSocket
,除非设备需要接受更多的连接。listenUsingRfcommWithServiceRecord()
获取 BluetoothServerSocket
。fromString(String)
初始化一个 UUID
。accept()
开始侦听连接请求。accept()
将返回已连接的 BluetoothSocket
。close()
。accept()
所返回的已连接的 BluetoothSocket
。与 TCP/IP 不同,RFCOMM 一次只允许每个通道有一个已连接的客户端,因此大多数情况下,在接受已连接的套接字后,您可以立即在 BluetoothServerSocket
上调用 close()
。accept()
为阻塞调用,因此不应在主Activity界面线程中执行该应用。accept()
等被阻塞的调用,需要通过另一个线程,在BluetoothServerSocket
或BluetoothSocket
上调用 close()
。请注意,BluetoothServerSocket
或 BluetoothSocket
中的所有方法都是线程安全的方法。BluetoothDevice
对象。然后,必须使用该对象来获取 BluetoothSocket
并发起连接BluetoothDevice
,通过调用 createRfcommSocketToServiceRecord(UUID)
获取 BluetoothSocket
。BluetoothSocket
对象,以便客户端连接至 BluetoothDevice
。此处传递的 UUID 必须与服务器设备在调用 listenUsingRfcommWithServiceRecord(String, UUID)
开放其 BluetoothServerSocket
时所用的 UUID 相匹配。如要使用匹配的 UUID,请通过硬编码方式将 UUID 字符串写入您的应用,然后通过服务器和客户端代码引用该字符串。connect()
发起连接。请注意,此方法为阻塞调用。connect()
方法将会返回。如果连接失败,或者 connect()
方法超时(约 12 秒后),则此方法将引发 IOException
。connect()
是阻塞调用,因此您应始终在主 Activity(界面)线程以外的线程中执行此连接步骤。注意:您应始终调用cancelDiscovery()
,以确保设备在您调用connect()
之前不会执行设备发现。如果正在执行发现操作,则会大幅降低连接尝试的速度,并增加连接失败的可能性。
BluetoothSocket
,可以在设备之间共享信息。BluetoothSocket
传输数据的一般过程如下:getInputStream()
和 getOutputStream()
,分别获取通过套接字处理数据传输的 InputStream
和 OutputStream
。read(byte[])
和 write(byte[])
读取数据以及将其写入数据流。read(byte[])
和 write(byte[])
方法都是阻塞调用。read(byte[])
方法将会阻塞,直至从数据流中读取数据。write(byte[])
方法通常不会阻塞,但若远程设备调用 read(byte[])
方法的速度不够快,进而导致中间缓冲区已满,则该方法可能会保持阻塞状态以实现流量控制。因此,线程中的主循环应专门用于从 InputStream
中读取数据。您可使用线程中单独的公共方法,发起对 OutputStream
的写入操作。BluetoothAdapter
表示本地蓝牙适配器(蓝牙无线装置)。BluetoothAdapter
是所有蓝牙交互的入口点。借助该类,您可以发现其他蓝牙设备、查询已绑定(已配对)设备的列表、使用已知的 MAC 地址实例化 BluetoothDevice
,以及通过创建 BluetoothServerSocket
侦听来自其他设备的通信。BluetoothDevice
表示远程蓝牙设备。借助该类,您可以通过 BluetoothSocket
请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。BluetoothSocket
表示蓝牙套接字接口(类似于 TCP Socket
)。这是允许应用使用 InputStream
和 OutputStream
与其他蓝牙设备交换数据的连接点。BluetoothServerSocket
表示用于侦听传入请求的开放服务器套接字(类似于 TCP ServerSocket
)。如要连接两台 Android 设备,其中一台设备必须使用此类开放一个服务器套接字。当远程蓝牙设备向此设备发出连接请求时,该设备接受连接,然后返回已连接的 BluetoothSocket
。BluetoothClass
描述蓝牙设备的一般特征和功能。这是一组只读属性,用于定义设备的类和服务。虽然这些信息会提供关于设备类型的有用提示,但该类的属性未必描述设备支持的所有蓝牙配置文件和服务。BluetoothProfile
表示蓝牙配置文件的接口。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。举个例子:免提配置文件。如需了解有关配置文件的详细讨论,请参阅使用配置文件。BluetoothHeadset
提供蓝牙耳机支持,以便与手机配合使用。这包括蓝牙耳机配置文件和免提 (v1.5) 配置文件。BluetoothA2dp
定义如何使用蓝牙立体声音频传输配置文件 (A2DP),通过蓝牙连接将高质量音频从一个设备流式传输至另一个设备。BluetoothHealth
表示用于控制蓝牙服务的健康设备配置文件代理。BluetoothHealthCallback
用于实现 BluetoothHealth
回调的抽象类。您必须扩展此类并实现回调方法,以接收关于应用注册状态和蓝牙通道状态变化的更新内容。BluetoothHealthAppConfiguration
表示第三方蓝牙健康应用注册的应用配置,该配置旨在实现与远程蓝牙健康设备的通信。BluetoothProfile.ServiceListener
当 BluetoothProfile
进程间通信 (IPC) 客户端连接到运行特定配置文件的内部服务或断开该服务连接时,向该客户端发送通知的接口。<manifest ... > <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- If your app targets Android 9 or lower, you can declare ACCESS_COARSE_LOCATION instead. --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> ... </manifest>
val bluetoothAdapter: BluetoothAdapter? = Bluetooth.getDefaultAdapter() if (bluetoothAdapter == null) { // Device does not support Bluetooth }
if (bluetoothAdapter?.isEnabled == false) { val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) }
val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices pairedDevices?.forEach { device -> val deviceName = device.name val deviceHardwareAddress = device.address // MAC address }
override fun onCreate(savedInstanceState: Bundle?) { ... // Register for broadcasts when a device is discovered. val filter = IntentFilter(BluetoothDevice.ACTION_FOUND) registerReceiver(receiver, filter) } // Create a BroadcastReceiver for ACTION_FOUND. private val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val action: String = intent.action when(action) { BluetoothDevice.ACTION_FOUND -> { // Discovery has found a device. Get the BluetoothDevice // object and its info from the Intent. val device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) val deviceName = device.name val deviceHardwareAddress = device.address // MAC address } } } } override fun onDestroy() { super.onDestroy() ... // Don't forget to unregister the ACTION_FOUND receiver. unregisterReceiver(receiver) }
val discoverableIntent: Intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply { putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300) } startActivity(discoverableIntent)
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 // 只需要一个传入连接,因此在接受连接并获取 BluetoothSocket 之后,应用会立即将获取的 BluetoothSocket 传送到单独的线程、关闭 BluetoothServerSocket 并中断循环。 while (shouldLoop) { val socket: BluetoothSocket? = try { // 如果 accept() 返回 BluetoothSocket,则表示已连接套接字。因此,您不应像从客户端那样调用 connect()。 mmServerSocket?.accept() } catch (e: IOException) { Log.e(TAG, "Socket's accept() method failed", e) shouldLoop = false null } socket?.also { // 应用特定的 manageMyConnectedSocket() 方法旨在启动用于传输数据的线程。 manageMyConnectedSocket(it) mmServerSocket?.close() shouldLoop = false } } } // Closes the connect socket and causes the thread to finish. fun cancel() { // 通常,在完成传入连接的侦听后,您应立即关闭您的 BluetoothServerSocket。在此示例中,获取 BluetoothSocket 后会立即调用 close()。此外,您可能还希望在线程中提供一个公共方法,以便在需要停止侦听服务器套接字时关闭私有 BluetoothSocket。 try { mmServerSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the connect socket", e) } } }
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?.use { 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() 方法旨在启动用于传输数据的线程 manageMyConnectedSocket(socket) } } // Closes the client socket and causes the thread to finish. fun cancel() { try { // 使用完 BluetoothSocket 后,请务必调用 close()。这样,您便可立即关闭连接的套接字,并释放所有相关的内部资源。 mmSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the client socket", e) } } }
private const val TAG = "MY_APP_DEBUG_TAG" // Defines several constants used when transmitting messages between the // service and the UI. const val MESSAGE_READ: Int = 0 const val MESSAGE_WRITE: Int = 1 const val MESSAGE_TOAST: Int = 2 // ... (Add other message types here as needed.) class MyBluetoothService( // handler that gets info from Bluetooth service private val handler: Handler ) { private inner class ConnectedThread(private val mmSocket: BluetoothSocket) : Thread() { private val mmInStream: InputStream = mmSocket.inputStream private val mmOutStream: OutputStream = mmSocket.outputStream private val mmBuffer: ByteArray = ByteArray(1024) // mmBuffer store for the stream // 当构造函数获取必要的数据流后,线程会等待通过 InputStream 传入的数据。当 read(byte[]) 返回数据流中的数据时,将使用来自父类的 Handler 成员将数据发送到主 Activity。然后,线程会等待从 InputStream 中读取更多字节。 override fun run() { var numBytes: Int // bytes returned from read() // Keep listening to the InputStream until an exception occurs. while (true) { // Read from the InputStream. numBytes = try { mmInStream.read(mmBuffer) } catch (e: IOException) { Log.d(TAG, "Input stream was disconnected", e) break } // Send the obtained bytes to the UI activity. val readMsg = handler.obtainMessage( MESSAGE_READ, numBytes, -1, mmBuffer) readMsg.sendToTarget() } } // Call this from the main activity to send data to the remote device. // 发送传出数据不外乎从主 Activity 调用线程的 write() 方法,并传入要发送的字节。此方法会调用 write(byte[]),从而将数据发送到远程设备。如果在调用 write(byte[]) 时引发 IOException,则线程会发送一条 Toast 至主 Activity,向用户说明设备无法将给定的字节发送到另一台(连接的)设备。 fun write(bytes: ByteArray) { try { mmOutStream.write(bytes) } catch (e: IOException) { Log.e(TAG, "Error occurred when sending data", e) // Send a failure message back to the activity. val writeErrorMsg = handler.obtainMessage(MESSAGE_TOAST) val bundle = Bundle().apply { putString("toast", "Couldn't send data to the other device") } writeErrorMsg.data = bundle handler.sendMessage(writeErrorMsg) return } // Share the sent message with the UI activity. val writtenMsg = handler.obtainMessage( MESSAGE_WRITE, -1, -1, mmBuffer) writtenMsg.sendToTarget() } // Call this method from the main activity to shut down the connection. // 借助线程的 cancel() 方法,您可通过关闭 BluetoothSocket 随时终止连接。当您结束蓝牙连接的使用时,应始终调用此方法。 fun cancel() { try { mmSocket.close() } catch (e: IOException) { Log.e(TAG, "Could not close the connect socket", e) } } } }