專案背景
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,篇幅問題我們這裡先只重寫DiscoveredPeripheral和 UpdatedState,我們這次的演示不需要實現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.Contains和Devices.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 儲存結果。
我們還需實現PlatformIsEnabledIsEnabled和PlatformCheckAndRequestBluetoothPermission方法,用來在掃描之前檢查藍芽是否可用並且已經經過使用者授權
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方法中指定了需要檢查的藍芽許可權,BasePlatformPermission的EnsureDeclared方法用來檢查是否在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之前的版本,需要將NSBluetoothAlwaysUsageDescription和NSBluetoothPeripheralUsageDescription同時新增。
藍芽掃描的效果和安卓機是完全一樣的,這裡就不展示了。前文詳情
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