当前位置: 首页 > news >正文

安卓低功耗蓝牙BLE官方开发例程(JAVA)翻译注释版

官方原文链接

https://developer.android.com/develop/connectivity/bluetooth/ble/ble-overview?hl=zh-cn


目录

低功耗蓝牙 

基础知识

关键术语和概念

角色和职责

查找 BLE 设备 

连接到 GATT 服务器 

设置绑定服务

设置 BluetoothAdapter

连接到设备

声明 GATT 回调

连接到 GATT 服务

广播动态

在活动中监听更新

关闭 GATT 连接

传输 BLE 数据 

发现服务

读取 BLE 特性

接收 GATT 通知

在后台交流 

查找设备

在后台

连接到设备

在后台

保持与设备的连接

在应用之间切换时

收听外围设备通知时




低功耗蓝牙 

bookmark_border

  • 本页内容
  • 基础知识
  • 关键术语和概念
    • 角色和职责

Android 为发挥核心作用的蓝牙低功耗 (BLE) 提供内置平台支持,并提供可供应用用于发现设备、查询服务和传输信息的 API。

常见用例包括:

  • 在临近设备间传输少量数据。
  • 与近程传感器交互,以便为用户提供基于其当前位置的自定义体验。

与传统蓝牙不同,BLE 旨在显著降低功耗。这样一来,应用便可与功率要求更严格的 BLE 设备(例如近程传感器、心率监测器和健身设备)进行通信。

注意:当用户使用 BLE 将其设备与其他设备配对时,用户设备上的所有应用都可以访问在这两个设备间传输的数据。

因此,如果您的应用捕获敏感数据,您应实现应用层安全以保护此类数据的私密性。

基础知识

为了让支持 BLE 的设备能够在彼此之间传输数据,它们必须先形成通信通道。若要使用 Bluetooth LE API,您需要在清单文件中声明多项权限。您的应用获得使用蓝牙的权限后,需要访问 BluetoothAdapter 并确定设备上是否支持蓝牙。如果支持蓝牙,设备将扫描附近的 BLE 设备。找到设备后,通过连接到 BLE 设备上的 GATT 服务器来发现 BLE 设备的功能。建立连接后,可以根据可用服务和特性与已连接的设备传输数据。

关键术语和概念

以下是对 BLE 关键术语和概念的总结:

  • 通用属性配置文件 (GATT)

    GATT 配置文件是一种通用规范,内容针对在 BLE 链路上发送和接收称为“属性”的简短数据片段。所有最新的 BLE 应用配置文件都基于 GATT。如需了解详情,请查看 GitHub 上的 Android BluetoothLeGatt 示例。

  • 配置文件

    蓝牙特别兴趣小组 (Bluetooth SIG) 为 BLE 设备定义了许多配置文件。配置文件是描述设备如何在特定应用中工作的规范。请注意,一台设备可以实现多个配置文件。例如,一台设备可能包含心率监测仪和电池电量检测器。

  • 属性协议 (ATT)

    GATT 以属性协议 (ATT) 为基础构建而成。二者的关系也被称为 GATT/ATT。ATT 经过优化,可在 BLE 设备上运行。为此,该协议尽可能少地使用字节。每个属性均由通用唯一标识符 (UUID) 进行唯一标识,后者是用于对信息进行唯一标识的字符串 ID 的 128 位标准化格式。ATT 传输的属性采用特征服务格式。

  • 特征

    特征包含一个值和 0 至多个描述特征值的描述符。您可将特征理解为类型,后者与类类似。

  • 描述符

    描述符是描述特征值的已定义属性。例如,描述符可指定人类可读的描述、特征值的可接受范围或特定于特征值的度量单位。

  • 服务

    服务是一系列特征。例如,您可能有一项名为“心率监测器”的服务,其中包含“心率测量”等特征。您可以在 bluetooth.org 上找到基于 GATT 的现有配置文件和服务的列表。

角色和职责

当设备与 BLE 设备交互时,角色和职责会以两种不同的方式划分:

  • 中央与外围。这适用于 BLE 连接本身:担任中央角色的设备进行扫描、寻找广播;外围设备发出广播。如果两个设备都仅支持外围角色,则无法相互通信;如果两个设备都仅支持中央角色,也无法相互通信。

  • GATT 服务器与 GATT 客户端。这决定两个设备建立连接后如何相互通信。处于客户端角色的设备发送数据请求,处于服务器角色的设备执行这些请求。

如需了解中心-外围设备角色划分与服务器-客户端角色划分的区别,请考虑以下示例:您有一台 Android 手机和一台支持 BLE 的活动追踪器,该追踪器会将传感器数据报告回手机。

  • 手机(中央设备)会主动扫描 BLE 设备。活动追踪器(即外围设备)会进行广告宣传,并等待收到连接请求。

  • 手机与活动追踪器建立连接后,它们便开始相互传送 GATT 元数据。在本例中,手机上运行的应用会发送数据请求,因此它充当 GATT 客户端。活动追踪器会执行这些请求,因此它充当 GATT 服务器

应用的另一种设计可能使手机扮演 GATT 服务器角色。如需了解详情,请参阅 BluetoothGattServer。




查找 BLE 设备 

bookmark_border

要查找 BLE 设备,您可以使用 startScan() 方法。此方法采用 ScanCallback 作为参数。 您必须实现此回调,因为这是返回扫描结果的方式。 由于扫描非常耗电,因此您应该注意以下事项: 指南:

  • 找到所需设备后,立即停止扫描。
  • 永不循环扫描,并始终设置扫描时间限制。具有如下特征的设备: 可能已经超出有效范围, 很耗电。

在以下示例中,BLE 应用提供了一个 activity (DeviceScanActivity),用于扫描可用的蓝牙 LE 设备和显示屏 向用户列出它们以下代码段展示了如何启动和停止 扫描:

// 定义一个 BluetoothLeScanner 实例,用于执行 BLE 扫描。
private BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();// 定义一个布尔变量来跟踪当前是否正在进行扫描。
private boolean scanning;// 创建一个 Handler 实例,用于在主线程上发布消息或运行代码块。
private Handler handler = new Handler();// 定义常量 SCAN_PERIOD 为 10 秒(10000 毫秒),表示扫描持续的时间。
private static final long SCAN_PERIOD = 10000;/*** 开始或停止 BLE 设备的扫描。*/
private void scanLeDevice() {// 如果当前没有进行扫描,则开始新的扫描。if (!scanning) {// 在指定的扫描周期后,通过 Handler 延迟执行一个 Runnable,以停止扫描。handler.postDelayed(new Runnable() {@Overridepublic void run() {// 将扫描状态设置为 false,表示不再扫描。scanning = false;// 调用 stopScan 方法并传入 leScanCallback,停止当前的 BLE 扫描。bluetoothLeScanner.stopScan(leScanCallback);}}, SCAN_PERIOD); // 设置延迟时间,即扫描周期。// 更新扫描状态为 true,表示正在扫描。scanning = true;// 调用 startScan 方法并传入 leScanCallback,开始 BLE 扫描。bluetoothLeScanner.startScan(leScanCallback);} else {// 如果当前正在进行扫描,则停止扫描。scanning = false;// 调用 stopScan 方法并传入 leScanCallback,立即停止当前的 BLE 扫描。bluetoothLeScanner.stopScan(leScanCallback);}
}
 

注意 : BluetoothLeScanner是 只能通过 BluetoothAdapter(如果蓝牙) 目前在设备上处于启用状态。如果未启用蓝牙,则 getBluetoothLeScanner() 会返回 null。

要仅扫描特定类型的外围设备,您可以改为调用 startScan(List<ScanFilter>, ScanSettings, ScanCallback)、 提供一系列 ScanFilter 对象,这些对象限制了扫描要查找的设备, ScanSettings 对象, 指定有关扫描的参数。

以下代码示例是 ScanCallback、 该接口是用于提供 BLE 扫描结果的接口。找到结果后, 它们会添加到 DeviceScanActivity 中的列表适配器中,以显示给 用户。

// 创建一个 LeDeviceListAdapter 实例,用于管理 BLE 设备列表的适配器。
private LeDeviceListAdapter leDeviceListAdapter = new LeDeviceListAdapter();// 定义一个 ScanCallback 的匿名内部类实例,作为 BLE 设备扫描的回调接口。
private ScanCallback leScanCallback =new ScanCallback() {// 当扫描到一个新的设备时,系统会调用此方法。@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result); // 调用父类的方法,确保默认行为被执行。// 将扫描结果中的 BluetoothDevice 添加到适配器中。leDeviceListAdapter.addDevice(result.getDevice());// 通知适配器数据集已更改,以便更新 UI 显示最新的设备列表。leDeviceListAdapter.notifyDataSetChanged();}};

连接到 GATT 服务器 

bookmark_border

  • 本页内容
  • 设置绑定服务
  • 设置 BluetoothAdapter
  • 连接到设备
  • 声明 GATT 回调
  • 连接到 GATT 服务

与 BLE 设备交互的第一步是连接该设备。更多 具体来说就是连接到设备上的 GATT 服务器。关联 GATT 服务器,请使用 connectGatt() 方法。此方法采用三个参数: Context 对象,autoConnect(一个布尔值) 指示是否在 BLE 设备完成后立即自动连接到 可用),并且引用了 BluetoothGattCallback:

KotlinJava

bluetoothGatt = device.connectGatt(this, false, gattCallback);
// 建立与指定 BLE 设备的 GATT 连接。
bluetoothGatt = device.connectGatt(this, false, gattCallback);

这将连接到由 BLE 设备托管的 GATT 服务器,并返回 BluetoothGatt 实例, 然后,您可以使用它执行 GATT 客户端操作。调用方(Android 应用) 是 GATT 客户端通过 BluetoothGattCallback 用于向客户端传递结果,例如 连接状态,以及任何进一步的 GATT 客户端操作。

设置绑定服务

在以下示例中,BLE 应用提供了一个 activity (DeviceControlActivity) 可连接到蓝牙设备、显示设备数据、 并显示设备支持的 GATT 服务和特征。位于 该活动会与 Service 调用了 BluetoothLeService, 通过 BLE API 与 BLE 设备进行交互。沟通是 使用绑定服务执行,这样, 要连接到 BluetoothLeService 并调用函数的 activity 连接到设备BluetoothLeService需要 Binder 实现,可提供对 服务。

// 定义一个名为 BluetoothLeService 的类,它继承自 Service。
class BluetoothLeService extends Service {// 创建一个 LocalBinder 类的实例,并将其赋值给名为 binder 的成员变量。private Binder binder = new LocalBinder();// 重写 onBind 方法,它是 Service 类的一部分。当有组件(如 Activity)绑定到服务时调用此方法。@Nullable@Overridepublic IBinder onBind(Intent intent) {// 返回 binder 对象,使调用者可以通过它与服务进行交互。return binder;}// 定义一个内部类 LocalBinder,它扩展了 Binder 类。class LocalBinder extends Binder {// 提供一个公共方法 getService,用于返回当前 BluetoothLeService 实例。public BluetoothLeService getService() {// 返回 BluetoothLeService 的当前实例,允许客户端直接访问其公共方法。return BluetoothLeService.this;}}
}

activity 可以使用以下代码启动服务: bindService()、 传入 Intent 以启动 服务,即ServiceConnection 用于监听连接和断开连接事件的实现,以及一个用于 以指定其他连接选项。

// 定义一个名为 DeviceControlActivity 的类,它继承自 AppCompatActivity。
class DeviceControlActivity extends AppCompatActivity {// 声明一个 BluetoothLeService 类型的成员变量 bluetoothService,用于与蓝牙服务交互。private BluetoothLeService bluetoothService;// 创建一个 ServiceConnection 实例,用于监听服务绑定和解绑事件。private ServiceConnection serviceConnection = new ServiceConnection() {// 当服务成功绑定时调用此方法。@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 将传入的 IBinder 转换为 LocalBinder,并通过它获取 BluetoothLeService 实例。bluetoothService = ((LocalBinder) service).getService();if (bluetoothService != null) {// 如果成功获取到服务实例,可以在这里调用服务上的方法来检查连接状态或连接设备。}}// 当服务断开连接时调用此方法。@Overridepublic void onServiceDisconnected(ComponentName name) {// 将 bluetoothService 设置为 null,表示服务已断开。bluetoothService = null;}};// 重写 onCreate 方法,它是 Activity 生命周期的一部分,在 Activity 第一次创建时调用。@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState); // 调用父类的 onCreate 方法。setContentView(R.layout.gatt_services_characteristics); // 设置 Activity 的布局文件。// 创建一个 Intent 对象,用于启动 BluetoothLeService 服务。Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);// 使用 bindService 方法将 Activity 绑定到 BluetoothLeService。// 参数 serviceConnection 是回调接口,用于处理服务连接的状态变化。// Context.BIND_AUTO_CREATE 标志表示如果服务未运行,则自动创建该服务。bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);}
}

设置 BluetoothAdapter

服务被绑定后,需要访问 BluetoothAdapter。它应该 检查适配器在设备上是否可用。请参阅设置 蓝牙,详细了解 BluetoothAdapter。以下示例将此设置代码封装在 initialize() 函数,用于返回表示成功的 Boolean 值。

KotlinJava

// 定义一个名为 BluetoothLeService 的类,它继承自 Service。
class BluetoothLeService extends Service {// 定义一个静态常量字符串 TAG,用于日志记录的标签。这有助于在日志中识别来自此服务的消息。public static final String TAG = "BluetoothLeService";// 声明一个 BluetoothAdapter 类型的私有成员变量 bluetoothAdapter,用于管理蓝牙适配器(本地蓝牙硬件)。private BluetoothAdapter bluetoothAdapter;// 定义一个公共方法 initialize(),用于初始化蓝牙适配器并检查是否可用。public boolean initialize() {// 获取默认的蓝牙适配器实例,并将其赋值给 bluetoothAdapter 成员变量。bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();// 检查是否成功获取到蓝牙适配器。if (bluetoothAdapter == null) {// 如果没有找到蓝牙适配器,则记录错误信息,并返回 false 表示初始化失败。Log.e(TAG, "Unable to obtain a BluetoothAdapter.");return false;}// 如果成功获取到了蓝牙适配器,则返回 true 表示初始化成功。return true;}...
}

此 activity 将在其 ServiceConnection 实现中调用此函数。 处理 initialize() 函数的 false 返回值取决于 应用。您可以向用户显示一条错误消息 当前设备不支持蓝牙操作或停用任何功能 需要蓝牙才能工作在以下示例中, 系统会对 activity 调用 finish() 可将用户带回上一屏幕。

// 定义一个名为 DeviceControlsActivity 的类,它继承自 AppCompatActivity。
class DeviceControlsActivity extends AppCompatActivity {// 创建一个 ServiceConnection 实例,用于监听服务绑定和解绑事件。private ServiceConnection serviceConnection = new ServiceConnection() {// 当服务成功绑定时调用此方法。@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 将传入的 IBinder 转换为 LocalBinder,并通过它获取 BluetoothLeService 实例。bluetoothService = ((LocalBinder) service).getService();// 检查是否成功获取到蓝牙服务实例。if (bluetoothService != null) {// 尝试初始化蓝牙服务。if (!bluetoothService.initialize()) {// 如果初始化失败,记录错误信息并结束 Activity。Log.e(TAG, "Unable to initialize Bluetooth");finish(); // 结束当前 Activity。}// 在这里可以执行设备连接逻辑,例如尝试连接到特定的 BLE 设备。// perform device connection}}// 当服务意外断开连接时调用此方法。@Overridepublic void onServiceDisconnected(ComponentName name) {// 将 bluetoothService 设置为 null,表示服务已断开。bluetoothService = null;}};...
}
 

连接到设备

初始化 BluetoothLeService 实例后,它可以连接到 BLE 设备。activity 需要将设备地址发送给服务,然后才能 发起连接。该服务将首先调用 getRemoteDevice() 在 BluetoothAdapter 上访问该设备。如果适配器找不到 在具有该地址的设备时,getRemoteDevice() 会抛出一个 IllegalArgumentException。

public boolean connect(final String address) {// 检查蓝牙适配器是否已初始化以及提供的地址是否为空。if (bluetoothAdapter == null || address == null) {// 如果蓝牙适配器未初始化或地址未指定,则记录警告信息并返回 false 表示连接失败。Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");return false;}try {// 使用给定的地址获取远程蓝牙设备对象。这一步骤可能会抛出 IllegalArgumentException,// 如果提供的地址格式不正确或不存在对应的蓝牙设备。final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);} catch (IllegalArgumentException exception) {// 捕获 IllegalArgumentException 异常,表示使用提供的地址找不到设备。Log.w(TAG, "Device not found with provided address.");return false; // 返回 false 表示连接失败。}// 连接到设备上的 GATT 服务器(这部分代码在原始片段中被省略)。// 下面是可能的后续代码,用于实际连接到 GATT 服务器:/*if (device != null) {// 尝试连接到 GATT 服务器,并传入回调接口以处理连接状态变化等事件。bluetoothGatt = device.connectGatt(this, false, gattCallback);// 可能需要在这里添加额外的逻辑来处理连接结果,例如等待连接完成或设置超时。return true; // 假设连接操作成功启动。}*/// 注意:原始代码片段在此处结束,没有提供完整的连接逻辑。
}

当服务被触发后,DeviceControlActivity 会调用此 connect() 函数。 初始化。activity 需要传入 BLE 设备的地址。在 在以下示例中,设备地址将作为 intent 传递给 activity extra。

// 定义一个 ServiceConnection 的匿名内部类实例,用于监听服务绑定和解绑事件。
private ServiceConnection serviceConnection = new ServiceConnection() {// 当服务成功绑定时调用此方法。@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 将传入的 IBinder 转换为 LocalBinder,并通过它获取 BluetoothLeService 实例。bluetoothService = ((LocalBinder) service).getService();// 检查是否成功获取到蓝牙服务实例。if (bluetoothService != null) {// 尝试初始化蓝牙服务。if (!bluetoothService.initialize()) {// 如果初始化失败,记录错误信息并结束当前 Activity。Log.e(TAG, "Unable to initialize Bluetooth");finish(); // 结束当前 Activity。}// 尝试连接到指定地址的设备。// perform device connectionbluetoothService.connect(deviceAddress); // 使用预先定义的 deviceAddress 进行连接。}}// 当服务意外断开连接时调用此方法。@Overridepublic void onServiceDisconnected(ComponentName name) {// 将 bluetoothService 设置为 null,表示服务已断开。bluetoothService = null;}
};
 

声明 GATT 回调

一旦该 activity 告知服务要连接到哪个设备和该服务 连接到设备时,服务需要连接到 BLE 设备。此连接需要 BluetoothGattCallback 才能接收 有关连接状态、服务发现、特征 读取和特征通知

本主题重点介绍连接状态通知。请参阅传输 BLE 数据来了解如何 服务发现、特征读取和请求特征 通知。

通过 onConnectionStateChange() 函数。 在以下示例中,回调是在 Service 类中定义的,因此它 可以搭配 BluetoothDevice 发生 服务就会连接到该网络。

// 定义一个 BluetoothGattCallback 的匿名内部类实例,用于处理与 GATT 服务器交互的回调事件。
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {// 当 GATT 服务器连接状态发生变化时调用此方法。@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {// 检查新的连接状态是否为已连接。if (newState == BluetoothProfile.STATE_CONNECTED) {// 成功连接到 GATT 服务器,在这里可以执行进一步的操作,// 例如开始服务发现或初始化设备交互逻辑。Log.i(TAG, "Successfully connected to the GATT Server");} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 连接断开,从 GATT 服务器断开连接。// 可能需要在这里清理资源或通知用户连接已丢失。Log.w(TAG, "Disconnected from the GATT Server");}// 注意:通常还需要检查 status 参数以确保操作成功。status 为 BluetoothGatt.GATT_SUCCESS 表示操作成功。// 如果 status 不是 GATT_SUCCESS,可能需要处理错误情况。}
};

连接到 GATT 服务

声明 BluetoothGattCallback 后,该服务便可使用 connect() 函数中的 BluetoothDevice 对象,用于连接到 GATT 服务。

通过 connectGatt() 函数。这需要一个 Context 对象,这是一个 autoConnect 布尔值 标志和 BluetoothGattCallback。在此示例中,应用直接 连接到 BLE 设备,因此系统会为 autoConnect 传递 false

此外,还添加了 BluetoothGatt 属性。这样,该服务就可以关闭 。

// 定义一个名为 BluetoothLeService 的类,它继承自 Service。
class BluetoothLeService extends Service {...// 声明一个 BluetoothGatt 类型的私有成员变量 bluetoothGatt,// 用于管理与远程 GATT 服务器(即 BLE 设备)之间的连接。private BluetoothGatt bluetoothGatt;...// 定义一个公共方法 connect,用于尝试连接到指定地址的 BLE 设备。public boolean connect(final String address) {// 检查蓝牙适配器是否已初始化以及提供的地址是否为空。if (bluetoothAdapter == null || address == null) {// 如果蓝牙适配器未初始化或地址未指定,则记录警告信息并返回 false 表示连接失败。Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");return false;}try {// 使用给定的地址获取远程蓝牙设备对象。final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);// 尝试连接到该设备上的 GATT 服务器,并传入回调接口以处理连接状态变化等事件。// 参数 `false` 表示不使用自动连接模式,即如果设备当前不在范围内,则不会重试连接。bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);// 返回 true 表示连接操作成功启动。请注意,这并不意味着连接已经建立成功,// 连接结果将通过 bluetoothGattCallback 中的方法来通知。return true;} catch (IllegalArgumentException exception) {// 如果提供的地址无效或不符合预期格式,则捕获 IllegalArgumentException 异常。// 记录警告信息并返回 false 表示连接失败。Log.w(TAG, "Device not found with provided address. Unable to connect.");return false;}}
}

广播动态

当服务器与 GATT 服务器连接或断开连接时,需要通知 新状态的 activity。您可以通过多种方式实现这一目标。通过 以下示例使用广播 从服务传递到 activity 的信息。

该服务会声明一个函数来广播新状态。此函数将 操作字符串中,该字符串在广播之前会传递到 Intent 对象 发送到系统。

// 定义一个名为 broadcastUpdate 的私有方法,用于广播特定动作的意图。
private void broadcastUpdate(final String action) {// 创建一个新的 Intent 对象,指定其动作(action)参数为传入的方法参数。final Intent intent = new Intent(action);// 使用 sendBroadcast 方法发送该 Intent,通知所有注册了对应 action 的广播接收者。sendBroadcast(intent);
}
 

广播函数设置好后,即可用在 BluetoothGattCallback,用于发送 GATT 服务器。声明常量和服务的当前连接状态 。Intent

// 定义一个名为 BluetoothLeService 的类,它继承自 Service。
class BluetoothLeService extends Service {// 定义两个静态字符串常量,用于表示广播动作(Intent action),分别是连接和断开连接。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";// 定义两个静态整型常量,用于内部跟踪蓝牙 GATT 服务器的连接状态。private static final int STATE_DISCONNECTED = 0;private static final int STATE_CONNECTED = 2;// 声明一个私有整型变量 connectionState,用于保存当前的连接状态。private int connectionState;...// 定义一个 BluetoothGattCallback 的匿名内部类实例,用于处理与 GATT 服务器交互的回调事件。private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {// 当 GATT 服务器连接状态发生变化时调用此方法。// 检查新的连接状态是否为已连接。if (newState == BluetoothProfile.STATE_CONNECTED) {// 成功连接到 GATT 服务器,更新连接状态为已连接。connectionState = STATE_CONNECTED;// 广播通知其他组件连接成功。broadcastUpdate(ACTION_GATT_CONNECTED);} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 连接断开,从 GATT 服务器断开连接,更新连接状态为断开。connectionState = STATE_DISCONNECTED;// 广播通知其他组件连接断开。broadcastUpdate(ACTION_GATT_DISCONNECTED);}// 注意:通常还需要检查 status 参数以确保操作成功。status 为 BluetoothGatt.GATT_SUCCESS 表示操作成功。// 如果 status 不是 GATT_SUCCESS,可能需要处理错误情况。}};…
}

 

在活动中监听更新

服务广播连接更新后,activity 需要 实现 BroadcastReceiver。 在设置 activity 时注册此接收器,并在 activity 正在离开屏幕。通过监听来自该服务的事件, activity 能够根据当前的 BLE 设备的连接状态。

// 定义一个名为 DeviceControlsActivity 的类,它继承自 AppCompatActivity。
class DeviceControlsActivity extends AppCompatActivity {...// 定义一个 BroadcastReceiver 的匿名内部类实例,用于监听蓝牙 GATT 事件的广播更新。private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// 获取广播的意图动作(action),用于识别广播类型。final String action = intent.getAction();// 检查广播动作是否为 ACTION_GATT_CONNECTED。if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {// 如果是连接成功的广播,则设置 connected 标志为 true,// 并调用 updateConnectionState 方法更新 UI 显示已连接状态。connected = true;updateConnectionState(R.string.connected);} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {// 如果是断开连接的广播,则设置 connected 标志为 false,// 并调用 updateConnectionState 方法更新 UI 显示已断开状态。connected = false;updateConnectionState(R.string.disconnected);}}};// 重写 onResume 方法,在活动恢复时注册广播接收器并尝试连接到设备。@Overrideprotected void onResume() {super.onResume(); // 调用父类方法,确保正常的生命周期管理。// 注册 gattUpdateReceiver,以便它可以接收蓝牙 GATT 状态变化的广播。registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter());// 如果 bluetoothService 不为空,尝试连接到指定地址的设备,并记录操作结果。if (bluetoothService != null) {final boolean result = bluetoothService.connect(deviceAddress);Log.d(TAG, "Connect request result=" + result); // 记录连接请求的结果。}}// 重写 onPause 方法,在活动暂停时取消注册广播接收器以节省资源。@Overrideprotected void onPause() {super.onPause(); // 调用父类方法,确保正常的生命周期管理。// 取消注册 gattUpdateReceiver,避免内存泄漏。unregisterReceiver(gattUpdateReceiver);}// 定义一个静态方法用于创建一个 IntentFilter,该过滤器包含两个动作:// ACTION_GATT_CONNECTED 和 ACTION_GATT_DISCONNECTED。private static IntentFilter makeGattUpdateIntentFilter() {final IntentFilter intentFilter = new IntentFilter();// 向 intentFilter 添加 ACTION_GATT_CONNECTED 动作。intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);// 向 intentFilter 添加 ACTION_GATT_DISCONNECTED 动作。intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);return intentFilter; // 返回配置好的 IntentFilter。}
}
 

在传输 BLE 数据中,执行以下操作: BroadcastReceiver 也用于以如下形式传达服务发现: 以及来自设备的特征数据。

关闭 GATT 连接

处理蓝牙连接时,一个重要的步骤是关闭 使用它们。为此,请调用 close() 针对 BluetoothGatt 对象调用函数。在以下示例中,服务 存储对 BluetoothGatt 的引用。当 activity 与 服务,连接会关闭,以免消耗设备电池电量。

// 定义一个名为 BluetoothLeService 的类,它继承自 Service。
class BluetoothLeService extends Service {...// 重写 onUnbind 方法,在所有客户端解除绑定时调用。@Overridepublic boolean onUnbind(Intent intent) {// 调用 close 方法来关闭与 GATT 服务器的连接并释放资源。close();// 调用父类的 onUnbind 方法,并返回其结果。这通常用于通知系统是否应保留服务实例以供将来使用。return super.onUnbind(intent);}// 定义一个私有方法 close,用于安全地关闭蓝牙 GATT 连接并清理相关资源。private void close() {// 检查 bluetoothGatt 是否为 null,以避免尝试关闭一个已经关闭或从未初始化的连接。if (bluetoothGatt == null) {// 如果 bluetoothGatt 为 null,则直接返回,不执行任何操作。return;}// 调用 bluetoothGatt 的 close 方法来断开与远程设备的 GATT 连接,并释放所有相关的资源。bluetoothGatt.close();// 将 bluetoothGatt 设置为 null,表示连接已关闭并且不再持有对 GATT 对象的引用。bluetoothGatt = null;}
}




传输 BLE 数据 

bookmark_border

  • 本页内容
  • 发现服务
  • 读取 BLE 特性
  • 接收 GATT 通知

连接到 BLE GATT 后, 服务器,您可以使用 以了解设备上提供哪些服务、查询数据 ,并在特定 GATT 特征时请求通知 更改。

发现服务

在 BLE 设备上连接到 GATT 服务器后,首先要做的是 来执行服务发现。此信息可提供有关您所用服务 以及服务特征及其 描述符。在以下示例中,服务成功连接到 设备(通过对 onConnectionStateChange() 的 BluetoothGattCallback), 该 discoverServices() 函数从 BLE 设备中查询信息。

该服务需要覆盖 onServicesDiscovered() 函数 BluetoothGattCallback。 此函数在设备报告其可用服务时被调用。

// 定义一个名为 BluetoothLeService 的类,它继承自 Service。
class BluetoothLeService extends Service {// 定义一个静态字符串常量 ACTION_GATT_SERVICES_DISCOVERED,// 用于标识广播动作,当 GATT 服务被成功发现时发送此广播。public final static String ACTION_GATT_SERVICES_DISCOVERED ="com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";...// 定义一个 BluetoothGattCallback 的匿名内部类实例,用于处理与 GATT 服务器交互的回调事件。private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {// 当 GATT 服务器连接状态发生变化时调用此方法。// 检查新的连接状态是否为已连接。if (newState == BluetoothProfile.STATE_CONNECTED) {// 成功连接到 GATT 服务器,更新连接状态为已连接。connectionState = STATE_CONNECTED;// 广播通知其他组件连接成功。broadcastUpdate(ACTION_GATT_CONNECTED);// 在成功连接后尝试发现 GATT 服务器上的服务。bluetoothGatt.discoverServices();} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 连接断开,从 GATT 服务器断开连接,更新连接状态为断开。connectionState = STATE_DISCONNECTED;// 广播通知其他组件连接断开。broadcastUpdate(ACTION_GATT_DISCONNECTED);}// 注意:通常还需要检查 status 参数以确保操作成功。status 为 BluetoothGatt.GATT_SUCCESS 表示操作成功。// 如果 status 不是 GATT_SUCCESS,可能需要处理错误情况。}@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {// 当 GATT 服务器的服务被发现时调用此方法。// 检查服务发现的状态是否为成功。if (status == BluetoothGatt.GATT_SUCCESS) {// 如果服务发现成功,则广播通知其他组件服务已被发现。broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);} else {// 如果服务发现失败,记录警告信息,说明服务发现的结果不是成功。Log.w(TAG, "onServicesDiscovered received: " + status);}}};
}
 

该服务使用广播来通知 活动。发现服务后,服务便可调用 getServices()至 获取报告的数据。

KotlinJava

// 定义一个名为 BluetoothLeService 的类,它继承自 Service。
class BluetoothLeService extends Service {...// 定义一个公共方法 getSupportedGattServices,用于获取支持的 GATT 服务列表。public List<BluetoothGattService> getSupportedGattServices() {// 检查 bluetoothGatt 是否为 null,以确保 GATT 连接已建立。if (bluetoothGatt == null) return null;// 调用 bluetoothGatt 的 getServices 方法来获取所有支持的服务列表,// 并将其返回给调用者。此列表包含所有由远程设备提供的 GATT 服务。return bluetoothGatt.getServices();}
}

然后,activity 在收到广播 intent 时可以调用此函数, 表示服务发现已完成。

// 定义一个名为 DeviceControlsActivity 的类,它继承自 AppCompatActivity。
class DeviceControlsActivity extends AppCompatActivity {...// 定义一个 BroadcastReceiver 的匿名内部类实例 gattUpdateReceiver,// 用于监听蓝牙 GATT 事件的广播更新。private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// 获取广播的意图动作(action),用于识别广播类型。final String action = intent.getAction();// 检查广播动作是否为 ACTION_GATT_CONNECTED。if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {// 如果是连接成功的广播,则设置 connected 标志为 true,// 并调用 updateConnectionState 方法更新 UI 显示已连接状态。connected = true;updateConnectionState(R.string.connected);} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {// 如果是断开连接的广播,则设置 connected 标志为 false,// 并调用 updateConnectionState 方法更新 UI 显示已断开状态。connected = false;updateConnectionState(R.string.disconnected);} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {// 如果是服务发现完成的广播,则调用 displayGattServices 方法显示所有支持的服务和特性。// 这个方法会更新用户界面以展示远程设备提供的所有 GATT 服务及其特征。displayGattServices(bluetoothService.getSupportedGattServices());}}};
}

读取 BLE 特性

一旦您的应用连接到 GATT 服务器并发现服务, 可以读取和写入属性(在支持的情况下)。例如,以下 该代码段会循环访问服务器的服务和特征,并显示 在界面中执行以下操作:

public class DeviceControlActivity extends Activity {...// 展示如何遍历支持的 GATT 服务和特征。// 在这个例子中,我们填充一个与 UI 中的 ExpandableListView 绑定的数据结构。private void displayGattServices(List<BluetoothGattService> gattServices) {if (gattServices == null) return; // 如果传入的服务列表为空,则直接返回。// 获取未知服务和特征的字符串资源,用于在找不到匹配项时显示。String unknownServiceString = getResources().getString(R.string.unknown_service);String unknownCharaString = getResources().getString(R.string.unknown_characteristic);// 创建三个列表来存储服务、特征的数据以及原始的 BluetoothGattCharacteristic 对象。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>>();// 遍历所有可用的 GATT 服务。for (BluetoothGattService gattService : gattServices) {HashMap<String, String> currentServiceData = new HashMap<String, String>(); // 创建一个哈希表来存储当前服务的信息。String uuid = gattService.getUuid().toString(); // 获取服务的 UUID 并转换为字符串格式。// 将服务名称和服务 UUID 存储到哈希表中。使用 SampleGattAttributes.lookup 方法查找已知服务名称,// 如果找不到则使用默认的未知服务字符串。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>(); // 创建一个列表来保存原始的特征对象。// 遍历所有可用的特征。for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {charas.add(gattCharacteristic); // 将每个特征对象添加到原始特征列表中。HashMap<String, String> currentCharaData = new HashMap<String, String>(); // 创建一个哈希表来存储当前特征的信息。uuid = gattCharacteristic.getUuid().toString(); // 获取特征的 UUID 并转换为字符串格式。// 将特征名称和特征 UUID 存储到哈希表中。使用 SampleGattAttributes.lookup 方法查找已知特征名称,// 如果找不到则使用默认的未知特征字符串。currentCharaData.put(LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString));currentCharaData.put(LIST_UUID, uuid);gattCharacteristicGroupData.add(currentCharaData); // 将当前特征的数据添加到特征数据列表中。}// 将当前服务下的所有特征对象列表添加到全局特征列表中。mGattCharacteristics.add(charas);// 将当前服务下的所有特征数据列表添加到全局特征数据列表中。gattCharacteristicData.add(gattCharacteristicGroupData);}}
}
 

GATT 服务提供了一系列特征,您可以从 设备。要查询数据,请调用 readCharacteristic() 函数 BluetoothGatt,传入 BluetoothGattCharacteristic 想要阅读的内容

class BluetoothLeService extends Service {...// 定义一个公共方法 readCharacteristic,用于读取指定的 GATT 特征值。public void readCharacteristic(BluetoothGattCharacteristic characteristic) {// 检查 bluetoothGatt 是否为 null,以确保 GATT 连接已初始化。if (bluetoothGatt == null) {// 如果 bluetoothGatt 未初始化,则记录警告信息并直接返回,不执行任何操作。Log.w(TAG, "BluetoothGatt not initialized");return;}// 调用 bluetoothGatt 的 readCharacteristic 方法来读取指定特征的值。// 这将向远程设备发送请求,要求它返回该特征的当前值。bluetoothGatt.readCharacteristic(characteristic);}
}

在此示例中,服务实现了一个用于调用 readCharacteristic()。 这是一个异步调用。系统会将结果发送到 BluetoothGattCallback 函数 onCharacteristicRead()。

class BluetoothLeService extends Service {...// 定义一个 BluetoothGattCallback 的匿名内部类实例,用于处理与 GATT 服务器交互的回调事件。private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {...// 重写 onCharacteristicRead 方法,当从 GATT 服务器读取特征值完成时调用。@Overridepublic void onCharacteristicRead(BluetoothGatt gatt,                    // 表示连接到的 GATT 服务器(即远程设备)。BluetoothGattCharacteristic characteristic, // 表示已读取其值的特征。int status                             // 操作状态码,指示读取操作是否成功。) {// 检查读取操作的状态是否为成功。if (status == BluetoothGatt.GATT_SUCCESS) {// 如果读取成功,则通过广播更新通知其他组件数据可用,// 并将读取到的特征对象作为参数传递给广播接收器。broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);} else {// 如果读取失败,可以在这里添加错误处理逻辑或记录日志。Log.w(TAG, "onCharacteristicRead failed with status: " + status);}}};
}

当特定回调被触发时,它会调用相应的 broadcastUpdate() 辅助方法,并向其传递操作。请注意, 本部分根据蓝牙心率执行解析 测量配置文件规范。

private void broadcastUpdate(final String action,final BluetoothGattCharacteristic characteristic) {// 创建一个意图(Intent)对象,用于广播更新。final Intent intent = new Intent(action);// 特殊处理心率测量特征。根据心率测量规范解析数据。if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {// 获取特征属性标志位。int flag = characteristic.getProperties();int format = -1; // 初始化格式变量为 -1,表示未知格式。// 根据标志位判断心率值的格式是 16 位无符号整数还是 8 位无符号整数。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.");}// 从特征中读取心率值,偏移量为 1,因为心率值通常位于特征值的第一个字节之后。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 {// 对于所有其他特征,以十六进制格式写入数据。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() 方法:

class BluetoothLeService extends Service {...// 定义一个公共方法 setCharacteristicNotification,用于启用或禁用 GATT 特征的通知/指示。public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {// 检查 bluetoothGatt 是否为 null,以确保 GATT 连接已初始化。if (bluetoothGatt == null) {// 如果 bluetoothGatt 未初始化,则记录警告信息并直接返回,不执行任何操作。Log.w(TAG, "BluetoothGatt not initialized");return; // 注意:这里应使用小写的 'return' 而不是大写的 'Return'}// 调用 bluetoothGatt 的 setCharacteristicNotification 方法来启用或禁用特征通知。// 此调用会向远程设备发送请求,要求它在特征值变化时发送通知(如果启用)或停止发送通知(如果禁用)。bluetoothGatt.setCharacteristicNotification(characteristic, enabled);// 下面的代码是特定于心率测量特征的处理逻辑。// 如果当前特征是心率测量特征,则需要进一步配置其描述符。if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {// 获取心率测量特征的 Client Characteristic Configuration 描述符。// 这个描述符控制是否接收来自远程设备的通知。BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));// 设置描述符的值为启用通知。// 注意:这里假设我们总是启用通知。如果需要支持指示(indication),则需要额外的逻辑来区分。descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);// 将修改后的描述符值写回到远程设备。// 这将实际启用或禁用远程设备上的通知机制。bluetoothGatt.writeDescriptor(descriptor);}}
}

为某个特征启用通知后, onCharacteristicChanged() 回调将触发回调函数:

class BluetoothLeService extends Service {...// 定义一个 BluetoothGattCallback 的匿名内部类实例,用于处理与 GATT 服务器交互的回调事件。private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {...// 重写 onCharacteristicChanged 方法,当 GATT 服务器上的特征值发生变化时调用。@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,                    // 表示连接到的 GATT 服务器(即远程设备)。BluetoothGattCharacteristic characteristic // 表示其值已更改的特征。) {// 当远程设备发送通知或指示时,这个方法会被调用,并且会包含更新后的特征对象。// 调用广播更新方法来通知其他组件有新的数据可用,// 并将变更的特征对象作为参数传递给广播接收器。broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);}};
}



在后台交流 

bookmark_border

  • 本页内容
  • 查找设备
    • 在后台
  • 连接到设备
    • 在后台
  • 保持与设备的连接
    • 在应用之间切换时
    • 收听外围设备通知时

本指南简要介绍了当您的应用在后台运行时,如何为与外围设备通信的关键用例提供支持:

  • 查找设备
  • 连接到设备
  • 保持设备连接状态

这些用例都有多个支持选项。每种方法各有优缺点,可能使其更适合或不太符合您的具体需求。

下图为本页指南的简化视图:

注意 :Android 上针对后台工作的一般指南也适用于蓝牙相关工作。

查找设备

首先,您的应用需要查找要连接的设备。如需查找 BLE 设备,您可以使用以下任一 API:

  • BluetoothLeScanner(如查找 BLE 设备中所述)。 (示例)
  • CompanionDeviceManager(如配套设备配对中所述)。 (示例)

注意 :CompanionDeviceManager 具有某些限制(例如过滤功能有限且不支持随机 MAC 地址),这些限制可能无法满足您的需求,具体取决于外围设备的实现。

在后台

在应用不可见时使用其中任一 API 不受限制,但它们都需要您的应用进程保持活跃状态。如果应用进程未运行,您可以使用以下解决方法:

  • 对于 BluetoothLeScanner:使用 PendingIntent 对象(而不是 ScanCallback 对象)调用 startScan(),以便在扫描到与您的过滤条件匹配的设备时收到通知。(示例)
  • 对于 CompanionDeviceManager:请按照让配套应用保持唤醒状态中的指南来唤醒应用,并在之前关联的设备在范围内时使其保持唤醒状态。(示例)

注意 :不建议安排定期扫描来查找设备。这种方法效率较低,因为无论设备是否在范围内,它都会定期启动应用进程。本指南中介绍的方法可确保设备在唤醒应用进程之前位于感应范围内。

连接到设备

如需在找到设备后连接到该设备,您需要从以下来源之一获取该设备的 BluetoothDevice 实例:

  • BluetoothLeScanner 扫描结果(如上一部分中所述)。
  • 从 BluetoothAdapter.getBondedDevices() 检索到的绑定设备列表。
  • 使用 BluetoothAdapter.getRemoteLeDevice() 的 BluetoothAdapter 缓存。

有了 BluetoothDevice 实例后,您便可以通过调用 connectGatt() 方法之一向相应设备发起连接请求。您传递到 autoConnect 布尔值的值决定了 GATT 客户端使用以下两种连接模式中的哪一种:

  • Direct connect (autoconnect = false):尝试直接连接到外围设备,如果设备不可用,则尝试连接失败。如果断开连接,GATT 客户端不会自动尝试重新连接。
  • 自动连接 (autoconnect = true):在外围设备可用时尝试自动连接。如果外围设备发起断开连接或外围设备不在覆盖范围内,GATT 客户端会在外围设备可用时自动尝试重新连接。

注意 :低于 10 的 Android 版本一次只能有一个连接请求,并将所有后续请求加入队列。在 Android 10 及更高版本中,系统会将连接请求分组以便批量执行。

在后台

当应用在后台运行时,连接设备不受限制,但如果您的进程被终止,连接会关闭。此外,从后台启动 activity(在 Android 10 及更高版本中)或前台服务(在 Android 12 及更高版本中)存在限制。

因此,如需在后台建立连接,应用可以使用以下解决方案:

  • 使用 WorkManager 连接到设备。
    • 您可以设置 PeriodicWorkRequest 或 OneTimeWorkRequest 来执行定义的操作,不过可能会受到应用限制。
    • 此外,您还可以受益于工作约束条件、加急工作、重试政策等 WorkManager 功能。
    • 如果需要尽可能让连接保持活跃状态以执行任务(例如同步数据或从外围设备进行轮询),则需要按照对长时间运行的 worker 的支持中的指南启动前台服务。不过,从 Android 12 开始,将适用前台服务启动限制。
  • 启动一项具有 connectedDevice 类型的前台服务。
    • 如果需要尽可能让连接保持活跃状态以执行任务(例如同步数据或从外围设备进行轮询),则需要按照对长时间运行的 worker 的支持中的指南启动前台服务。不过,从 Android 12 开始,将适用前台服务启动限制。
  • 如查找设备中所述,使用 PendingIntent 对象调用 startScan(),以在设备存在时唤醒您的进程。外围设备必须进行广播。
    • 我们建议您启动一个 worker 和一个 Job。此操作可能会被系统中断,因此仅支持短时间的通信。
    • 在 Android 12 之前的版本中,您可以直接从 PendingIntent 对象启动前台服务。
  • 使用 CompanionDeviceService 以及 REQUEST_COMPANION_RUN_IN_BACKGROUND 或 REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND 权限即可在后台启动服务。

保持与设备的连接

理想情况下,应用应仅在必要时保持与外围设备的连接,并在任务完成后断开连接。不过,在以下两种情况下,应用可能需要无限期地保持连接的活动:

  • 在应用之间切换时。
  • 在收听外围设备通知时。

在这两种情况下,都可以使用以下选项:

  • 请结合使用 CompanionDeviceService、REQUEST_COMPANION_RUN_IN_BACKGROUND 权限和 CompanionDeviceManager.startObservingDevicePresence() 方法。
  • 当应用在前台(或在某个豁免范围内)且前台类型为 connectedDevice 时,请启动前台服务。

在应用之间切换时

查找设备、连接到设备并传输数据既耗时又耗费资源。为避免每次用户切换应用或同时执行任务时连接中断且必须执行完整流程,您应使连接保持活跃状态,直到操作完成。您可以使用 connectedDevice 类型的前台服务或配套设备在线状态 API。

收听外围设备通知时

如需监听外围设备通知,应用必须调用 setCharacteristicNotification(),使用 onCharacteristicChanged() 监听回调,并使连接保持活跃状态。对于大多数应用来说,最好通过 CompanionDeviceService 来支持此用例,因为应用可能需要长时间保持监听。不过,您也可以使用前台服务。

无论是哪种情况,您都可以在终止进程后按照连接到设备部分中的说明重新连接。

相关文章:

安卓低功耗蓝牙BLE官方开发例程(JAVA)翻译注释版

官方原文链接 https://developer.android.com/develop/connectivity/bluetooth/ble/ble-overview?hlzh-cn 目录 低功耗蓝牙 基础知识 关键术语和概念 角色和职责 查找 BLE 设备 连接到 GATT 服务器 设置绑定服务 设置 BluetoothAdapter 连接到设备 声明 GATT 回…...

搭建fastapi项目

环境准备 # 创建项目目录 mkdir my_fastapi_project cd my_fastapi_project# 创建和激活虚拟环境 python -m venv venv .\venv\Scripts\activate安装必要的包 pip install fastapi uvicorn python-dotenv创建项目基本结构 my_fastapi_project/ │ .env # …...

Maven学习(Maven项目模块化。模块间“继承“机制。父(工程),子项目(模块)间聚合)

目录 一、Maven项目模块化&#xff1f; &#xff08;1&#xff09;基本介绍。 &#xff08;2&#xff09;汽车模块化生产再聚合组装。 &#xff08;3&#xff09;Maven项目模块化图解。 1、maven_parent。 2、maven_pojo。 3、maven_dao。 4、maven_service。 5、maven_web。 6…...

华为云云原生中间件DCS DMS 通过中国信通院与全球IPv6测试中心双重能力检测

近日&#xff0c;中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;与全球IPv6测试中心相继宣布&#xff0c;华为云的分布式缓存服务&#xff08;Distributed Cache Service&#xff0c;简称DCS&#xff09;和分布式消息服务&#xff08;Distributed Message …...

PostgreSQL中事件触发器Event Trigger

在PostgreSQL中&#xff0c;事件触发器&#xff08;Event Trigger&#xff09;是一种特殊的触发器类型&#xff0c;它允许你在特定的数据库系统事件发生时执行特定的操作。与普通的触发器不同&#xff0c;事件触发器并不与特定的表或视图相关联&#xff0c;而是与数据库级别的全…...

uni.request流式(Stream)请求,实现打印机效果

最近使用扣子 - 开发指南 (coze.cn)和智谱AI开放平台开发小程序AI导诊和用药对话指南。 开发的过程中也是走了不少坑,下面就来聊聊走了哪些坑。 坑1 :coze试了v2和v3的接口,两个接口请求还是有点差别的,v2拿到了botId和accessToken可以直接请求不需要做任何处理,v3还需要…...

canvas保存图片

需求&#xff1a;上面有几个按钮&#xff0c;其中有一个切换是图片 用v-if会导致图片加载慢 实现方法&#xff1a; 一进来就加载&#xff0c;通过监听元素显示&#xff0c;用于控制canvas的宽高&#xff0c;从而达到隐藏的效果 组件dowolad.vue <template><view …...

DNS到底有什么用?

举个例子&#xff0c;对于我们来说访问的域名是www.baidu.com&#xff0c;但是实际在计算机并不认识这个域名&#xff0c;计算机是需要通过IP地址去访问这个网站&#xff0c;所以呢&#xff1f;这个时候就需要一个dns解析器&#xff0c;来把这串域名转换为IP地址给计算机去访问…...

什么是CRM系统?CRM系统的功能、操作流程、生命周期

CRM系统作为企业管理和维护客户关系的重要工具&#xff0c;在商业活动中扮演着越来越重要的角色。今天&#xff0c;就让我们一起揭开它的神秘面纱&#xff0c;看看这个“幕后英雄”到底是怎么工作的。 什么是CRM系统&#xff1f; 首先&#xff0c;我们要了解什么是CRM。简单来…...

美畅物联丨JS播放器录像功能:从技术到应用的全面解析

畅联云平台的JS播放器是一款功能十分强大的视频汇聚平台播放工具&#xff0c;它已经具备众多实用功能&#xff0c;像实时播放、历史录像回放、云台控制、倍速播放、录像记录、音频播放、画面放大、全屏展示、截图捕捉等等。这些功能构建起了一个高效、灵活且用户友好的播放环境…...

我们来学mysql -- 事务并发之不可重复读(原理篇)

事务并发之不可重复读 题记不可重复读系列文章 题记 在《事务之概念》提到事务对应现实世界的状态转换&#xff0c;这个过程要满足4个特性这世界&#xff0c;真理只在大炮射程之类&#xff0c;通往和平的道路&#xff0c;非“常人”可以驾驭一个人生活按部就班&#xff0c;人多…...

ABAQUS进行焊接仿真分析(含子程序)

0 前言 焊接技术作为现代制造业中的重要连接工艺,广泛应用于汽车、船舶、航空航天、能源等多个行业。焊接接头的质量和性能直接影响到结构件的安全性、可靠性和使用寿命。因此,在焊接过程中如何有效预测和优化焊接过程中的热效应、应力变化以及材料变形等问题,成为了焊接研…...

BAPI_GOODSMVT_CREATE物料凭证增强字段

目的&#xff1a;增加字段LSMNG LSMEH的赋值 项目MSEG 的 BAPI 表增强结构 BAPI_TE_XMSEG 抬头MKPF 的 BAIP 表增强 BAPI_TE_XMKPF 1. 在结构BAPI_TE_XMSEG中appending structure附加结构 ZMSEG_001&#xff0c;增加字段LSMNG&#xff0c; LSMEH In The method IF_EX_MB_H…...

tomcat的优化和动静分离

tomcat的优化 1.tomcat的配置优化 2.操作系统的内核优化 注意&#xff1a;设置保存后&#xff0c;需要重新ssh连接才会看到配置更改的变化 vim /etc/security/limits.conf # 65535 为Linux系统最大打开文件数 * soft nproc 65535 * hard nproc 65535 * soft nofile 65535 *…...

[ShaderLab] 【Unity】【图像编程】理解 Unity Shader 的结构

在计算机图形学领域,开发者经常面临着管理着色器复杂性的挑战。正如大卫惠勒(David Wheeler)所说:“计算机科学中的任何问题都可以通过增加一层抽象来解决。” Unity 提供了这样一层抽象,即 ShaderLab,它通过组织和定义渲染过程的各个步骤,简化了编写着色器的过程。 什…...

vue的前端架构 介绍各自的优缺点

Vue.js 是一个用于构建用户界面的渐进式框架&#xff0c;可以根据项目的复杂性和需求选择不同的前端架构。以下是几种常见的 Vue 前端架构及其优缺点&#xff1a; 1. 单页应用 (SPA) 单页应用&#xff08;Single Page Application&#xff0c;简称 SPA&#xff09;是一种现代…...

可信AI与零知识证明的概念

可信AI 可信AI是指人工智能的设计、开发和部署遵循一系列原则和方法,以确保其行为和决策是可靠、可解释、公平、安全且符合人类价值观和社会利益的.以下是关于可信AI的举例说明、实现方式及主流方案: 举例说明 医疗诊断领域:一个可信AI的医疗诊断系统,不仅能够准确地识别…...

JavaScript逆向时,常用的11个hook

提示:记录工作中遇到的需求及解决办法 文章目录 前言01、dom操作02、Cookie操作03、事件监听操作04、AJAX拦截操作05、函数替换操作06、Header操作07、URL操作08、JSON.stringify操作09、JSON.parse操作10、eval操作11、Function操作前言 在逆向分析JavaScript代码时,开发者…...

PCL点云库入门——PCL库可视化之CloudViewer类简单点云信息显示

1、前言 可视化&#xff08;visualization&#xff09;涉及运用计算机图形学和图像处理技术&#xff0c;将数据转换成图像并在屏幕上展示&#xff0c;同时支持交互式处理。在PCL库中&#xff0c;一系列强大的可视化工具可供使用&#xff0c;其中较为流行的包括CloudViewer和PCL…...

C++ 【衔接篇】

大名鼎鼎的c实际上是由c语言扩展而来的&#xff0c;它最初是由本贾尼在20世纪80年代开发。目的是支持面向对象编程&#xff0c;同时保持c语言高效和可移植等优点。c是c的扩展&#xff0c;在一定程度上解决了c语言在特殊场景下的使用局限。 1、命名空间 在详细说明命名空间之前…...

qcreator 调试原理

在 Qt 开发中&#xff0c;Qt Creator 是一个集成开发环境&#xff08;IDE&#xff09;&#xff0c;用于开发 Qt 应用程序。Qt Creator 提供了强大的调试功能&#xff0c;可以帮助开发者高效地调试 C、QML 等语言的应用程序。Qt Creator 支持多种调试工具&#xff0c;包括基于 G…...

Windows 系统中的组策略编辑器如何打开?

组策略是 Windows 操作系统中用于设置计算机和用户配置的重要工具。它允许管理员控制各种系统功能&#xff0c;从桌面背景到安全设置等。对于 Windows 专业版、企业版和教育版用户来说&#xff0c;可以通过组策略编辑器&#xff08;Group Policy Editor&#xff09;来管理这些设…...

scala的泛型类

泛型&#xff1a;类型参数化 泛型类指的是把泛型定义到类的声明上, 即:该类中的成员的参数类型是由泛型来决定的. 在创建对象时, 明确具体的数据类型. 定义格式: class 类名&#xff08;成员名&#xff1a;数据类型&#xff09; class 类名[泛型名](成员名:泛型名) 参考代…...

基于Couchbase的数据构建方案:数仓分层

初步方案是将公共层和报表层分别放在不同的bucket中&#xff0c;这种设计从存储和访问优化的角度是合理的&#xff0c;但仍有以下细节需要考虑&#xff1a; 1. 数仓公共层设计&#xff08;origin bucket&#xff09; 合理性分析&#xff1a; 将ODS、DWD、DWS层的数据放在一个b…...

信创改造-Spring Boot 项目部署至 TongWeb

打 war 包参考&#xff1a;https://blog.csdn.net/z1353095373/article/details/144330999...

supervision - 好用的计算机视觉 AI 工具库

Supervision库是一款出色的Python计算机视觉低代码工具&#xff0c;其设计初衷在于为用户提供一个便捷且高效的接口&#xff0c;用以处理数据集以及直观地展示检测结果。简化了对象检测、分类、标注、跟踪等计算机视觉的开发流程。开发者仅需加载数据集和模型&#xff0c;就能轻…...

程序的调试

一名优秀的程序员也是一名出色的侦探&#xff0c;每一次调试都是尝试破案的过程 目录 前言 一、什么是调试&#xff1f; 二、调试 1.调试是什么 2.基本步骤 三、调试注意事项 1.怎么写出易于调试的代码 assert(断言) const 2.常见错误 总结 前言 主要是怎么调试&#xff0c;调…...

使用html 和javascript 实现微信界面功能2

1.功能说明&#xff1a; 对上一篇的基础上进行了稍稍改造 主要修改点&#xff1a; 搜索功能: 在搜索框后面增加了搜索按钮。 搜索按钮调用performSearch函数来执行搜索操作。 表单形式的功能: 上传文件: 修改为表单形式&#xff0c;允许用户通过文件输入控件选择文件并上传。 …...

虚幻引擎Actor类生命周期

AActor构造函数 在AActor类的构造函数中,虚幻引擎会初始化与该Actor相关的一些关键属性,比如: 默认的组件(如RootComponent、MeshComponent等)。默认的属性设置,例如位置、旋转、缩放等。还会调用BeginPlay等生命周期函数,但在构造函数中,这些函数不会执行。当你在场景…...

记录2024-leetcode-字符串DP

10. 正则表达式匹配 - 力扣&#xff08;LeetCode&#xff09;...