与 BLE 设备互动的第一步是连接到该设备。更具体地说,是连接到设备上的 GATT 服务器。如需连接到 BLE 设备上的 GATT 服务器,请使用 connectGatt() 方法。此方法可接受三个参数:Context 对象、autoConnect(一个布尔值,用于指示是否在 BLE 设备可用时立即自动连接到该设备)以及对 BluetoothGattCallback 的引用:
var bluetoothGatt: BluetoothGatt? = null // ... bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
此方法会连接到 BLE 设备托管的 GATT 服务器,并返回一个 BluetoothGatt 实例,然后您可以使用该实例执行 GATT 客户端操作。调用方(Android 应用)是 GATT 客户端。BluetoothGattCallback 用于向客户端提供结果(例如连接状态)以及任何进一步的 GATT 客户端操作。
设置绑定服务
在以下示例中,BLE 应用提供了一个 activity (DeviceControlActivity),用于连接到蓝牙设备、显示设备数据,以及显示设备支持的 GATT 服务和特征。根据用户输入,此 activity 会与名为 BluetoothLeService 的 Service 通信,后者会使用 BLE API 与 BLE 设备交互。绑定服务可让 activity 连接到 BluetoothLeService 并调用函数来连接到设备,从而处理此通信。BluetoothLeService 需要一个 Binder 实现,以便为 activity 提供对服务的访问权限。
class BluetoothLeService : Service() { private val binder = LocalBinder() override fun onBind(intent: Intent): IBinder? { return binder } inner class LocalBinder : Binder() { fun getService(): BluetoothLeService { return this@BluetoothLeService } } }
该 activity 可以使用 bindService() 启动服务,并传入一个 Intent 来启动服务、一个 ServiceConnection 实现来监听连接和断开连接事件,以及一个用于指定其他连接选项的标志。
class DeviceControlActivity : AppCompatActivity() { private var bluetoothService: BluetoothLeService? = null // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> // call functions on service to check connection and connect to devices } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.gatt_services_characteristics) val gattServiceIntent = Intent(this, BluetoothLeService::class.java) bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE) } }
设置 BluetoothAdapter
activity 绑定到服务后,服务需要访问 BluetoothAdapter。它应检查设备上是否有适配器。如需详细了解 BluetoothAdapter,请参阅设置蓝牙。以下示例将此设置代码封装在 initialize() 函数中,该函数会返回一个表示成功的 Boolean 值。
private const val TAG = "BluetoothLeService" class BluetoothLeService : Service() { private var bluetoothAdapter: BluetoothAdapter? = null fun initialize(): Boolean { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() if (bluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter.") return false } return true } // ... }
该 activity 会在其 ServiceConnection 实现中调用此函数。
如何处理 initialize() 函数返回的 false 值取决于您的应用。您可以向用户显示一条出错提示,指明当前设备不支持蓝牙操作,或停用任何需要蓝牙才能运行的功能。在以下示例中,activity 会调用 finish(),将用户送回上一个屏幕。
class DeviceControlActivity : AppCompatActivity() { // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } // ... }
连接到设备
BluetoothLeService 实例初始化后,便可以连接到 BLE 设备。该 activity 需要将设备地址发送到服务,以便服务可以发起连接。该服务首先在 BluetoothAdapter 上调用 getRemoteDevice() 以访问设备。如果适配器无法找到具有该地址的设备,getRemoteDevice() 会抛出 IllegalArgumentException。
fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address.") return false } // connect to the GATT server on the device return true } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } }
DeviceControlActivity 会在服务初始化后调用此 connect() 函数。该 activity 需要传入 BLE 设备的地址。在以下示例中,设备地址作为 intent extra 传递给 activity。
// Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection deviceAddress?.let { bluetooth.connect(it) } } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } }
声明 GATT 回调
在 activity 告知服务要连接到哪个设备,并且服务连接到该设备后,服务需要连接到 BLE 设备上的 GATT 服务器。此连接需要 BluetoothGattCallback 才能接收有关连接状态、服务发现、特征读取和特征通知的通知。
本文档重点介绍连接状态通知。如需了解如何发现服务、读取特征和请求特征通知,请参阅传输 BLE 数据。
当与设备的 GATT 服务器的连接发生变化时,系统会触发 onConnectionStateChange() 函数。在以下示例中,回调在 Service 类中定义,以便在服务连接到 BluetoothDevice 后,可以与该类一起使用。
private val bluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server } } }
连接到 GATT 服务
声明 BluetoothGattCallback 后,服务可以使用 connect() 函数中的 BluetoothDevice 对象连接到设备上的 GATT 服务。
该服务会调用 connectGatt() 函数。这需要一个 Context 对象、一个 autoConnect 布尔值标志和 BluetoothGattCallback。在此示例中,应用直接连接到 BLE 设备,因此为 autoConnect 传递了 false。
该服务还会添加 BluetoothGatt 属性,以便在不再需要连接时关闭连接。
class BluetoothLeService : Service() { // ... private var bluetoothGatt: BluetoothGatt? = null // ... fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback) return true } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address. Unable to connect.") return false } } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } } }
广播更新
当服务连接到 GATT 服务器或与其断开连接时,需要向 activity 通知新状态。您可以通过多种方式来实现此目的。 以下示例使用广播将信息从服务发送到 activity。
该服务会声明一个函数来广播新状态。此函数接受一个操作字符串,该字符串会传递给 Intent 对象,然后再广播到系统。
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
广播函数就位后,服务会在 BluetoothGattCallback 中使用该函数来发送与 GATT 服务器的连接状态相关的信息。该服务会声明表示 Intent 操作和服务当前连接状态的常量。
class BluetoothLeService : Service() { private var connectionState = STATE_DISCONNECTED private val bluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server connectionState = STATE_CONNECTED broadcastUpdate(ACTION_GATT_CONNECTED) } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server connectionState = STATE_DISCONNECTED broadcastUpdate(ACTION_GATT_DISCONNECTED) } } } // ... companion object { const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED" const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED" private const val STATE_DISCONNECTED = 0 private const val STATE_CONNECTED = 2 } }
监听活动更新
在服务广播连接更新后,activity 需要实现 BroadcastReceiver。在设置 activity 时注册此接收器,并在 activity 离开屏幕时取消注册。通过监听来自服务的事件,activity 能够根据与 BLE 设备的当前连接状态更新界面。
class DeviceControlActivity : AppCompatActivity() { private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { BluetoothLeService.ACTION_GATT_CONNECTED -> { connected = true updateConnectionState(R.string.connected) } BluetoothLeService.ACTION_GATT_DISCONNECTED -> { connected = false updateConnectionState(R.string.disconnected) } } } } override fun onResume() { super.onResume() registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()) bluetoothService?.let { service -> deviceAddress?.let { address -> val result = service.connect(address) Log.d(TAG, "Connect request result=$result") } } } override fun onPause() { super.onPause() unregisterReceiver(gattUpdateReceiver) } private fun makeGattUpdateIntentFilter(): IntentFilter { return IntentFilter().apply { addAction(BluetoothLeService.ACTION_GATT_CONNECTED) addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED) } } }
在传输 BLE 数据中,BroadcastReceiver 还用于传达服务发现以及来自设备的特征数据。
关闭 GATT 连接
处理蓝牙连接时,一个重要步骤是在连接完成后关闭连接。为此,请对 BluetoothGatt 对象调用 close() 函数。在以下示例中,服务持有对 BluetoothGatt 的引用。当 activity 与服务解除绑定时,连接会关闭,以避免耗尽设备电池电量。
class BluetoothLeService : Service() { // ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }