Android:蓝牙设置配套设备配对
一、概述
在搭载 Android 8.0(API 级别 26)及更高版本的设备上,配套设备配对会代表您的应用对附近的设备执行蓝牙或 Wi-Fi 扫描,而不需要 ACCESS_FINE_LOCATION 权限。这有助于最大限度地保护用户隐私。使用此方法执行配套设备(如支持 BLE 的智能手表)的初始配置。此外,配套设备配对需要启用位置信息服务。
配对配套设备不会自行创建连接,也不会启用连续扫描。应用可以使用蓝牙或 Wi-Fi 连接 API 建立连接。
配对设备后,设备可以使用 REQUEST_COMPANION_RUN_IN_BACKGROUND 和 REQUEST_COMPANION_USE_DATA_IN_BACKGROUND 权限从后台启动应用。应用还可以使用 REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND 权限从后台启动前台服务。
用户可以从列表中选择设备,并向应用授予访问该设备的权限。如果您卸载应用或调用 disassociate(),系统会撤消这些权限。如果用户不再需要配套应用的关联(例如在用户退出账号或移除已绑定的设备时),配套应用负责清除自己的关联。
二、实现配套设备配对
本部分介绍了如何使用 CompanionDeviceManager 通过蓝牙、BLE 和 Wi-Fi 将应用与配套设备配对。
指定配套设备
以下代码示例展示了如何向清单文件添加 <uses-feature> 标志。这会告知系统您的应用打算设置配套设备。
<uses-feature android:name="android.software.companion_device_setup"/>
按 DeviceFilter 列出设备
您可以显示与您提供的 DeviceFilter 匹配的所有在范围内的配套设备(如图 1 所示)。如果您想将扫描范围限制为一台设备,可以将 setSingleDevice() 设为 true(如图 2 所示)。
图1 配套设备配对
图 2. 单设备配对
以下是可在 AssociationRequest 中指定的 DeviceFilter 的子类:
- BluetoothDeviceFilter
- BluetoothLeDeviceFilter
- WifiDeviceFilter
这三个子类都有用于简化过滤器配置的构建器。在以下示例中,设备使用 BluetoothDeviceFilter 扫描蓝牙设备。
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
// Match only Bluetooth devices whose name matches the pattern.
.setNamePattern(Pattern.compile("My device"))
// Match only Bluetooth devices whose service UUID matches this pattern.
.addServiceUuid(ParcelUuid(UUID(0x123abcL, -1L)), null)
.build()
将 DeviceFilter 设置为 AssociationRequest,以便 CompanionDeviceManager 确定要搜索的设备类型。
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
// Find only devices that match this request filter.
.addDeviceFilter(deviceFilter)
// Stop scanning as soon as one device matching the filter is found.
.setSingleDevice(true)
.build()
应用初始化 AssociationRequest 后,请对 CompanionDeviceManager 运行 associate() 函数。associate() 函数接受 AssociationRequest 和 Callback。
当 CompanionDeviceManager 定位到设备且准备好启动用户意见征求对话框时,Callback 会在 onAssociationPending 中返回 IntentSender。用户确认设备后,系统会在 onAssociationCreated 中返回设备的 AssociationInfo。如果您的应用找不到任何设备,回调会返回 onFailure 和错误消息。
在搭载 Android 13(API 级别 33)及更高版本的设备上:
val deviceManager =
requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE)
val executor: Executor = Executor { it.run() }
deviceManager.associate(pairingRequest,
executor,
object : CompanionDeviceManager.Callback() {
// Called when a device is found. Launch the IntentSender so the user
// can select the device they want to pair with.
override fun onAssociationPending(intentSender: IntentSender) {
intentSender?.let {
startIntentSenderForResult(it, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0)
}
}
override fun onAssociationCreated(associationInfo: AssociationInfo) {
// An association is created.
}
override fun onFailure(errorMessage: CharSequence?) {
// To handle the failure.
}
})
在搭载 Android 12L(API 级别 32)或更低版本的设备上(已废弃):
val deviceManager =
requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE)
deviceManager.associate(pairingRequest,
object : CompanionDeviceManager.Callback() {
// Called when a device is found. Launch the IntentSender so the user
// can select the device they want to pair with.
override fun onDeviceFound(chooserLauncher: IntentSender) {
startIntentSenderForResult(chooserLauncher,
SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0)
}
override fun onFailure(error: CharSequence?) {
// To handle the failure.
}
}, null)
用户选择的结果会发送回 activity 的 onActivityResult() 中的 fragment。然后,您就可以访问所选设备了。
当用户选择蓝牙设备时,应显示 BluetoothDevice。当用户选择蓝牙 LE 设备时,会遇到 android.bluetooth.le.ScanResult。当用户选择 Wi-Fi 设备时,应显示 android.net.wifi.ScanResult。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
SELECT_DEVICE_REQUEST_CODE -> when(resultCode) {
Activity.RESULT_OK -> {
// The user chose to pair the app with a Bluetooth device.
val deviceToPair: BluetoothDevice? =
data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
deviceToPair?.let { device ->
device.createBond()
// Continue to interact with the paired device.
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
查看完整示例:
在搭载 Android 13(API 级别 33)及更高版本的设备上:
private const val SELECT_DEVICE_REQUEST_CODE = 0
class MainActivity : AppCompatActivity() {
private val deviceManager: CompanionDeviceManager by lazy {
getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
}
val mBluetoothAdapter: BluetoothAdapter by lazy {
val java = BluetoothManager::class.java
getSystemService(java)!!.adapter }
val executor: Executor = Executor { it.run() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// To skip filters based on names and supported feature flags (UUIDs),
// omit calls to setNamePattern() and addServiceUuid()
// respectively, as shown in the following Bluetooth example.
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile("My device"))
.addServiceUuid(ParcelUuid(UUID(0x123abcL, -1L)), null)
.build()
// The argument provided in setSingleDevice() determines whether a single
// device name or a list of them appears.
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.build()
// When the app tries to pair with a Bluetooth device, show the
// corresponding dialog box to the user.
deviceManager.associate(pairingRequest,
executor,
object : CompanionDeviceManager.Callback() {
// Called when a device is found. Launch the IntentSender so the user
// can select the device they want to pair with.
override fun onAssociationPending(intentSender: IntentSender) {
intentSender?.let {
startIntentSenderForResult(it, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0)
}
}
override fun onAssociationCreated(associationInfo: AssociationInfo) {
// AssociationInfo object is created and get association id and the
// macAddress.
var associationId: int = associationInfo.id
var macAddress: MacAddress = associationInfo.deviceMacAddress
}
override fun onFailure(errorMessage: CharSequence?) {
// Handle the failure.
}
)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
SELECT_DEVICE_REQUEST_CODE -> when(resultCode) {
Activity.RESULT_OK -> {
// The user chose to pair the app with a Bluetooth device.
val deviceToPair: BluetoothDevice? =
data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
deviceToPair?.let { device ->
device.createBond()
// Maintain continuous interaction with a paired device.
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
}
在搭载 Android 12L(API 级别 32)或更低版本的设备上(已废弃):
private const val SELECT_DEVICE_REQUEST_CODE = 0
class MainActivity : AppCompatActivity() {
private val deviceManager: CompanionDeviceManager by lazy {
getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// To skip filters based on names and supported feature flags (UUIDs),
// omit calls to setNamePattern() and addServiceUuid()
// respectively, as shown in the following Bluetooth example.
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile("My device"))
.addServiceUuid(ParcelUuid(UUID(0x123abcL, -1L)), null)
.build()
// The argument provided in setSingleDevice() determines whether a single
// device name or a list of them appears.
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.build()
// When the app tries to pair with a Bluetooth device, show the
// corresponding dialog box to the user.
deviceManager.associate(pairingRequest,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
startIntentSenderForResult(chooserLauncher,
SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0)
}
override fun onFailure(error: CharSequence?) {
// Handle the failure.
}
}, null)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
SELECT_DEVICE_REQUEST_CODE -> when(resultCode) {
Activity.RESULT_OK -> {
// The user chose to pair the app with a Bluetooth device.
val deviceToPair: BluetoothDevice? =
data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
deviceToPair?.let { device ->
device.createBond()
// Maintain continuous interaction with a paired device.
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
}
三、配套设备配置文件
在 Android 12(API 级别 31)及更高版本中,用于管理手表等设备的配套应用可以使用配套设备配置文件,通过在配对时授予必要的权限来简化设置流程。如需了解详情,请参阅配套设备配置文件。
四、使配套应用保持唤醒状态
在 Android 12(API 级别 31)及更高版本中,您可以使用其他 API 来帮助配套应用在配套设备在范围内时保持运行。借助这些 API,您可以执行以下操作:
- 当配套设备处于范围内时,唤醒您的应用。
如需了解详情,请参阅 CompanionDeviceManager.startObservingDevicePresence()。- 保证只要配套设备保持在范围内,应用进程将继续运行。
如需了解详情,请参阅 CompanionDeviceService.onDeviceAppeared()。