Android 4.3(API 级别 18)为发挥核心作用的蓝牙低功耗 (BLE) 引入内置平台支持,并提供相应 API,方便应用发现设备、查询服务和传输信息。
常见用例包括:
- 在临近设备间传输少量数据。
- 与 Google Beacons 等近程传感器交互,以便为用户提供基于其当前位置的自定义体验。
与传统蓝牙不同,蓝牙低功耗 (BLE) 旨在提供显著降低的功耗。这使 Android 应用可与功率要求更严格的 BLE 设备(例如近程传感器、心率监测仪和健身设备)通信。
注意:当用户使用 BLE 将其设备与其他设备配对时,用户设备上的所有应用都可以访问在这两个设备间传输的数据。
因此,如果您的应用捕获敏感数据,您应实现应用层安全以保护此类数据的私密性。
关键术语和概念
以下是对 BLE 关键术语和概念的总结:
- 通用属性配置文件 (GATT) — GATT 配置文件是一种通用规范,内容针对在 BLE 链路上发送和接收称为“属性”的简短数据片段。目前所有低功耗应用配置文件均以 GATT 为基础。
- 蓝牙特别兴趣小组 (Bluetooth SIG) 为低功耗设备定义诸多配置文件。配置文件是描述设备如何在特定应用中工作的规范。请注意,一台设备可以实现多个配置文件。例如,一台设备可能包含心率监测仪和电池电量检测器。
- 属性协议 (ATT) — 属性协议 (ATT) 是 GATT 的构建基础,二者的关系也被称为 GATT/ATT。ATT 经过优化,可在 BLE 设备上运行。为此,该协议尽可能少地使用字节。每个属性均由通用唯一标识符 (UUID) 进行唯一标识,后者是用于对信息进行唯一标识的字符串 ID 的 128 位标准化格式。由 ATT 传输的属性采用特征和服务格式。
- 特征 — 特征包含一个值和 0 至多个描述特征值的描述符。您可将特征理解为类型,后者与类类似。
- 描述符 — 描述符是描述特征值的已定义属性。例如,描述符可指定人类可读的描述、特征值的可接受范围或特定于特征值的度量单位。
- Service — 服务是一系列特征。例如,您可能拥有名为“心率监测器”的服务,其中包括“心率测量”等特征。您可以在 bluetooth.org 上找到基于 GATT 的现有配置文件和服务的列表。
角色和职责
以下是 Android 设备与 BLE 设备交互时应用的角色和职责:
- 中央与外围。这适用于 BLE 连接本身。担任中央角色的设备进行扫描、寻找广播;外围设备发出广播。
- GATT 服务器与 GATT 客户端。这确定两个设备建立连接后如何相互通信。
要了解两者的区别,请想象您有一部 Android 手机和一个 Activity 追踪器,该 Activity 追踪器是一个 BLE 设备。手机支持中央角色;Activity 追踪器支持外围角色(要建立 BLE 连接必须具备这两个两个角色—如果两个设备都仅支持中央或外围角色,则无法相互通信)。
手机与 Activity 追踪器建立连接后,它们便开始相互传送 GATT 数据。根据它们传送数据的种类,其中一个会充当 GATT 服务器。例如,如果 Activity 追踪器要将传感器数据汇报给手机,那么 Activity 追踪器便充当服务器。如果 Activity 追踪器要从手机接收更新,那么手机便充当服务器。
在本文档中使用的示例中,Android 应用(在 Android 设备上运行)是 GATT 客户端。该应用从 GATT 服务器获取数据,后者是一个支持 Heart Rate Profile 的心率监测仪。但您也可以设计您的 Android 应用,使它充当 GATT 服务器角色。如需了解详情,请参阅 BluetoothGattServer
。
BLE 权限
要在您的应用中使用蓝牙功能,您必须声明 BLUETOOTH
蓝牙权限。您需要此权限才能执行任何蓝牙通信,例如请求连接、接受连接和传输数据等。
考虑到 LE 信标通常与位置相关联,您还须声明 ACCESS_FINE_LOCATION
权限。没有此权限,扫描将无法返回任何结果。
注意:如果您的应用适配 Android 9(API 级别 28)或更低版本,则您可以声明 ACCESS_COARSE_LOCATION
权限而非 ACCESS_FINE_LOCATION
权限。
如果您想让应用启动设备发现或操纵蓝牙设置,则您还须声明 BLUETOOTH_ADMIN
权限。注意:如果使用 BLUETOOTH_ADMIN
权限,则您必须拥有 BLUETOOTH
权限。
在应用清单文件中声明蓝牙权限。例如:
<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" />
如果您要声明您的应用仅适用于支持 BLE 的设备,请在应用清单中添加以下内容:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
如果您希望应用适用于不支持 BLE 的设备,则您应仍将此元素添加到应用清单中,但设置 required="false"
。然后您可以在运行时使用 PackageManager.hasSystemFeature()
确定 BLE 的可用性:
Kotlin
private fun PackageManager.missingSystemFeature(name: String): Boolean = !hasSystemFeature(name) ... packageManager.takeIf { it.missingSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) }?.also { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show() finish() }
Java
// Use this check to determine whether BLE is supported on the device. Then // you can selectively disable BLE-related features. if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
设置 BLE
在您的应用程序可以通过 BLE 进行通信之前,您需验证设备是否支持 BLE,如果支持,请确保该功能已启用。请注意,仅当 <uses-feature.../>
设置为 false 时才需要进行此检查。
如果不支持 BLE,则应妥善停用任何 BLE 功能。如果设备支持 BLE 但已停用此功能,则您可以请求用户在不离开应用的同时启用蓝牙。借助 BluetoothAdapter
,您可以分两步完成此设置。
- 获取
BluetoothAdapter
所有蓝牙 Activity 都需要
BluetoothAdapter
。BluetoothAdapter
代表设备自身的蓝牙适配器(蓝牙无线装置)。整个系统有一个蓝牙适配器,并且您的应用可使用此对象与之进行交互。以下代码段展示如何获取适配器。请注意,此方法使用getSystemService()
返回BluetoothManager
的实例,然后使用该实例获取适配器。Android 4.3(API 级别 18)引入BluetoothManager
:Kotlin
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager.adapter }
Java
private BluetoothAdapter bluetoothAdapter; ... // Initializes Bluetooth adapter. final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter();
- 启用蓝牙
下一步,您需要确保蓝牙已启用。调用
isEnabled()
,以检查当前是否已启用蓝牙。如果此方法返回 false,则表示蓝牙处于停用状态。以下代码段会检查蓝牙是否已启用。如果并未启用,则代码段会显示错误,提示用户前往“Settings”启用蓝牙:Kotlin
private val BluetoothAdapter.isDisabled: Boolean get() = !isEnabled ... // Ensures Bluetooth is available on the device and it is enabled. If not, // displays a dialog requesting user permission to enable Bluetooth. bluetoothAdapter?.takeIf { it.isDisabled }?.apply { val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) }
Java
// Ensures Bluetooth is available on the device and it is enabled. If not, // displays a dialog requesting user permission to enable Bluetooth. if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
注意:传递给
startActivityForResult(android.content.Intent, int)
的REQUEST_ENABLE_BT
常量是本地定义的整数(必须大于0),系统会在您的onActivityResult(int, int, android.content.Intent)
实现中将其作为requestCode
参数传回给您。
查找 BLE 设备
如要查找 BLE 设备,请使用 startLeScan()
方法。此方法将 BluetoothAdapter.LeScanCallback
作为参数。您必须实现此回调,因为这是返回扫描结果的方式。扫描非常耗电,因此您应遵循以下准则:
- 找到所需设备后,立即停止扫描。
- 绝对不进行循环扫描,并设置扫描时间限制。之前可用的设备可能已超出范围,继续扫描会耗尽电池电量。
以下代码段展示如何启动和停止扫描:
Kotlin
private const val SCAN_PERIOD: Long = 10000 /** * Activity for scanning and displaying available BLE devices. */ class DeviceScanActivity( private val bluetoothAdapter: BluetoothAdapter, private val handler: Handler ) : ListActivity() { private var mScanning: Boolean = false private fun scanLeDevice(enable: Boolean) { when (enable) { true -> { // Stops scanning after a pre-defined scan period. handler.postDelayed({ mScanning = false bluetoothAdapter.stopLeScan(leScanCallback) }, SCAN_PERIOD) mScanning = true bluetoothAdapter.startLeScan(leScanCallback) } else -> { mScanning = false bluetoothAdapter.stopLeScan(leScanCallback) } } } }
Java
/** * Activity for scanning and displaying available BLE devices. */ public class DeviceScanActivity extends ListActivity { private BluetoothAdapter bluetoothAdapter; private boolean mScanning; private Handler handler; // Stops scanning after 10 seconds. private static final long SCAN_PERIOD = 10000; ... private void scanLeDevice(final boolean enable) { if (enable) { // Stops scanning after a pre-defined scan period. handler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; bluetoothAdapter.stopLeScan(leScanCallback); } }, SCAN_PERIOD); mScanning = true; bluetoothAdapter.startLeScan(leScanCallback); } else { mScanning = false; bluetoothAdapter.stopLeScan(leScanCallback); } ... } ... }
如果您想扫描特定类型的外围设备,则可调用 startLeScan(UUID[], BluetoothAdapter.LeScanCallback)
,它会提供一组 UUID
对象,用于指定您的应用支持的 GATT 服务。
以下是 BluetoothAdapter.LeScanCallback
的实现,其为用于传递 BLE 扫描结果的界面:
Kotlin
val leDeviceListAdapter: LeDeviceListAdapter = ... private val leScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord -> runOnUiThread { leDeviceListAdapter.addDevice(device) leDeviceListAdapter.notifyDataSetChanged() } }
Java
private LeDeviceListAdapter leDeviceListAdapter; ... // Device scan callback. private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { leDeviceListAdapter.addDevice(device); leDeviceListAdapter.notifyDataSetChanged(); } }); } };
注意:您仅能扫描蓝牙 LE 设备或传统蓝牙设备,正如蓝牙概览中所述。您无法同时扫描蓝牙 LE 设备和传统蓝牙设备。
连接到 GATT 服务器
与 BLE 设备交互的第一步便是连接到 GATT 服务器。更具体地说,是连接到设备上的 GATT 服务器。如要连接到 BLE 设备上的 GATT 服务器,请使用 connectGatt()
方法。此方法采用三个参数:一个 Context
对象、autoConnect
(布尔值,指示是否在可用时自动连接到 BLE 设备),以及对 BluetoothGattCallback
的引用:
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
这将连接到由 BLE 设备托管的 GATT 服务器,并返回 BluetoothGatt
实例,然后您可使用该实例执行 GATT 客户端操作。调用方(Android 应用)是 GATT 客户端。BluetoothGattCallback
用于向客户端传递结果(例如连接状态),以及任何进一步的 GATT 客户端操作。
在本例中,BLE 应用提供一个 Activity (DeviceControlActivity
) 来连接、显示数据和显示设备支持的 GATT 服务和特征。根据用户输入,此 Activity 和一个名为 BluetoothLeService
的 Service
通信,该服务通过 Android BLE API 与 BLE 设备交互:
Kotlin
private val TAG = BluetoothLeService::class.java.simpleName private const val STATE_DISCONNECTED = 0 private const val STATE_CONNECTING = 1 private const val STATE_CONNECTED = 2 const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED" const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED" const val ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED" const val ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE" const val EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA" val UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT) // A service that interacts with the BLE device via the Android BLE API. class BluetoothLeService(private var bluetoothGatt: BluetoothGatt?) : Service() { private var connectionState = STATE_DISCONNECTED // Various callback methods defined by the BLE API. private val gattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int ) { val intentAction: String when (newState) { BluetoothProfile.STATE_CONNECTED -> { intentAction = ACTION_GATT_CONNECTED connectionState = STATE_CONNECTED broadcastUpdate(intentAction) Log.i(TAG, "Connected to GATT server.") Log.i(TAG, "Attempting to start service discovery: " + bluetoothGatt?.discoverServices()) } BluetoothProfile.STATE_DISCONNECTED -> { intentAction = ACTION_GATT_DISCONNECTED connectionState = STATE_DISCONNECTED Log.i(TAG, "Disconnected from GATT server.") broadcastUpdate(intentAction) } } } // New services discovered override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { when (status) { BluetoothGatt.GATT_SUCCESS -> broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED) else -> Log.w(TAG, "onServicesDiscovered received: $status") } } // Result of a characteristic read operation override fun onCharacteristicRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int ) { when (status) { BluetoothGatt.GATT_SUCCESS -> { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic) } } } } }
Java
// A service that interacts with the BLE device via the Android BLE API. public class BluetoothLeService extends Service { private final static String TAG = BluetoothLeService.class.getSimpleName(); private BluetoothManager bluetoothManager; private BluetoothAdapter bluetoothAdapter; private String bluetoothDeviceAddress; private BluetoothGatt bluetoothGatt; private int connectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); // Various callback methods defined by the BLE API. private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; connectionState = STATE_CONNECTED; broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); Log.i(TAG, "Attempting to start service discovery:" + bluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { intentAction = ACTION_GATT_DISCONNECTED; connectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); } } @Override // New services discovered public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override // Result of a characteristic read operation public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } ... }; ... }
当一个特定回调被触发时,它会调用相应的 broadcastUpdate()
辅助方法并向其传递操作。请注意,本部分的数据解析参照“蓝牙心率测量”配置文件规范执行:
Kotlin
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) } private fun broadcastUpdate(action: String, characteristic: BluetoothGattCharacteristic) { val intent = Intent(action) // This is special handling for the Heart Rate Measurement profile. Data // parsing is carried out as per profile specifications. when (characteristic.uuid) { UUID_HEART_RATE_MEASUREMENT -> { val flag = characteristic.properties val format = when (flag and 0x01) { 0x01 -> { Log.d(TAG, "Heart rate format UINT16.") BluetoothGattCharacteristic.FORMAT_UINT16 } else -> { Log.d(TAG, "Heart rate format UINT8.") BluetoothGattCharacteristic.FORMAT_UINT8 } } val heartRate = characteristic.getIntValue(format, 1) Log.d(TAG, String.format("Received heart rate: %d", heartRate)) intent.putExtra(EXTRA_DATA, (heartRate).toString()) } else -> { // For all other profiles, writes the data formatted in HEX. val data: ByteArray? = characteristic.value if (data?.isNotEmpty() == true) { val hexString: String = data.joinToString(separator = " ") { String.format("%02X", it) } intent.putExtra(EXTRA_DATA, "$data\n$hexString") } } } sendBroadcast(intent) }
Java
private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); } private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) { final Intent intent = new Intent(action); // This is special handling for the Heart Rate Measurement profile. Data // parsing is carried out as per profile specifications. if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) { format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG, "Heart rate format UINT16."); } else { format = BluetoothGattCharacteristic.FORMAT_UINT8; Log.d(TAG, "Heart rate format UINT8."); } final int heartRate = characteristic.getIntValue(format, 1); Log.d(TAG, String.format("Received heart rate: %d", heartRate)); intent.putExtra(EXTRA_DATA, String.valueOf(heartRate)); } else { // For all other profiles, writes the data formatted in HEX. final byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for(byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); } } sendBroadcast(intent); }
回到 DeviceControlActivity
,这些事件由 BroadcastReceiver
处理:
Kotlin
// Handles various events fired by the Service. // ACTION_GATT_CONNECTED: connected to a GATT server. // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. // ACTION_DATA_AVAILABLE: received data from the device. This can be a // result of read or notification operations. private val gattUpdateReceiver = object : BroadcastReceiver() { private lateinit var bluetoothLeService: BluetoothLeService override fun onReceive(context: Context, intent: Intent) { val action = intent.action when (action){ ACTION_GATT_CONNECTED -> { connected = true updateConnectionState(R.string.connected) (context as? Activity)?.invalidateOptionsMenu() } ACTION_GATT_DISCONNECTED -> { connected = false updateConnectionState(R.string.disconnected) (context as? Activity)?.invalidateOptionsMenu() clearUI() } ACTION_GATT_SERVICES_DISCOVERED -> { // Show all the supported services and characteristics on the // user interface. displayGattServices(bluetoothLeService.getSupportedGattServices()) } ACTION_DATA_AVAILABLE -> { displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)) } } } }
Java
// Handles various events fired by the Service. // ACTION_GATT_CONNECTED: connected to a GATT server. // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. // ACTION_DATA_AVAILABLE: received data from the device. This can be a // result of read or notification operations. private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { connected = true; updateConnectionState(R.string.connected); invalidateOptionsMenu(); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { connected = false; updateConnectionState(R.string.disconnected); invalidateOptionsMenu(); clearUI(); } else if (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // Show all the supported services and characteristics on the // user interface. displayGattServices(bluetoothLeService.getSupportedGattServices()); } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); } } };
读取 BLE 属性
当您的 Android 应用成功连接到 GATT 服务器并发现服务后,应用便可在支持的位置读取和写入属性。例如,以下代码段遍历服务器的服务和特征,并在界面中将其显示出来:
Kotlin
class DeviceControlActivity : Activity() { // Demonstrates how to iterate through the supported GATT // Services/Characteristics. // In this sample, we populate the data structure that is bound to the // ExpandableListView on the UI. private fun displayGattServices(gattServices: List<BluetoothGattService>?) { if (gattServices == null) return var uuid: String? val unknownServiceString: String = resources.getString(R.string.unknown_service) val unknownCharaString: String = resources.getString(R.string.unknown_characteristic) val gattServiceData: MutableList<HashMap<String, String>> = mutableListOf() val gattCharacteristicData: MutableList<ArrayList<HashMap<String, String>>> = mutableListOf() mGattCharacteristics = mutableListOf() // Loops through available GATT Services. gattServices.forEach { gattService -> val currentServiceData = HashMap<String, String>() uuid = gattService.uuid.toString() currentServiceData[LIST_NAME] = SampleGattAttributes.lookup(uuid, unknownServiceString) currentServiceData[LIST_UUID] = uuid gattServiceData += currentServiceData val gattCharacteristicGroupData: ArrayList<HashMap<String, String>> = arrayListOf() val gattCharacteristics = gattService.characteristics val charas: MutableList<BluetoothGattCharacteristic> = mutableListOf() // Loops through available Characteristics. gattCharacteristics.forEach { gattCharacteristic -> charas += gattCharacteristic val currentCharaData: HashMap<String, String> = hashMapOf() uuid = gattCharacteristic.uuid.toString() currentCharaData[LIST_NAME] = SampleGattAttributes.lookup(uuid, unknownCharaString) currentCharaData[LIST_UUID] = uuid gattCharacteristicGroupData += currentCharaData } mGattCharacteristics += charas gattCharacteristicData += gattCharacteristicGroupData } } }
Java
public class DeviceControlActivity extends Activity { ... // Demonstrates how to iterate through the supported GATT // Services/Characteristics. // In this sample, we populate the data structure that is bound to the // ExpandableListView on the UI. private void displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources(). getString(R.string.unknown_service); String unknownCharaString = getResources(). getString(R.string.unknown_characteristic); ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); // Loops through available GATT Services. for (BluetoothGattService gattService : gattServices) { HashMap<String, String> currentServiceData = new HashMap<String, String>(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes. lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>(); // Loops through available Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap<String, String> currentCharaData = new HashMap<String, String>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } ... } ... }
接收 GATT 通知
BLE 应用通常会要求在设备上的特定特征发生变化时收到通知。以下代码段展示如何使用 setCharacteristicNotification()
方法设置特征的通知:
Kotlin
lateinit var bluetoothGatt: BluetoothGatt lateinit var characteristic: BluetoothGattCharacteristic var enabled: Boolean = true ... bluetoothGatt.setCharacteristicNotification(characteristic, enabled) val uuid: UUID = UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG) val descriptor = characteristic.getDescriptor(uuid).apply { value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE } bluetoothGatt.writeDescriptor(descriptor)
Java
private BluetoothGatt bluetoothGatt; BluetoothGattCharacteristic characteristic; boolean enabled; ... bluetoothGatt.setCharacteristicNotification(characteristic, enabled); ... BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); bluetoothGatt.writeDescriptor(descriptor);
为某个特征启用通知后,如果远程设备上的特征发生更改,则会触发 onCharacteristicChanged()
回调:
Kotlin
// Characteristic notification override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic) }
Java
@Override // Characteristic notification public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); }
关闭客户端应用
当应用完成对 BLE 设备的使用后,其应调用 close()
,以便系统可以适当地释放资源:
Kotlin
fun close() { bluetoothGatt?.close() bluetoothGatt = null }
Java
public void close() { if (bluetoothGatt == null) { return; } bluetoothGatt.close(); bluetoothGatt = null; }