配套设备配对

在搭载 Android 8.0(API 级别 26)及更高版本的设备上,配套设备配对会代表应用对附近的设备执行蓝牙或 Wi-Fi 扫描,而不需要 ACCESS_FINE_LOCATION 权限。这有助于最大限度地保护用户隐私。使用此方法可执行配套设备的初始配置,例如支持 BLE 的智能手表。此外,配套设备配对需要启用位置信息服务。

配套设备配对本身不会创建连接,也不会启用持续扫描。应用可以使用蓝牙或 Wi-Fi 连接 API 来建立连接。

设备配对后,可以使用 REQUEST_COMPANION_RUN_IN_BACKGROUNDREQUEST_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 的子类:

这三个子类都有可简化过滤器配置的 build 方法。在以下示例中,设备扫描具有 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,以便 CompanionDeviceManager 确定要搜索的设备类型。

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() 函数接受 AssociationRequestCallback

CompanionDeviceManager 找到设备并准备好启动用户意见征求对话框时,Callback 会在 onAssociationPending 中返回 IntentSender。用户确认设备后,系统会在 onAssociationCreated 中返回设备的 AssociationInfo。如果应用未找到任何设备,回调会返回 onFailure 并附带一条错误消息。

在搭载 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) {         // An association is created.     }      override fun onFailure(errorMessage: CharSequence?) {         // To 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) {         // An association is created.     }      @Override     public void onFailure(CharSequence errorMessage) {         // To 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?) {             // To 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) {         // To handle the failure.     } }, null);

用户选择的结果会通过 activity 的 onActivityResult() 发送回 fragment。然后,您就可以访问所选设备了。

当用户选择蓝牙设备时,应会收到 BluetoothDevice。当用户选择蓝牙 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)及更高版本中,管理手表等设备的配套应用可以使用配套设备配置文件,在配对时授予必要的权限,从而简化设置流程。如需了解详情,请参阅配套设备配置文件

使配套应用保持唤醒状态

从 Android 16(API 级别 36)开始,

废弃了 CompanionDeviceManager.startObservingDevicePresence(String)CompanionDeviceService.onDeviceAppeared()