Xamarin C#开发Android蓝牙应用无法扫描发现设备问题
问题根因
ACTION_FOUND广播不触发属于蓝牙开发的高频踩坑问题,结合你的代码和配置,核心原因有以下几点:
- 只在Manifest声明了权限,没有做运行时动态申请。从Android 6.0开始位置权限、Android 12开始蓝牙扫描/连接权限都属于运行时权限,仅在清单文件声明、不主动申请且用户未手动授权的话,系统会直接拦截设备发现结果,不会向应用下发
ACTION_FOUND广播,这是占比最高的诱因。 - 测试设备未开启可发现模式。Windows笔记本默认仅打开蓝牙开关时是不可被外部扫描到的,只有进入「添加蓝牙设备」的系统搜索页面时,才会临时进入可发现状态对外广播蓝牙信号;日常连接键鼠、耳机的正常使用状态下,不会响应任何外部扫描请求。
- 广播注册逻辑冲突。你同时通过类特性做了静态广播注册、又在
OnCreate里动态调用RegisterReceiver注册,静态注册时系统会调用接收器的无参构造生成实例,这个实例里的_bluetoothDevices字段为null,哪怕收到广播也会触发空指针,多实例共存也会直接导致逻辑混乱。 - 冗余权限声明触发校验拦截。你清单里配置的
BLUETOOTH_PRIVILEGED是系统签名应用才能获取的权限,普通第三方应用声明了也无法通过授权;ACCESS_BACKGROUND_LOCATION是后台扫描才需要的权限,前台扫描不需要,多余的权限声明反而可能触发系统的扫描结果拦截逻辑。
可行修复步骤
按顺序调整即可解决问题:
- 补全运行时权限申请逻辑
启动蓝牙扫描前必须按系统版本申请对应权限,等用户全部授权后再调用StartDiscovery():- Android 12(API 31)及以上:申请
BLUETOOTH_SCAN、BLUETOOTH_CONNECT、ACCESS_FINE_LOCATION三个权限;如果不需要通过蓝牙扫描获取位置信息,可以在Manifest的BLUETOOTH_SCAN权限声明中加上android:usesPermissionFlags="neverForLocation",即可免除位置权限申请要求。 - Android 11(API 30)及以下:仅需申请
ACCESS_FINE_LOCATION权限。
参考实现代码:
protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // 其余页面初始化逻辑 CheckAndRequestPermissions(); } private void CheckAndRequestPermissions() { if (Build.VERSION.SdkInt >= BuildVersionCodes.S) { string[] requiredPerms = new string[] { Manifest.Permission.BluetoothScan, Manifest.Permission.BluetoothConnect, Manifest.Permission.AccessFineLocation }; if (requiredPerms.Any(p => CheckSelfPermission(p) != Permission.Granted)) { RequestPermissions(requiredPerms, 1001); return; } } else { if (CheckSelfPermission(Manifest.Permission.AccessFineLocation) != Permission.Granted) { RequestPermissions(new string[] { Manifest.Permission.AccessFineLocation }, 1002); return; } } // 权限校验通过后再注册广播、启动扫描 RegisterBroadcastReceivers(); BluetoothAdapter.DefaultAdapter.StartDiscovery(); } public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.All(r => r == Permission.Granted)) { RegisterBroadcastReceivers(); BluetoothAdapter.DefaultAdapter.StartDiscovery(); } else { Toast.MakeText(this, "必须授予相关权限才能扫描蓝牙设备", ToastLength.Long).Show(); } } - Android 12(API 31)及以上:申请
- 调整测试方式
扫描Windows笔记本时,手动打开系统设置的「添加蓝牙设备」页面,保持页面停留状态再启动应用扫描,否则设备不会对外广播,必然无法被搜索到。 - 清理重复的广播注册逻辑
把三个BroadcastReceiver类上的[BroadcastReceiver]、[IntentFilter]特性全部删除,放弃静态注册——因为你需要向接收器传入外部维护的设备列表,动态注册的方式更适配这个场景。同时记得在OnDestroy生命周期里注销接收器,避免内存泄漏:private List<BluetoothDevice> _bluetoothDevices = new List<BluetoothDevice>(); private DeviceDiscoveryStarted _startedReceiver; private DeviceFound _foundReceiver; private DiscoveryFinished _finishedReceiver; private void RegisterBroadcastReceivers() { _startedReceiver = new DeviceDiscoveryStarted(); RegisterReceiver(_startedReceiver, new IntentFilter(BluetoothAdapter.ActionDiscoveryStarted)); _foundReceiver = new DeviceFound(_bluetoothDevices); RegisterReceiver(_foundReceiver, new IntentFilter(BluetoothDevice.ActionFound)); _finishedReceiver = new DiscoveryFinished(_bluetoothDevices); RegisterReceiver(_finishedReceiver, new IntentFilter(BluetoothAdapter.ActionDiscoveryFinished)); } protected override void OnDestroy() { base.OnDestroy(); // 注销所有已注册的广播接收器 if (_startedReceiver != null) UnregisterReceiver(_startedReceiver); if (_foundReceiver != null) UnregisterReceiver(_foundReceiver); if (_finishedReceiver != null) UnregisterReceiver(_finishedReceiver); // 页面销毁时主动停止扫描 BluetoothAdapter.DefaultAdapter.CancelDiscovery(); } - 增加日志辅助排查
在DeviceFound接收器的OnReceive方法开头增加日志输出,调试时通过Logcat确认是系统未下发广播,还是自身代码执行出错:public override void OnReceive(Context context, Intent intent) { Android.Util.Log.Debug("BT_SCAN", $"收到广播,Action类型: {intent.Action}"); // 原有业务逻辑,注意对_bluetoothDevices增加空判断,避免空指针崩溃 } - 清理冗余权限
把Manifest里的BLUETOOTH_PRIVILEGED、ACCESS_BACKGROUND_LOCATION权限声明删除,普通前台蓝牙扫描不需要这两个权限,多留无益。
内容的提问来源于stack exchange,提问作者TechGeek
相关产品推荐
相关产品推荐

