Android CompanionDeviceManager无法发现附近蓝牙设备问题求助
CompanionDeviceManager无法显示附近的非LE蓝牙设备
更新说明
- 添加了包含蓝牙权限逻辑的主活动代码
问题描述
我在运行Android 13的Pixel 5设备上使用CompanionDeviceManager API搜索附近的非低功耗(non LE)蓝牙设备,但它仅能发现附近的WiFi网络。我怀疑deviceFilter配置存在问题。
最初配置BluetoothDeviceFilter的代码如下:
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder() // 仅匹配名称符合该模式的蓝牙设备 .setNamePattern(Pattern.compile("(?i)\b(Certain Device Name)\b")) .build() private val pairingRequest: AssociationRequest = AssociationRequest.Builder() // 仅查找符合请求过滤器的设备 .addDeviceFilter(deviceFilter) // 找到一个匹配过滤器的设备后不停止扫描 .setSingleDevice(false) .build()
使用这段代码时,系统生成的配套设备配对界面中始终没有设备显示,加载动画会一直转圈直到超时。
我猜测可能是正则表达式限制过严,于是将过滤器改为允许所有设备的正则:
.setNamePattern(Pattern.compile(".*"))
但即使这样,配对界面中仍然没有附近的蓝牙设备显示。
当我故意不添加任何过滤器时,只能看到WiFi网络,说明CompanionDeviceManager可以正常工作,但针对蓝牙结果的配置有误。
private val pairingRequest: AssociationRequest = AssociationRequest.Builder() // 不设置过滤器,查看所有设备 .setSingleDevice(false) .build()
在Android系统的蓝牙菜单中,我能清楚看到范围内的蓝牙设备,甚至可以连接它们,但这些设备从未在我的应用中显示。
相关代码
HomeFragment.kt
class HomeFragment : Fragment() { //过滤可见蓝牙设备,仅显示范围内的Mozi设备 private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder() // 仅匹配名称符合该模式的蓝牙设备 .setNamePattern(Pattern.compile(BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR)) .build() private val pairingRequest: AssociationRequest = AssociationRequest.Builder() // 仅查找符合该请求过滤器的设备 .addDeviceFilter(deviceFilter) // 找到一个匹配过滤器的设备后不停止扫描 .setSingleDevice(false) .build() private val deviceManager: CompanionDeviceManager by lazy { requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager } private val executor: Executor = Executor { it.run() } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { setupPairingButton() return binding.root } /** * 此回调监听与Mozi蓝牙设备的连接尝试结果 */ @Deprecated("Deprecated in Java") @SuppressLint("MissingPermission") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { SELECT_DEVICE_REQUEST_CODE -> when (resultCode) { Activity.RESULT_OK -> { // 用户选择将应用与蓝牙设备配对 val deviceToPair: BluetoothDevice? = data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE) deviceToPair?.createBond() } } else -> super.onActivityResult(requestCode, resultCode, data) } } private fun setupPairingButton() { binding.buttonPair.setOnClickListener { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { /** * 这是Android 33及以上版本显示配对对话框的方式 */ deviceManager.associate(pairingRequest, executor, object : CompanionDeviceManager.Callback() { // 找到设备时调用。启动IntentSender让用户选择要配对的设备 override fun onAssociationPending(intentSender: IntentSender) { intentSender.let { sender -> activity?.let { fragmentActivity -> startIntentSenderForResult( fragmentActivity, sender, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0, null ) } } } override fun onAssociationCreated(associationInfo: AssociationInfo) { // 关联已创建 var associationId = associationInfo.id var macAddress: MacAddress? = associationInfo.deviceMacAddress } override fun onFailure(errorMessage: CharSequence?) { // 处理失败情况 showBluetoothErrorMessage(errorMessage) } }) } else { /** * 这是Android 32及以下版本显示配对对话框的方式 */ deviceManager.associate( pairingRequest, object : CompanionDeviceManager.Callback() { override fun onDeviceFound(chooserLauncher: IntentSender) { startIntentSenderForResult( chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0, null ) } override fun onFailure(error: CharSequence?) { // 处理失败情况 showBluetoothErrorMessage(error) } }, null ) } } } companion object { private const val SELECT_DEVICE_REQUEST_CODE = 0 private const val BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR = "(?i)\bCertain Device Name\b" } }
MainActivity.kt
class MainActivity : AppCompatActivity() { private val enableBluetoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) private var bluetoothEnableResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> binding.loadingSpinner.hide() when (result.resultCode) { Activity.RESULT_OK -> { Snackbar.make( binding.root, resources.getString(R.string.bluetooth_enabled_lets_pair_with_your_mozi), Snackbar.LENGTH_SHORT ).show() } Activity.RESULT_CANCELED -> { Snackbar.make( binding.root, getString(R.string.without_bluetooth_you_cant_pair_with_your_mozi), Snackbar.LENGTH_INDEFINITE ) .setAction(resources.getString(R.string._retry)) { ensureBluetoothIsEnabled() } .show() } } } private val requestBluetoothPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (isGranted) { bluetoothEnableResultLauncher.launch(enableBluetoothIntent) } else { Snackbar.make( binding.root, getString(R.string.without_bluetooth_you_cant_pair_with_your_mozi), Snackbar.LENGTH_INDEFINITE ) .setAction(resources.getString(R.string._retry)) { ensureBluetoothIsEnabled() } .show() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setupViews() ensureBluetoothIsEnabled() } private fun setupViews() { binding.bluetoothPermissionsRationaleDialogButton.setOnClickListener { binding.permissionsRationaleDialog.animateShow(false) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT) } else { requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH) } } } private fun ensureBluetoothIsEnabled() { binding.loadingSpinner.show() val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java) val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter if (bluetoothAdapter == null) { binding.loadingSpinner.hide() Snackbar.make( binding.root, resources.getString(R.string.you_need_a_bluetooth_enabled_device), Snackbar.LENGTH_INDEFINITE ).show() } if (bluetoothAdapter?.isEnabled == false) { if (ActivityCompat.checkSelfPermission( this, Manifest.permission.BLUETOOTH_CONNECT ) != PackageManager.PERMISSION_GRANTED ) { binding.loadingSpinner.hide() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) { binding.permissionsRationaleDialog.animateShow(true) } else { requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT) } } else { if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH)) { binding.permissionsRationaleDialog.animateShow(true) } else { requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH) } } return } else { binding.loadingSpinner.hide() bluetoothEnableResultLauncher.launch(enableBluetoothIntent) } } else { binding.loadingSpinner.hide() } } }
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!-- 蓝牙权限 --> <uses-feature android:name="android.software.companion_device_setup" android:required="true"/> <uses-feature android:name="android.hardware.bluetooth" android:required="true"/> <!-- 在旧设备上请求传统蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!-- 仅当应用需要搜索蓝牙设备时需要。如果应用不使用蓝牙扫描结果获取物理位置信息,可以声明不获取位置 --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags= "neverForLocation" tools:targetApi="s" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> ... </manifest>
解决方案
1. 补充蓝牙扫描权限请求
Android 12+中,扫描蓝牙设备需要BLUETOOTH_SCAN权限,你当前只请求了BLUETOOTH_CONNECT,导致无法扫描设备。修改权限请求逻辑为请求多个权限:
// 替换原有的requestBluetoothPermissionLauncher private val requestBluetoothPermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> val connectGranted = permissions[Manifest.permission.BLUETOOTH_CONNECT] ?: false val scanGranted = permissions[Manifest.permission.BLUETOOTH_SCAN] ?: false if (connectGranted && scanGranted) { bluetoothEnableResultLauncher.launch(enableBluetoothIntent) } else { Snackbar.make( binding.root, getString(R.string.without_bluetooth_permissions_cant_pair), Snackbar.LENGTH_INDEFINITE ) .setAction(resources.getString(R.string._retry)) { ensureBluetoothIsEnabled() } .show() } } // 修改ensureBluetoothIsEnabled中的权限请求部分 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val needsRationale = shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT) || shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_SCAN) if (needsRationale) { binding.permissionsRationaleDialog.animateShow(true) } else { requestBluetoothPermissionsLauncher.launch( arrayOf( Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN ) ) } } else { // 旧版本处理逻辑不变 }
2. 指定扫描传统蓝牙设备
在过滤器中明确设置设备类型为传统蓝牙(非BLE),避免默认只扫描BLE设备:
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder() .setNamePattern(Pattern.compile(BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR)) // 扫描传统蓝牙设备 .setDeviceType(BluetoothDeviceFilter.DEVICE_TYPE_CLASSIC) .build()
3. 修复正则表达式转义问题
Kotlin中字符串的\b需要转义为\\b,否则会被解析为退格符,导致正则不匹配任何设备:
private const val BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR = "(?i)\\bCertain Device Name\\b"
4. 优化清单权限
移除不必要的ACCESS_BACKGROUND_LOCATION权限(如果不需要后台定位),减少权限申请的复杂度:
<!-- 移除不需要的后台定位权限 --> <!-- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> -->
内容的提问来源于stack exchange,提问作者Cody
相关产品推荐
相关产品推荐

