Android Java/Xamarin 连接已发现蓝牙设备无法触发配对请求排查
问题背景
在Xamarin绑定封装的Android Java项目中,已实现范围内蓝牙设备扫描功能,扫描流程正常,可将搜索到的可发现未知蓝牙设备名称填充至ListView控件。根据Android官方蓝牙开发文档说明,无需提前判断设备是否已配对,调用BluetoothSocket.Connect方法时Android系统会自动发起配对流程。由于BluetoothSocket.Connect()在主线程运行会占用大量计算资源导致应用卡顿,因此继承Thread类将蓝牙连接逻辑写入该类的Run方法中执行。
异常日志
点击ListView中设备名称时,初始Logcat输出如下日志:
BluetoothClient D/BluetoothUtils: isSocketAllowedBySecurityPolicy start : device null BluetoothClient I/BluetoothSocket: connect() for device 74C17D called by pid: 23141 BluetoothClient W/BluetoothAdapter: getBluetoothService() called with no BluetoothManagerCallback
排查后确认原因为使用的UUID错误,改为读取设备自身支持的UUID列表后,原有报错消除,Logcat仅输出如下日志:
BluetoothClient.BluetoothClient I/BluetoothAdapter: cancelDiscovery BluetoothClient.BluetoothClient I/BluetoothAdapter: cancelDiscovery = false
问题复现代码
当前使用的蓝牙连接实现代码如下:
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)] public class MainActivity : AppCompatActivity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // 存储扫描到的蓝牙设备友好名称的ListView _deviceList = FindViewById<ListView>(Resource.Id.mylist); // 处理蓝牙设备名称点击事件 _deviceList.ItemClick += (o, e) => { // 初始化蓝牙设备对象为空 BluetoothDevice _mydevice=null; // 读取点击项对应的设备名称 string name = (string)_deviceList.GetItemAtPosition(e.Position); // 遍历所有已发现蓝牙设备匹配名称 foreach (var device in _bluetoothDevices) { if (device.Name == name) { // 匹配到目标设备后终止循环 _mydevice = device; Log.Info("MainActivity", device.Name); break; } } if (_mydevice != null) { try { BluetoothThread thread = new BluetoothThread(_mydevice,this); thread.Run(); } catch (Exception m) { Toast.MakeText(this,m.Message,ToastLength.Short).Show(); } } }; } } // 蓝牙连接多线程处理类 public class BluetoothThread : Thread { private BluetoothDevice dev; private Context ctx; public BluetoothThread() { } public BluetoothThread(BluetoothDevice device,Context context) { dev = device; ctx = context; } public override void Run() { try { Task.Run(() => { ParcelUuid[] parcelUuids = dev.GetUuids(); BluetoothSocket socket = dev.CreateRfcommSocketToServiceRecord(UUID.FromString(parcelUuids[0].ToString())); socket.Connect(); }); } catch (Exception p) { Toast.MakeText(ctx,p.Message,ToastLength.Short).Show(); } } }
预期调用socket.Connect()时应用会向远端设备发起配对请求,但实际无任何配对弹窗出现,仅输出上述日志信息。
问题根因
- 线程启动逻辑错误:直接调用
thread.Run()是在当前主线程同步执行方法体,并未真正启动独立工作线程。后续嵌套的Task.Run虽然会将任务投递到线程池,但外层try-catch无法捕获线程池内的异常,连接过程抛出的错误会被静默吞掉,代码实际未执行到触发配对的socket.Connect()逻辑。 - UUID获取逻辑错误:未配对设备在刚扫描完成时,本地缓存的UUID列表可能为空或不完整,直接在连接阶段调用
dev.GetUuids()取第一个UUID创建socket,大概率拿到的是无效服务UUID,无法建立有效RFCOMM连接。 - 扫描未完全终止:日志中
cancelDiscovery = false说明发起连接时蓝牙扫描仍在运行,Android系统会直接拦截扫描过程中的连接请求,不会触发配对流程。 - 权限缺失:Android 12(API 31)及以上版本需要动态申请
BLUETOOTH_CONNECT权限,权限未授予时系统会静默拒绝配对请求,不会弹出配对窗口。 - 线程操作违规:原有代码在子线程中直接调用Toast弹提示,会触发Android线程访问异常,导致连接流程中断。
修复方案
- 修正线程启动逻辑:不要直接调用
Run()方法,调用Start()方法启动独立工作线程,移除多余的Task.Run嵌套,保证异常可以被正常捕获。 - 提前拉取设备UUID:在扫描发现设备阶段,调用
FetchUuidsWithSdp()主动拉取设备的完整服务UUID列表,不要在连接时临时读取缓存值。 - 连接前强制终止扫描:发起连接前先调用
BluetoothAdapter.DefaultAdapter.CancelDiscovery(),确认扫描终止后再执行连接逻辑。 - 补全权限配置:在AndroidManifest中声明对应版本需要的蓝牙权限,在页面启动时动态申请
BLUETOOTH_CONNECT权限,权限授予后再允许用户点击设备发起连接。 - 修正线程UI操作:所有Toast等UI操作必须切到主线程执行,避免子线程访问UI导致的崩溃。
核心修复代码参考:
// 设备点击事件中先终止扫描再启动线程 BluetoothAdapter.DefaultAdapter.CancelDiscovery(); BluetoothThread thread = new BluetoothThread(_mydevice,this); // 调用Start()启动新线程,不要直接调用Run() thread.Start();
public override void Run() { try { ParcelUuid[] parcelUuids = dev.GetUuids(); // 优先匹配标准SPP服务UUID,不要直接取第一个UUID UUID targetUuid = parcelUuids.FirstOrDefault(u => u.Uuid.ToString().Equals("00001101-0000-1000-8000-00805F9B34FB"))?.Uuid ?? parcelUuids[0].Uuid; BluetoothSocket socket = dev.CreateRfcommSocketToServiceRecord(targetUuid); socket.Connect(); // 连接成功后的后续业务逻辑 } catch (Exception p) { // 切到主线程弹出错误提示 Handler mainHandler = new Handler(Looper.MainLooper); mainHandler.Post(() => Toast.MakeText(ctx,p.Message,ToastLength.Short).Show()); } }
内容的提问来源于stack exchange,提问作者TechGeek
相关产品推荐
相关产品推荐

