关于xamarin:MASA-MAUI-Plugin-IOS蓝牙低功耗三蓝牙扫描

5次阅读

共计 7166 个字符,预计需要花费 18 分钟才能阅读完成。

我的项目背景

MAUI 的呈现,赋予了宽广 Net 开发者开发多平台利用的能力,MAUI 是 Xamarin.Forms 演变而来,然而相比 Xamarin 性能更好,可扩展性更强,构造更简略。然而 MAUI 对于平台相干的实现并不残缺。所以 MASA 团队发展了一个实验性我的项目,意在对微软 MAUI 的补充和扩大,
我的项目地址:https://github.com/BlazorComp…

每个性能都有独自的 demo 演示我的项目,思考到 app 安装文件体积(尽管 MAUI 曾经集成裁剪性能,然而该性能对于代码自身有影响),届时每一个性能都会以独自的 nuget 包的模式提供,不便测试,当初我的项目才刚刚开始,然而置信很快就会有能够交付的内容啦。

前言

本系列文章面向挪动开发小白,从零开始进行平台相干性能开发,演示如何参考平台的官网文档应用 MAUI 技术来开发相应性能。

介绍

之前两篇文章咱们实现了安卓蓝牙 BLE 的相干性能,本文咱们将 IOS 的 BLE 性能实现一下。
,思考到 Swift 语法对于 c# 开发人员更敌对,本文示例代码参考 Swift,相干代码来自苹果开发者官网
https://developer.apple.com/d…

开发步骤

批改我的项目

Masa.Blazor.Maui.Plugin.Bluetooth 我的项目中的 Platforms->iOS 文件夹下,增加一个局部类 MasaMauiBluetoothService,在安卓中有BluetoothManager,在 ios 中对应的是CBCentralManager,然而不同有安卓还有个适配器 Adapter 的概念,在 ios 中对于设施扫描、连贯和治理外围设备的对象,都是通过CBCentralManager 间接治理的,咱们看一下他的初始化办法

init(
    delegate: CBCentralManagerDelegate?,
    queue: DispatchQueue?,
    options: [String : Any]? = nil
)

delegate:接管核心事件的委托。相当于咱们在装置中实现的 DevicesCallback

queue:用于调度核心角色事件的调度队列。如果该值为 nil,则地方管理器将应用主队列分派核心角色事件。这个咱们能够简略的了解为和安卓的 UI 线程或者后盾线程对应,更详尽的阐明请参考
https://developer.apple.com/d…

options:配置信息,咱们这里只用到了 ShowPowerAlert,代表蓝牙设施如果不可用,给用户提示信息。就好比你用了不符合标准的数据线,iphone 会给你提醒是一个意思。

 public static partial class MasaMauiBluetoothService
    {private static BluetoothDelegate _delegate = new();
        public static CBCentralManager _manager = new CBCentralManager(_delegate, DispatchQueue.DefaultGlobalQueue, new CBCentralInitOptions
        {ShowPowerAlert = true,});
        private sealed class BluetoothDelegate : CBCentralManagerDelegate
        {private readonly EventWaitHandle _eventWaitHandle = new(false, EventResetMode.AutoReset);

            public List<BluetoothDevice> Devices {get;} = new();

            public void WaitOne()
            {Task.Run(async () =>
                {await Task.Delay(5000);
                    _eventWaitHandle.Set();});

                _eventWaitHandle.WaitOne();}
            public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral,
                NSDictionary advertisementData,
                NSNumber RSSI)
            {System.Diagnostics.Debug.WriteLine("OnScanResult");
                if (!Devices.Contains(peripheral))
                {Devices.Add(peripheral);
                }
            }
            [Preserve]
            public override void UpdatedState(CBCentralManager central)
            {}}
    }

咱们将 MasaMauiBluetoothService 批改为动态类,
咱们自定义的 BluetoothDelegate 继承自 CBCentralManagerDelegate,篇幅问题咱们这里先只重写DiscoveredPeripheralUpdatedState,咱们这次的演示不须要实现 UpdatedState,然而这里的重写必须先放上去,否则调试过程会呈现上面的报错

ObjCRuntime.ObjCException: ‘Objective-C exception thrown. Name: NSInvalidArgumentException Reason: -[Masa_Blazor_Maui_Plugin_Bluetooth_MasaMauiBluetoothService_BluetoothDelegate centralManagerDidUpdateState:]: unrecognized selector sent to instance 0x284bfe200

另外有一点须要特地留神,这个 UpdatedState 办法我没有实现的代码,那么我就须要增加一个 [Preserve],这样是为了避免 链接器 在生成 nuget 包的时候把这个办法帮我优化掉。

实现发现左近设施性能,_eventWaitHandle和安卓一样,我这里只是实现了一个异步转同步不便间接通过 Devices 拿到后果,如果小伙伴不喜爱前期我会增加不阻塞的形式。
这里之所以能够 Devices.ContainsDevices.Add是因为咱们在 BluetoothDevice 类中实现了隐式转换
如下是 iOS 目录下 BluetoothDevice.ios.cs 的局部代码

    partial class BluetoothDevice
    {
        ...
        private BluetoothDevice(CBPeripheral peripheral)
        {_peripheral = peripheral;}

        public static implicit operator BluetoothDevice(CBPeripheral peripheral)
        {return peripheral == null ? null : new BluetoothDevice(peripheral);
        }

        public static implicit operator CBPeripheral(BluetoothDevice device)
        {return device._peripheral;}
        ...

ios 扫描外围设备是通过 scanForPeripherals
咱们持续在 MasaMauiBluetoothService 增加一个扫描附件设施的办法,咱们看一下 Swift 的文档

func scanForPeripherals(withServices serviceUUIDs: [CBUUID]?,
    options: [String : Any]? = nil
)

serviceUUIDs:代表须要过滤的服务 UUID,相似安卓的 scanFilter 对象。
option:提供扫描的选项,咱们这里用到了 AllowDuplicatesKey,该值指定扫描是否应在不反复筛选的状况下运行
咱们参照实现以下咱们的 PlatformScanForDevices 办法

        private static async Task<IReadOnlyCollection<BluetoothDevice>> PlatformScanForDevices()
        {if (!_manager.IsScanning)
            {_manager.ScanForPeripherals(new CBUUID[] { }, new PeripheralScanningOptions
                {AllowDuplicatesKey = true});

                await Task.Run(() => { _delegate.WaitOne(); });

                _manager.StopScan();
                _discoveredDevices = _delegate.Devices.AsReadOnly();}


            return _discoveredDevices;
        }

通过 _cbCentralManager.IsScanning来判断是否处于扫描状态,如果没有,那就就通过 ScanForPeripherals 扫描外围设备,扫描 5 秒之后(BluetoothDelegate 外部管制)通过 StopScan 进行扫描,并通过 _discoveredDevices 保留后果。
咱们还需实现 PlatformIsEnabledIsEnabledPlatformCheckAndRequestBluetoothPermission办法,用来在扫描之前查看蓝牙是否可用并且曾经通过用户受权

        public static bool PlatformIsEnabledIsEnabled()
        {return _manager.State == CBManagerState.PoweredOn;}
        public static async Task<PermissionStatus> PlatformCheckAndRequestBluetoothPermission()
        {PermissionStatus status = await Permissions.CheckStatusAsync<BluetoothPermissions>();

            if (status == PermissionStatus.Granted)
                return status;

            if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
            {
                // Prompt the user to turn on in settings
                // On iOS once a permission has been denied it may not be requested again from the application
                return status;
            }

            status = await Permissions.RequestAsync<BluetoothPermissions>();
               
            return status;
        }
        private class BluetoothPermissions : Permissions.BasePlatformPermission
        {
            protected override Func<IEnumerable<string>> RequiredInfoPlistKeys
                =>
                    () => new string[] {"NSBluetoothAlwaysUsageDescription", "NSBluetoothPeripheralUsageDescription"};

            public override Task<PermissionStatus> CheckStatusAsync()
            {EnsureDeclared(); 
                return Task.FromResult(GetBleStatus());
            }
            
            private PermissionStatus GetBleStatus() //Todo:Needs to be replenished
            {
                var status = _cbCentralManager.State;
                return status switch
                {
                    CBManagerState.PoweredOn=> PermissionStatus.Granted,
                    CBManagerState.Unauthorized => PermissionStatus.Denied,
                    CBManagerState.Resetting => PermissionStatus.Restricted,
                    _ => PermissionStatus.Unknown,
                };
            }
        }

PlatformIsEnabledIsEnabled 办法中通过 _cbCentralManager.State == CBManagerState.PoweredOn 来判断蓝牙是否可用。该状态一共有如下枚举,从字面意思很好了解

**Unknown**, // 手机没有辨认到蓝牙
**Resetting**, // 手机蓝牙已断开连接
**Unsupported**, // 手机蓝牙性能没有权限
**Unauthorized**, // 手机蓝牙性能没有权限
**PoweredOff**,// 手机蓝牙性能敞开
**PoweredOn** // 蓝牙开启且可用

权限查看这里和安卓有一些区别,在重写的 RequiredInfoPlistKeys 办法中指定了须要查看的蓝牙权限,BasePlatformPermissionEnsureDeclared 办法用来查看是否在 Info.plist 文件增加了须要的权限,GetBleStatus办法通过 _cbCentralManager 的状态,来查看受权状况。

咱们在 Masa.Blazor.Maui.Plugin.Bluetooth 的根目录增加局部类 MasaMauiBluetoothService.cs,向使用者提供ScanForDevicesAsync 等办法,办法外部通过 PlatformScanForDevices 来调用具体平台的实现。

    public static partial class MasaMauiBluetoothService
    {
        private static IReadOnlyCollection<BluetoothDevice> _discoveredDevices;
        public static Task<IReadOnlyCollection<BluetoothDevice>> ScanForDevicesAsync()
        {return PlatformScanForDevices();
        }
        
        public static bool IsEnabled()
        {return PlatformIsEnabledIsEnabled();
        }

        public static async Task<PermissionStatus> CheckAndRequestBluetoothPermission()
        {return await PlatformCheckAndRequestBluetoothPermission();
        }
    }

应用

右键 Masa.Blazor.Maui.Plugin.Bluetooth 我的项目,点击打包,生成一个 nuget 包,在 Masa.Blazor.Maui.Plugin.BlueToothSample 我的项目中离线装置即可,代码的应用与安卓齐全一样,只是权限配置形式不同
Masa.Blazor.Maui.Plugin.BlueToothSample我的项目的 Platforms->iOS->Info.plist 中增加蓝牙相干权限

    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>App required to access Bluetooth</string>
    <key>NSBluetoothPeripheralUsageDescription</key>
    <string>App required to access Bluetooth</string>

NSBluetoothAlwaysUsageDescription对应 iOS 13 以上版本,对于 iOS 13 之前的版本,须要将 NSBluetoothAlwaysUsageDescriptionNSBluetoothPeripheralUsageDescription同时增加。

蓝牙扫描的成果和安卓机是齐全一样的,这里就不展现了。前文详情

iOS 调试及谬误排查

目前在 windows 的 vs 环境调试 MAUI 的 ios 程序,是不须要 mac 电脑反对的,数据线连上后会显示一个本地设施,然而你依然须要一个开发者账号,vs 会调用 apple 开发者 api 主动帮你配置好须要的证书。

1、如果没有显示查看 Xamarin->iOS 设置,热重启是否开启

2、调试过程如果提醒相似
Could not find executable for C:\Users\xxx\AppData\Local\Temp\hbjayi2h.ydn
找不到文件的状况,右键抉择清理我的项目即可,如果无奈解决手动删除 bin 和 obj 目录重试

3、调试过程如果 app 无端退出,排查一下思考 APP 的启动和调试断点工夫,iOS 要求所有办法必须在 17 秒之内返回,否则 iOS 零碎将进行该利用

4、调试过程呈现 Deploy Error: An Lockdown error occurred. The error code was “MuxError” 的谬误,请查看你的数据线,从新插拔或者更换原装线。


本文到此结束。

如果你对咱们 MASA 感兴趣,无论是代码奉献、应用、提 Issue,欢送分割咱们

WeChat:MasaStackTechOps
QQ:7424099

正文完
 0