Android NDK开发详解连接性之传输 BLE 数据
传输 BLE 数据
连接到 BLE GATT 服务器后,您可以通过该连接了解设备上提供哪些服务、从设备查询数据,并在特定 GATT 特性发生变化时请求通知。
发现服务
在 BLE 设备上连接到 GATT 服务器后,首先要执行服务发现。它提供了有关远程设备上可用服务以及服务特征及其描述符的信息。在以下示例中,服务成功连接到设备(通过对 BluetoothGattCallback 的 onConnectionStateChange() 函数的适当调用来指示)后,discoverServices() 函数会从 BLE 设备查询信息。
该服务需要替换 BluetoothGattCallback 中的 onServicesDiscovered() 函数。当设备报告其可用服务时,系统会调用此函数。
Kotlin
class BluetoothLeService : Service() {
...
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
broadcastUpdate(ACTION_GATT_CONNECTED)
connectionState = STATE_CONNECTED
// Attempts to discover services after successful connection.
bluetoothGatt?.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// disconnected from the GATT Server
broadcastUpdate(ACTION_GATT_DISCONNECTED)
connectionState = STATE_DISCONNECTED
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED)
} else {
Log.w(BluetoothService.TAG, "onServicesDiscovered received: $status")
}
}
}
...
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"
const val ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"
private const val STATE_DISCONNECTED = 0
private const val STATE_CONNECTED = 2
}
Java
class BluetoothService extends Service {
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// successfully connected to the GATT Server
connectionState = STATE_CONNECTED;
broadcastUpdate(ACTION_GATT_CONNECTED);
// Attempts to discover services after successful connection.
bluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// disconnected from the GATT Server
connectionState = STATE_DISCONNECTED;
broadcastUpdate(ACTION_GATT_DISCONNECTED);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
};
}
服务使用广播来通知 activity。发现服务后,服务可以调用 getServices() 来获取报告的数据。
Kotlin
class BluetoothLeService : Service() {
...
fun getSupportedGattServices(): List<BluetoothGattService?>? {
return bluetoothGatt?.services
}
}
Java
class BluetoothService extends Service {
...
public List<BluetoothGattService> getSupportedGattServices() {
if (bluetoothGatt == null) return null;
return bluetoothGatt.getServices();
}
}
然后,activity 可以在收到广播 intent 时调用此函数,以表明服务发现已完成。
Kotlin
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)
}
BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED -> {
// Show all the supported services and characteristics on the user interface.
displayGattServices(bluetoothService?.getSupportedGattServices())
}
}
}
}
}
Java
class DeviceControlsActivity extends AppCompatActivity {
...
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);
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
connected = false;
updateConnectionState(R.string.disconnected);
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
// Show all the supported services and characteristics on the user interface.
displayGattServices(bluetoothService.getSupportedGattServices());
}
}
};
}
读取 BLE 特征
在您的应用连接到 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 服务提供了您可以从设备读取的特征列表。如需查询数据,请对 BluetoothGatt 调用 readCharacteristic() 函数,并传入您要读取的 BluetoothGattCharacteristic。
Kotlin
class BluetoothLeService : Service() {
...
fun readCharacteristic(characteristic: BluetoothGattCharacteristic) {
bluetoothGatt?.let { gatt ->
gatt.readCharacteristic(characteristic)
} ?: run {
Log.w(TAG, "BluetoothGatt not initialized")
Return
}
}
}
Java
class BluetoothService extends Service {
...
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (bluetoothGatt == null) {
Log.w(TAG, "BluetoothGatt not initialized");
return;
}
bluetoothGatt.readCharacteristic(characteristic);
}
}
在此示例中,服务实现了一个函数来调用 readCharacteristic()。这是一个异步调用。结果会被发送到 BluetoothGattCallback 函数 onCharacteristicRead()。
Kotlin
class BluetoothLeService : Service() {
...
private val bluetoothGattCallback = object : BluetoothGattCallback() {
...
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(BluetoothService.ACTION_DATA_AVAILABLE, characteristic)
}
}
}
}
Java
class BluetoothService extends Service {
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
...
@Override
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, 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 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);
}
接收 GATT 通知
当设备上的特定特性发生变化时,BLE 应用通常会要求接收通知。在以下示例中,服务实现了一个函数来调用 setCharacteristicNotification() 方法:
Kotlin
class BluetoothLeService : Service() {
...
fun setCharacteristicNotification(
characteristic: BluetoothGattCharacteristic,
enabled: Boolean
) {
bluetoothGatt?.let { gatt ->
gatt.setCharacteristicNotification(characteristic, enabled)
// This is specific to Heart Rate Measurement.
if (BluetoothService.UUID_HEART_RATE_MEASUREMENT == characteristic.uuid) {
val descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG))
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
} ?: run {
Log.w(BluetoothService.TAG, "BluetoothGatt not initialized")
}
}
}
Java
class BluetoothService extends Service {
...
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enabled) {
if (bluetoothGatt == null) {
Log.w(TAG, "BluetoothGatt not initialized");
Return;
}
bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
// This is specific to Heart Rate Measurement.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
}
}
为某个特征启用通知后,如果远程设备上的特征发生更改,会触发 onCharacteristicChanged() 回调:
Kotlin
class BluetoothLeService : Service() {
...
private val bluetoothGattCallback = object : BluetoothGattCallback() {
...
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)
}
}
}
Java
class BluetoothService extends Service {
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
...
@Override
public void onCharacteristicChanged(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic
) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
}
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2023-11-07。