Xamarin iOS 蓝牙外设扫描从来没有看到任何外设
Xamarin iOS Bluetooth peripheral scanning never sees any peripherals
我正在尝试创建一个 Xamarin.Forms 应用程序,它将在 iOS 和 Android 上 运行。最终我需要应用程序的实例通过蓝牙相互通信,但我坚持让 iOS 端使用蓝牙做任何事情。我最初尝试使用 Plugin.BluetoothLE 和 Plugin.BLE,但一周半后我无法使用让广告或扫描在任一插件上工作 OS,所以我决定尝试使用平台 API 的 .NET 包装器实现简单的蓝牙交互,至少有详细记录。我确实在 Android 端进行了扫描以正常工作。但是,使用 iOS,我现在所拥有的构建得很好,并且 运行s 在我的 iPad 上没有错误,但是 DiscoveredPeripheral 处理程序是从未调用过,即使 iPad 距离 Android 平板电脑只有几英寸,而且大概应该能够看到相同的设备。我已经通过在该方法中设置断点来验证这一点,该断点从未达到过;当我打开 iPad 上的蓝牙设置以使其可发现时,Android 平板电脑上的应用程序版本可以看到它,所以我认为这不是 iPad 硬件问题。
似乎很明显,我不知道该过程的某些部分是什么,但(对我而言)不明显还可以去哪里寻找它是什么。这是与 CBCentralManager 交互的 class 的代码(据我所读,这应该包括 return 所需的一切外围设备列表):
using MyBluetoothApp.Shared; // for the interfaces and constants
using CoreBluetooth;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
[assembly: Dependency(typeof(MyBluetoothApp.iOS.PeripheralScanner))]
namespace MyBluetoothApp.iOS
{
public class PeripheralScanner : IPeripheralScanner
{
private readonly CBCentralManager manager;
private List<IPeripheral> foundPeripherals;
public PeripheralScanner()
{
this.foundPeripherals = new List<IPeripheral>();
this.manager = new CBCentralManager();
this.manager.DiscoveredPeripheral += this.DiscoveredPeripheral;
this.manager.UpdatedState += this.UpdatedState;
}
public async Task<List<IPeripheral>> ScanForService(string serviceUuid)
{
return await this.ScanForService(serviceUuid, BluetoothConstants.DEFAULT_SCAN_TIMEOUT);
}
public async Task<List<IPeripheral>> ScanForService(string serviceUuid, int duration)
{
CBUUID uuid = CBUUID.FromString(serviceUuid);
//this.manager.ScanForPeripherals(uuid);
this.manager.ScanForPeripherals((CBUUID)null); // For now I'd be happy to see ANY peripherals
await Task.Delay(duration);
this.manager.StopScan();
return this.foundPeripherals;
}
private void DiscoveredPeripheral(object sender, CBDiscoveredPeripheralEventArgs args)
{
this.foundPeripherals.Add(new CPeripheral(args.Peripheral));
}
private void UpdatedState(object sender, EventArgs args)
{
CBCentralManagerState state = ((CBCentralManager)sender).State;
if (CBCentralManagerState.PoweredOn != state)
{
throw new Exception(state.ToString());
}
}
}
}
任何人都可以指出我所缺少的方向吗?
编辑:好吧...好吧,我偶然发现如果我在共享代码中这样做:
IPeripheralScanner scanner = DependencyService.Get<IPeripheralScanner>();
List<IPeripheral> foundPeripherals = await scanner.ScanForService(BluetoothConstants.VITL_SERVICE_UUID);
连续两次,第二次有效。我感到既充满希望又更加困惑。
潜在的问题是,在 PeripheralScanner 的第一个实例中,ScanForService 在 State 更新之前被调用。我尝试了很多方法来等待引发该事件,因此我可以确定状态为 PoweredOn,但似乎没有任何效果;轮询循环根本就没有达到所需的状态,但是如果我在 UpdatedState 处理程序中抛出异常,它会在启动后的几毫秒内抛出,并且当时的状态始终是 PoweredOn 。 (该处理程序中的断点导致调试冻结并输出 Resolved pending breakpoint,甚至 VS 团队似乎也无法解释)。
阅读一些 Apple 开发人员博客后,我发现这种情况通常可以通过在 UpdatedState 处理程序中发生所需的操作来避免。它终于浸透了我的头脑,我从来没有看到那个处理程序的任何影响 运行 因为事件是在不同的线程上引发和处理的。我真的需要将服务 UUID 传递给扫描逻辑,并与我可以 return 来自 ScanForService 的通用列表进行交互,所以只是将它全部移动到处理程序并没有这似乎是一个很有前途的方向。所以我创建了一个单例来标记状态:
internal sealed class ManagerState // .NET makes singletons easy - Lazy<T> FTW
{
private static readonly Lazy<ManagerState> lazy = new Lazy<ManagerState>(() => new ManagerState());
internal static ManagerState Instance { get { return ManagerState.lazy.Value; } }
internal bool IsPoweredOn { get; set; }
private ManagerState()
{
this.IsPoweredOn = false;
}
}
并在处理程序中更新它:
private void updatedState(object sender, EventArgs args)
{
ManagerState.Instance.IsPoweredOn = CBCentralManagerState.PoweredOn == ((CBCentralManager) sender).State;
}
然后在 ScanForService 开始时轮询(每次都在一个单独的线程中,因为我不会在我的基本线程中看到更新):
while (false == await Task.Run(() => ManagerState.Instance.IsPoweredOn)) { }
我完全不确定这是最好的解决方案,但它确实有效,至少对我而言是这样。我想我可以将逻辑移至处理程序并创建一个更漂亮的单例 class 来回移动所有状态,但这对我来说感觉不太好。
我正在尝试创建一个 Xamarin.Forms 应用程序,它将在 iOS 和 Android 上 运行。最终我需要应用程序的实例通过蓝牙相互通信,但我坚持让 iOS 端使用蓝牙做任何事情。我最初尝试使用 Plugin.BluetoothLE 和 Plugin.BLE,但一周半后我无法使用让广告或扫描在任一插件上工作 OS,所以我决定尝试使用平台 API 的 .NET 包装器实现简单的蓝牙交互,至少有详细记录。我确实在 Android 端进行了扫描以正常工作。但是,使用 iOS,我现在所拥有的构建得很好,并且 运行s 在我的 iPad 上没有错误,但是 DiscoveredPeripheral 处理程序是从未调用过,即使 iPad 距离 Android 平板电脑只有几英寸,而且大概应该能够看到相同的设备。我已经通过在该方法中设置断点来验证这一点,该断点从未达到过;当我打开 iPad 上的蓝牙设置以使其可发现时,Android 平板电脑上的应用程序版本可以看到它,所以我认为这不是 iPad 硬件问题。
似乎很明显,我不知道该过程的某些部分是什么,但(对我而言)不明显还可以去哪里寻找它是什么。这是与 CBCentralManager 交互的 class 的代码(据我所读,这应该包括 return 所需的一切外围设备列表):
using MyBluetoothApp.Shared; // for the interfaces and constants
using CoreBluetooth;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
[assembly: Dependency(typeof(MyBluetoothApp.iOS.PeripheralScanner))]
namespace MyBluetoothApp.iOS
{
public class PeripheralScanner : IPeripheralScanner
{
private readonly CBCentralManager manager;
private List<IPeripheral> foundPeripherals;
public PeripheralScanner()
{
this.foundPeripherals = new List<IPeripheral>();
this.manager = new CBCentralManager();
this.manager.DiscoveredPeripheral += this.DiscoveredPeripheral;
this.manager.UpdatedState += this.UpdatedState;
}
public async Task<List<IPeripheral>> ScanForService(string serviceUuid)
{
return await this.ScanForService(serviceUuid, BluetoothConstants.DEFAULT_SCAN_TIMEOUT);
}
public async Task<List<IPeripheral>> ScanForService(string serviceUuid, int duration)
{
CBUUID uuid = CBUUID.FromString(serviceUuid);
//this.manager.ScanForPeripherals(uuid);
this.manager.ScanForPeripherals((CBUUID)null); // For now I'd be happy to see ANY peripherals
await Task.Delay(duration);
this.manager.StopScan();
return this.foundPeripherals;
}
private void DiscoveredPeripheral(object sender, CBDiscoveredPeripheralEventArgs args)
{
this.foundPeripherals.Add(new CPeripheral(args.Peripheral));
}
private void UpdatedState(object sender, EventArgs args)
{
CBCentralManagerState state = ((CBCentralManager)sender).State;
if (CBCentralManagerState.PoweredOn != state)
{
throw new Exception(state.ToString());
}
}
}
}
任何人都可以指出我所缺少的方向吗?
编辑:好吧...好吧,我偶然发现如果我在共享代码中这样做:
IPeripheralScanner scanner = DependencyService.Get<IPeripheralScanner>();
List<IPeripheral> foundPeripherals = await scanner.ScanForService(BluetoothConstants.VITL_SERVICE_UUID);
连续两次,第二次有效。我感到既充满希望又更加困惑。
潜在的问题是,在 PeripheralScanner 的第一个实例中,ScanForService 在 State 更新之前被调用。我尝试了很多方法来等待引发该事件,因此我可以确定状态为 PoweredOn,但似乎没有任何效果;轮询循环根本就没有达到所需的状态,但是如果我在 UpdatedState 处理程序中抛出异常,它会在启动后的几毫秒内抛出,并且当时的状态始终是 PoweredOn 。 (该处理程序中的断点导致调试冻结并输出 Resolved pending breakpoint,甚至 VS 团队似乎也无法解释)。
阅读一些 Apple 开发人员博客后,我发现这种情况通常可以通过在 UpdatedState 处理程序中发生所需的操作来避免。它终于浸透了我的头脑,我从来没有看到那个处理程序的任何影响 运行 因为事件是在不同的线程上引发和处理的。我真的需要将服务 UUID 传递给扫描逻辑,并与我可以 return 来自 ScanForService 的通用列表进行交互,所以只是将它全部移动到处理程序并没有这似乎是一个很有前途的方向。所以我创建了一个单例来标记状态:
internal sealed class ManagerState // .NET makes singletons easy - Lazy<T> FTW
{
private static readonly Lazy<ManagerState> lazy = new Lazy<ManagerState>(() => new ManagerState());
internal static ManagerState Instance { get { return ManagerState.lazy.Value; } }
internal bool IsPoweredOn { get; set; }
private ManagerState()
{
this.IsPoweredOn = false;
}
}
并在处理程序中更新它:
private void updatedState(object sender, EventArgs args)
{
ManagerState.Instance.IsPoweredOn = CBCentralManagerState.PoweredOn == ((CBCentralManager) sender).State;
}
然后在 ScanForService 开始时轮询(每次都在一个单独的线程中,因为我不会在我的基本线程中看到更新):
while (false == await Task.Run(() => ManagerState.Instance.IsPoweredOn)) { }
我完全不确定这是最好的解决方案,但它确实有效,至少对我而言是这样。我想我可以将逻辑移至处理程序并创建一个更漂亮的单例 class 来回移动所有状态,但这对我来说感觉不太好。