【问题标题】:Xamarin iOS Bluetooth peripheral scanning never sees any peripheralsXamarin iOS 蓝牙外围设备扫描永远不会看到任何外围设备
【发布时间】:2021-06-08 23:18:17
【问题描述】:

我正在尝试创建一个可在 iOS 和 Android 上运行的 Xamarin.Forms 应用。最终我需要应用程序的实例通过蓝牙相互通信,但我坚持让 iOS 端使用蓝牙做任何事情。我最初尝试使用 Plugin.BluetoothLEPlugin.BLE,但一周半后,我无法在任何一个操作系统上使用广告或扫描任何一个插件,所以我决定尝试使用平台 API 的 .NET 包装器来实现简单的蓝牙交互,至少有据可查。我确实让扫描在 Android 端正常工作。但是,对于 iOS,我现在的构建很好,并且在我的 iPad 上运行没有错误,但是 DiscoveredPeripheral 处理程序从未被调用,即使 iPad 距离 Android 平板电脑只有几英寸并且大概应该能够看到相同的设备。我已经通过在该方法中设置断点来验证这一点,该断点永远不会到达;当我在 iPad 上打开蓝牙设置以使其可发现时,Android 平板电脑上的应用程序版本可以看到它,所以我认为这不是 iPad 硬件问题。

似乎很明显,我不知道该过程的某些部分是什么,但(对我而言)还不清楚还有什么地方可以找出它是什么。这是与 CBCentralManager 交互的类的代码(据我了解,这应该包括返回外围设备列表所需的一切):

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);

连续两次,第二次有效。我感到既充满希望又更加困惑。

【问题讨论】:

    标签: ios .net-core xamarin.ios ios-bluetooth


    【解决方案1】:

    潜在的问题是,在 PeripheralScanner 的第一次实例化中,ScanForService 在状态更新之前被调用。我尝试了很多方法来等待该事件被引发,这样我就可以确定状态是PoweredOn,但似乎没有任何效果;轮询循环根本无法达到所需的状态,但如果我在 UpdatedState 处理程序中抛出异常,它会在启动后的几毫秒内抛出,并且当时的状态始终是 PoweredOn。 (该处理程序中的断点导致调试冻结并输出 Resolved pending breakpoint,甚至 VS 团队似乎都无法解释)。

    阅读了一些 Apple 开发者博客,我发现这种情况通常可以通过在 UpdatedState 处理程序中发生所需的操作来避免。终于让我头脑清醒了,我从来没有看到那个处理程序运行产生的任何影响,因为事件是在不同的线程上引发和处理的。我真的需要将服务 UUID 传递给扫描逻辑,并与我可以从 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)) { }
    

    我完全不确定这是不是最好的解决方案,但它确实有效,至少在我的情况下是这样。我想我可以将逻辑移动到处理程序并创建一个更漂亮的单例类来来回移动所有状态,但这对我来说感觉不太好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-08
      • 1970-01-01
      • 2017-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-13
      相关资源
      最近更新 更多