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

news2024/12/17 2:20:15

官方原文链接

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() {
            @Override
            public 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() {
            // 当扫描到一个新的设备时,系统会调用此方法。
            @Override
            public 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
    @Override
    public 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() {
        // 当服务成功绑定时调用此方法。
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 将传入的 IBinder 转换为 LocalBinder,并通过它获取 BluetoothLeService 实例。
            bluetoothService = ((LocalBinder) service).getService();
            if (bluetoothService != null) {
                // 如果成功获取到服务实例,可以在这里调用服务上的方法来检查连接状态或连接设备。
            }
        }

        // 当服务断开连接时调用此方法。
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 将 bluetoothService 设置为 null,表示服务已断开。
            bluetoothService = null;
        }
    };

    // 重写 onCreate 方法,它是 Activity 生命周期的一部分,在 Activity 第一次创建时调用。
    @Override
    protected 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() {
        // 当服务成功绑定时调用此方法。
        @Override
        public 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
            }
        }

        // 当服务意外断开连接时调用此方法。
        @Override
        public 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() {

    // 当服务成功绑定时调用此方法。
    @Override
    public 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 connection
            bluetoothService.connect(deviceAddress); // 使用预先定义的 deviceAddress 进行连接。
        }
    }

    // 当服务意外断开连接时调用此方法。
    @Override
    public 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 服务器连接状态发生变化时调用此方法。
    @Override
    public 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() {
        @Override
        public 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() {
        @Override
        public 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 方法,在活动恢复时注册广播接收器并尝试连接到设备。
    @Override
    protected 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 方法,在活动暂停时取消注册广播接收器以节省资源。
    @Override
    protected 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 方法,在所有客户端解除绑定时调用。
    @Override
    public 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() {
        @Override
        public 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,可能需要处理错误情况。
        }

        @Override
        public 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() {
        @Override
        public 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 服务器读取特征值完成时调用。
        @Override
        public 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 服务器上的特征值发生变化时调用。
        @Override
        public 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 来支持此用例,因为应用可能需要长时间保持监听。不过,您也可以使用前台服务。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2260809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp打包apk允许横屏竖屏内容翻转

文章目录 一、教程总结 一、教程 1.添加配置 "orientation": [//竖屏正方向"portrait-primary",//竖屏反方向"portrait-secondary",//横屏正方向"landscape-primary",//横屏反方向"landscape-secondary",//自然方向"…

ElasticSearch 常见故障解析与修复秘籍

文章目录 一、ElasticSearch启动服务提示无法使用root用户二、ElasticSearch启动提示进程可拥有的虚拟内存少三、ElasticSearch提示用户拥有的可创建文件描述符太少四、ElasticSearch集群yellow状态分析五、ElasticSearch节点磁盘使用率过高&#xff0c;read_only状态问题解决六…

Java——网络编程(上)

1 计算机网络 (作用资源共享和信息传递) (计算机网络组成——> 硬件——>计算机设备&#xff0c;外部设备&#xff0c;通信线路 软件——>网络操作系统&#xff0c;网络管理软件&#xff0c;网络通信协议) 计算机网络是指将地理位置不同的具有独立功能的多台计算机…

游戏引擎学习第50天

仓库: https://gitee.com/mrxiao_com/2d_game Minkowski 这个算法有点懵逼 回顾 基本上&#xff0c;现在我们所处的阶段是&#xff0c;回顾最初的代码&#xff0c;我们正在讨论我们希望在引擎中实现的所有功能。我们正在做的版本是初步的、粗略的版本&#xff0c;涵盖我们认…

Unix 和 Windows 的有趣比较

Unix 和 Windows NT 比较 来源于这两本书&#xff0c;把两本书对照来读&#xff0c;发现很多有意思的地方&#xff1a; 《Unix 传奇》 https://book.douban.com/subject/35292726/ 《观止 微软创建NT和未来的夺命狂奔 》 Showstopper!: The Breakneck Race to Create Windows…

攻防世界逆向刷题笔记(新手模式6-?)

6.1000clicks 看题目名字似乎是让咱们点击1000次之后才会出flag。本来打算用CE看能不能搜索出来数值&#xff0c;技术不到家&#xff0c;最后没有搜索到&#xff0c;还导致永劫无间打不了了。所以还是拿出IDA老实分析。 直接搜索flag字符&#xff0c;出来一大堆。张紫涵大佬说…

ANOMALY BERT 解读

出处&#xff1a; ICLR workshop 2023 代码&#xff1a;Jhryu30/AnomalyBERT 可视化效果&#xff1a; 一 提出动机 动机&#xff1a;无监督 TSAD 领域内&#xff0c;“训练集” 也缺失&#xff1a;真值标签&#xff08;GT&#xff09;&#xff1b;换句话说&#xff0c;一个…

Java——网络编程(中)—TCP通讯(下)

1 双向通讯—创建服务端 (双向通信是指通信双方中&#xff0c;任何一方都可为发送端&#xff0c;任何一方都可为接收端) (1 创建ServerSocket对象&#xff0c;accept()返回socket) (2 双向通讯——>也要创建键盘输入对象) (3 通过与客户端对应的Socket对象获取输入流对象…

JavaFX使用jfoenix的UI控件

jfoenix还是一个不错的样式&#xff0c;推荐使用&#xff0c;而且也可以支持scene builder中的拖拖拽拽 需要注意的是过高的javafx版本可能会使得某些样式或控件无法使用 比如alert控件&#xff0c;亲测javaFX 19版本可以正常使用 1.在pom.xml中引入依赖 GitHub地址https://gi…

利用cnocr库完成中文扫描pdf文件的文字识别

很多pdf文件文字识别软件都会收费&#xff0c;免费的网页版可能会带来信息泄露&#xff0c;还有一些类似于腾讯AI和百度AI的接口都有调用次数限制&#xff0c;因此&#xff0c;利用识别正确率极高且免费的cnocr库来自己动手做个pdf文件文字识别程序就是一个很不错的选择。以下程…

大数据笔记之flink-cdc实时同步数据

大数据笔记之flink-cdc实时同步数据(mysql -->doris) 一、基本概念 Flink CDC 是一个基于流的数据集成工具&#xff0c;旨在为用户提供一套功能更加全面的编程接口&#xff08;API&#xff09;。 该工具使得用户能够以 YAML配置文件的形式&#xff0c;优雅地定义其 ETL&…

【数学】矩阵的逆与伪逆 EEGLAB

文章目录 前言matlab代码作用EEGLAB 中的代码总结参考文献 前言 在 EEGLAB 的使用中&#xff0c;运行程序时出现了矩阵接近奇异值&#xff0c;或者缩放错误。结果可能不准确。RCOND 1.873732e-20 的 bug&#xff0c;调查 EEGLAB 后发现是 raw 数据的问题。 matlab代码 A_1 …

RTMP推流平台EasyDSS在无人机推流直播安防监控中的创新应用

无人机与低空经济的关系密切&#xff0c;并且正在快速发展。2024年中国低空经济行业市场规模达到5800亿元&#xff0c;其中低空制造产业占整个低空经济产业的88%。预计未来五年复合增速将达到16.03%。 随着科技的飞速发展&#xff0c;公共安防关乎每一个市民的生命财产安全。在…

【记录49】vue2 vue-office在线预览 docx、pdf、excel文档

vue2 在线预览 docx、pdf、excel文档 docx npm install vue-office/docx vue-demi0.14.6 指定版本 npm install vue-office/docx vue-demi <template><VueOfficeDocx :src"pdf" style"height: 100vh;" rendere"rendereHandler" error&…

C# 探险之旅:第二十四节 - 类型class基础,一场“类”似的奇妙冒险

嘿&#xff0c;勇敢的探险家们&#xff01;欢迎来到C#王国的“类”似奇妙冒险&#xff01;今天&#xff0c;我们要深入探索一个神秘而强大的领域——class&#xff08;类&#xff09;。想象一下&#xff0c;class就像C#世界里的一块魔法土地&#xff0c;每块土地上都能孕育出独…

Burp suite 3 (泷羽sec)

声明 学习视频来自B站UP主 泷羽sec,如涉及侵泷羽sec权马上删除文章。 笔记只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 这节课旨在扩大自己在网络安全方面的知识面&#xff0c;了解网络安全领域的见闻&#xff0c;了…

cpptoml介绍

cpptoml 是一个用于 C 的开源库&#xff0c;旨在提供对 TOML&#xff08;Toms Obvious, Minimal Language&#xff09;格式的支持。它允许开发者轻松地在 C 项目中读取、解析和生成 TOML 格式的配置文件。cpptoml 是一个轻量级、易于使用的库&#xff0c;适用于那些希望将 TOML…

用户认证系统登录界面

下面是使用HTML和JavaScript实现的一个中文版登录界面&#xff0c;包含登录、注册和修改密码功能。注册成功后会显示提示信息&#xff0c;在登录成功后进入一个大大的欢迎页面。 1.代码展示 <!DOCTYPE html> <html lang"zh-CN"> <head><meta …

Pyside6 --Qt设计师--简单了解各个控件的作用之:Item Views

目录 一、List View二、Tree View三、Table View四、Column View 一、List View 学习方法和Buttons一样&#xff0c;大家自己在qt设计师上面在属性编辑区进行相应的学习&#xff01; 我就先紧着qt设计师的页面进行讲解&#xff0c;部分内容查自AI。 后面有什么好用的控件或者…

ArcGIS MultiPatch数据转换Obj数据

文章目录 ArcGIS MultiPatch数据转换Obj数据1 效果2 技术路线2.1 Multipatch To Collada2.2 Collada To Obj3 代码实现4 附录4.1 环境4.2 一些坑ArcGIS MultiPatch数据转换Obj数据 1 效果 2 技术路线 MultiPatch --MultipatchToCollada–> Collada --Assimp–> Obj 2.…