Android NDK开发详解连接性之配套设备配对
在搭载 Android 8.0(API 级别 26)及更高版本的设备上,配套设备配对会代表您的应用对附近的设备执行蓝牙或 Wi-Fi 扫描,而无需 ACCESS_FINE_LOCATION 权限。这有助于最大限度增强用户隐私保护。配对设备后,设备可以利用 REQUEST_COMPANION_RUN_IN_BACKGROUND 和 REQUEST_COMPANION_USE_DATA_IN_BACKGROUND 权限从后台启动应用。使用此方法可对配套设备(例如支持 BLE 的智能手表)执行初始配置。此外,配对设备需要启用位置信息服务。
配套设备配对不会自行创建连接。Bluetooth 和 Wi-Fi 连接 API 会建立连接。配套设备配对也无法启用连续扫描。
用户可以从列表中选择设备并授予其访问应用的权限。如果您卸载应用或调用 disassociate(),系统会撤消这些权限。如果用户不再需要关联,应用负责清除自己的关联,例如在用户退出绑定设备或移除绑定设备时。
实现配套设备配对
如需建立和管理与配套设备的连接,请使用 CompanionDeviceManager。本部分介绍了通过蓝牙、BLE 和 Wi-Fi 将应用与配套设备配对时,如何自定义配对请求对话框。
指定配套设备
以下代码示例展示了如何将 标志添加到清单文件中。这会告知系统,您的应用打算设置配套设备。
<uses-feature android:name="android.software.companion_device_setup"/>
按类型列出设备
您可以显示与您提供的过滤条件匹配的所有可能的配套设备,也可以显示单个选项(如图 1 所示)。为此,您可以创建一个过滤器来指定应用正在查找的设备类型,也可以将 setSingleDevice() 设置为 true(如图 2 所示)。
配套设备配对屏幕,仅限一个配对选项。
图 1. 配套设备配对屏幕,仅限一个配对选项。
配套设备配对屏幕,只能有一个没有个人资料的配对选项。
图 2.配套设备配对屏幕,只能有一个没有个人资料的配对选项。
如需对请求对话框中显示的配套设备列表应用过滤条件,请检查蓝牙是否已开启或检查 WLAN 是否已开启。启用连接后,您可以添加 DeviceFilter。DeviceFilter 的以下子类会根据连接类型指定应用可以关联的设备类型:
BluetoothDeviceFilter
BluetoothLeDeviceFilter
WifiDeviceFilter
所有三个子类都具有可简化过滤器配置的构建器。在以下示例中,设备会扫描具有 BluetoothDeviceFilter 的蓝牙设备。
Kotlin
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()
Java
BluetoothDeviceFilter deviceFilter = new 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(new ParcelUuid(new UUID(0x123abcL, -1L)), null)
.build();
将 DeviceFilter 设置为 AssociationRequest,以便设备管理器可以确定要查找的设备类型。
Kotlin
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()
Java
AssociationRequest pairingRequest = new 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() 函数用于配对请求对象和回调。回调会指示应用何时定位设备,以及何时准备好启动对话框供用户输入他们的选择。如果应用找不到任何设备,该回调将返回错误消息。
在搭载 Android 13(API 级别 33)及更高版本的设备上:
Kotlin
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) {
// The association is created.
}
override fun onFailure(errorMessage: CharSequence?) {
// Handle the failure.
}
})
Java
CompanionDeviceManager deviceManager =
(CompanionDeviceManager) getSystemService(Context.COMPANION_DEVICE_SERVICE);
Executor executor = new Executor() {
@Override
public void execute(Runnable runnable) {
runnable.run();
}
};
deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() {
executor,
// Called when a device is found. Launch the IntentSender so the user can
// select the device they want to pair with.
@Override
public void onDeviceFound(IntentSender chooserLauncher) {
try {
startIntentSenderForResult(
chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0
);
} catch (IntentSender.SendIntentException e) {
Log.e("MainActivity", "Failed to send intent");
}
}
@Override
public void onAssociationCreated(AssociationInfo associationInfo) {
// The association is created.
}
@Override
public void onFailure(CharSequence errorMessage) {
// Handle the failure.
});
在搭载 Android 12L(API 级别 32)及更低版本(已废弃)的设备上:
Kotlin
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?) {
// Handle the failure.
}
}, null)
Java
CompanionDeviceManager deviceManager =
(CompanionDeviceManager) getSystemService(Context.COMPANION_DEVICE_SERVICE);
deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() {
// Called when a device is found. Launch the IntentSender so the user can
// select the device they want to pair with.
@Override
public void onDeviceFound(IntentSender chooserLauncher) {
try {
startIntentSenderForResult(
chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0
);
} catch (IntentSender.SendIntentException e) {
Log.e("MainActivity", "Failed to send intent");
}
}
@Override
public void onFailure(CharSequence error) {
// Handle the failure.
}
}, null);
为了让用户能够选择要连接到的设备类型,请使用 onAssociationPending() 函数中的 intentSender 参数启动偏好设置 activity。此操作的结果会发送回偏好设置 activity 的 onActivityResult() 函数中的 fragment。当用户根据结果做出选择时,此操作会更新您。然后,您就可以访问所选设备了。当用户选择蓝牙设备时,发送的结果是一个 BluetoothDevice 对象。同样,当 onAssociationPending() 函数检测到用户选择蓝牙 LE 设备时,便会遇到 android.bluetooth.le.ScanResult 对象。对于 Wi-Fi 设备,需要一个 android.net.wifi.ScanResult 对象。
Kotlin
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)
}
}
Java
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
if (requestCode == SELECT_DEVICE_REQUEST_CODE && data != null) {
BluetoothDevice deviceToPair =
data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE);
if (deviceToPair != null) {
deviceToPair.createBond();
// Continue to interact with the paired device.
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
如需使用可指定设备并按类型列出设备的过滤条件来实现配套设备配对,请参阅以下示例:
在搭载 Android 13(API 级别 33)及更高版本的设备上:
Kotlin
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)
}
}
}
Java
class MainActivityJava extends AppCompatActivity {
private static final int SELECT_DEVICE_REQUEST_CODE = 0;
Executor executor = new Executor() {
@Override
public void execute(Runnable runnable) {
runnable.run();
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CompanionDeviceManager deviceManager =
(CompanionDeviceManager) getSystemService(
Context.COMPANION_DEVICE_SERVICE
);
// To skip filtering based on name and supported feature flags,
// do not include calls to setNamePattern() and addServiceUuid(),
// respectively. This example uses Bluetooth.
BluetoothDeviceFilter deviceFilter =
new BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile("My device"))
.addServiceUuid(
new ParcelUuid(new UUID(0x123abcL, -1L)), null
)
.build();
// The argument provided in setSingleDevice() determines whether a single
// device name or a list of device names is presented to the user as
// pairing options.
AssociationRequest pairingRequest = new AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.build();
// When the app tries to pair with the Bluetooth device, show the
// appropriate pairing request dialog to the user.
deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() {
executor,
// Called when a device is found. Launch the IntentSender so the user can
// select the device they want to pair with.
@Override
public void onDeviceFound(IntentSender chooserLauncher) {
try {
startIntentSenderForResult(
chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0
);
} catch (IntentSender.SendIntentException e) {
Log.e("MainActivity", "Failed to send intent");
}
}
@Override
public void onAssociationCreated(AssociationInfo associationInfo) {
// AssociationInfo object is created and get association id and the
// macAddress.
int associationId = associationInfo.getId();
MacAddress macAddress = associationInfo.getDeviceMacAddress();
}
@Override
public void onFailure(CharSequence errorMessage) {
// Handle the failure.
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
if (requestCode == SELECT_DEVICE_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
BluetoothDevice deviceToPair = data.getParcelableExtra(
CompanionDeviceManager.EXTRA_DEVICE
);
if (deviceToPair != null) {
deviceToPair.createBond();
// ... Continue interacting with the paired device.
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
在搭载 Android 12L(API 级别 32)及更低版本(已废弃)的设备上:
Kotlin
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)
}
}
}
Java
class MainActivityJava extends AppCompatActivity {
private static final int SELECT_DEVICE_REQUEST_CODE = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CompanionDeviceManager deviceManager =
(CompanionDeviceManager) getSystemService(
Context.COMPANION_DEVICE_SERVICE
);
// To skip filtering based on name and supported feature flags,
// don't include calls to setNamePattern() and addServiceUuid(),
// respectively. This example uses Bluetooth.
BluetoothDeviceFilter deviceFilter =
new BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile("My device"))
.addServiceUuid(
new ParcelUuid(new UUID(0x123abcL, -1L)), null
)
.build();
// The argument provided in setSingleDevice() determines whether a single
// device name or a list of device names is presented to the user as
// pairing options.
AssociationRequest pairingRequest = new AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.build();
// When the app tries to pair with the Bluetooth device, show the
// appropriate pairing request dialog to the user.
deviceManager.associate(pairingRequest,
new CompanionDeviceManager.Callback() {
@Override
public void onDeviceFound(IntentSender chooserLauncher) {
try {
startIntentSenderForResult(chooserLauncher,
SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
// failed to send the intent
}
}
@Override
public void onFailure(CharSequence error) {
// handle failure to find the companion device
}
}, null);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == SELECT_DEVICE_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
BluetoothDevice deviceToPair = data.getParcelableExtra(
CompanionDeviceManager.EXTRA_DEVICE
);
if (deviceToPair != null) {
deviceToPair.createBond();
// ... Continue interacting with the paired device.
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
配套设备配置文件
对于以 Android 12(API 级别 31)及更高版本为目标平台的合作伙伴应用,可以在连接到手表时使用配套设备配置文件。如需了解详情,请参阅有关在 Wear OS 上请求权限的指南。
让配套应用保持唤醒状态
在 Android 12(API 级别 31)及更高版本中,您可以使用其他 API 来帮助配套应用在配套设备在范围内时保持运行。利用这些 API,您可以执行以下操作:
当配套设备在范围内时唤醒您的应用。
如需了解详情,请参阅 CompanionDeviceManager.startObservingDevicePresence()。
保证只要配套设备保持在范围内,应用进程就会继续运行。
如需了解详情,请参阅 CompanionDeviceService.onDeviceAppeared()。
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2023-11-11。