MASA MAUI Plugin 安卓藍芽低功耗(一)藍芽掃描
專案背景
MAUI的出現,賦予了廣大Net開發者開發多平臺應用的能力,MAUI 是Xamarin.Forms演變而來,但是相比Xamarin效能更好,可擴充套件性更強,結構更簡單。但是MAUI對於平臺相關的實現並不完整。所以MASA團隊開展了一個實驗性專案,意在對微軟MAUI的補充和擴充套件,專案地址:http://github.com/BlazorComponent/MASA.Blazor/tree/main/src/Masa.Blazor.Maui.Plugin
每個功能都有單獨的demo演示專案,考慮到app安裝檔案體積(雖然MAUI已經整合裁剪功能,但是該功能對於程式碼本身有影響),屆時每一個功能都會以單獨的nuget包的形式提供,方便測試,現在專案才剛剛開始,但是相信很快就會有可以交付的內容啦。
前言
本系列文章面向移動開發小白,從零開始進行平臺相關功能開發,演示如何參考平臺的官方文件使用MAUI技術來開發相應功能。
介紹
微軟的MAUI並沒有提供藍芽低功耗裝置的相關功能,而物聯網開發中藍芽低功耗是十分常見的,所以我們今天自己整合一個。 由於藍芽功能設計的內容比較多,篇幅有限,本文只整合一個最基本的藍芽掃描功能,意在拋磚引玉。後續會陸續更新其他藍芽通訊功能的文章。本文藍芽低功耗簡稱為BLE 如果你對BLE的相關概念不瞭解,可以參考 開發者官網連結: 藍芽低功耗-安卓 http://developer.android.google.cn/guide/topics/connectivity/bluetooth-le/ 本文JAVA相關程式碼均來自安卓開發者官網
開發步驟
新建專案
在vs中新建一個基於MAUI Blazor的專案MauiBlueToothDemo,然後新增一個MAUI類庫專案Masa.Maui.Plugin.Bluetooth
新增許可權
專案建立好了之後,我們首先介紹一下BLE需要的安卓許可權,相信大家對各種APP首次開啟的許可權確認彈窗應該不會陌生。
在應用中使用藍芽功能,必須宣告 BLUETOOTH 藍芽許可權,需要此許可權才能執行任何藍芽通訊,例如請求連線、接受連線和傳輸資料等。 由於 LE 信標通常與位置相關聯,還須宣告 ACCESS_FINE_LOCATION 許可權。沒有此許可權,掃描將無法返回任何結果。 如果適配 Android 9(API 級別 28)或更低版本,可以宣告 ACCESS_COARSE_LOCATION 許可權而非 ACCESS_FINE_LOCATION 許可權 如果想讓應用啟動裝置發現或操縱藍芽設定,還須宣告 BLUETOOTH_ADMIN 許可權。注意:如果使用 LUETOOTH_ADMIN 許可權,則您必須擁有 BLUETOOTH 許可權。 在MauiBlueToothDemo專案中的AndroidManifest.xml新增許可權,我們這裡面向Android 9以上版本。
<!--藍芽許可權-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!--讓應用啟動裝置發現或操縱藍芽設定-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 如果設配Android9及更低版本,可以申請 ACCESS_COARSE_LOCATION -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Android 6.0之後,只在AndroidManifest.xml宣告許可權已經不夠了,出於安全考慮,必須動態申請許可權,也就是需要在使用特定功能之前提示使用者進行許可權確認。 我們在Masa.Maui.Plugin.Bluetooth專案的Platforms_Android下新建MasaMauiBluetoothService類,並新增一個內部類BluetoothPermissions ,MAUI的預設許可權沒有包含藍芽低功耗,所以我們需要擴充套件一個自定義的藍芽許可權類,只要繼承自 Permissions.BasePermission即可
private class BluetoothPermissions : Permissions.BasePlatformPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new List<(string androidPermission, bool isRuntime)>
{
(global::Android.Manifest.Permission.AccessFineLocation, true),
(global::Android.Manifest.Permission.Bluetooth, true),
(global::Android.Manifest.Permission.BluetoothAdmin, true),
}.ToArray();
}
我們在MasaMauiBluetoothService類內部新增一個方法,來實現動態獲取許可權
public async Task<bool> CheckAndRequestBluetoothPermission()
{
var status = await Permissions.CheckStatusAsync<BluetoothPermissions>();
if (status == PermissionStatus.Granted)
return true;
status = await Permissions.RequestAsync<BluetoothPermissions>();
if (status == PermissionStatus.Granted)
return true;
return false;
}
檢查許可權的當前狀態,使用 Permissions.CheckStatusAsync 方法。 向用戶請求許可權,使用 Permissions.RequestAsync 方法。 如果使用者以前授予了許可權,並且尚未撤消該許可權,則此方法將返回 Granted 而不向使用者顯示對話方塊。
設定BLE
BLE的開發第一步驟就是設定BLE 為什麼要設定BLE,因為我們在使用BLE進行通訊之前,需要驗證裝置是否支援BLE或者檢查BLE是否開啟。我們先看一下java的實現方式
JAVA 程式碼
private BluetoothAdapter bluetoothAdapter;
...
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
在編寫平臺相關程式碼時,安卓的系統管理服務都是同getSystemService方法獲取的,該方法的引數為系統服務的名稱,對應在MAUI中的方法為Android.App.Application.Context.GetSystemService,流程是完全一樣的,語法稍有不同,我們如法炮製,在MasaMauiBluetoothService中新增一個建構函式,和兩個欄位
private readonly BluetoothManager _bluetoothManager;
private readonly BluetoothAdapter _bluetoothAdapter;
public MasaMauiBluetoothService()
{
_bluetoothManager = (BluetoothManager)Android.App.Application.Context.GetSystemService(Android.App.Application.BluetoothService);
_bluetoothAdapter = _bluetoothManager?.Adapter;
}
GetSystemService返回BluetoothManager 例項,然後通過BluetoothManager 獲取BluetoothAdapter, BluetoothAdapter代表裝置自身的藍芽介面卡,之後的藍芽操作都需要通過BluetoothAdapter完成 繼續在MasaMauiBluetoothService新增一個檢查藍芽介面卡是否存在並開啟的方法
public bool IsEnabled()
{
return _bluetoothAdapter is {IsEnabled: true};
}
BLE掃描
與BLE裝置通訊,首先需要掃描出附近的BLE裝置,我們先看看Java怎麼實現的
JAVA 程式碼
/**
* Activity for scanning and displaying available BLE devices.
*/
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter bluetoothAdapter;
private boolean mScanning;
private Handler handler;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
handler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
bluetoothAdapter.stopLeScan(leScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
bluetoothAdapter.startLeScan(leScanCallback);
} else {
mScanning = false;
bluetoothAdapter.stopLeScan(leScanCallback);
}
...
}
...
}
掃描裝置需要使用bluetoothAdapter.startLeScan方法,並指定一個BluetoothAdapter.LeScanCallback回撥方法作為引數 我們再看一下LeScanCallback的Java實現
JAVA 程式碼
private LeDeviceListAdapter leDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback leScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
leDeviceListAdapter.addDevice(device);
leDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
因為掃描很耗費資源,所以示例程式碼通過runOnUiThread設定掃描程序在裝置的前臺執行,掃描到裝置後觸發leScanCallback 回撥,然後通過私有的LeDeviceListAdapter欄位儲存掃描到的裝置列表。 我們如法炮製這部分功能,在MasaMauiBluetoothService中新增一個繼承自ScanCallback內部類DevicesCallback,ScanCallback類 對應安卓的leScanCallback
private class DevicesCallback : ScanCallback
{
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 OnScanResult(ScanCallbackType callbackType, ScanResult result)
{
System.Diagnostics.Debug.WriteLine("OnScanResult");
if (!Devices.Contains(result.Device))
{
Devices.Add(result.Device);
}
base.OnScanResult(callbackType, result);
}
}
篇幅問題我們這裡只重寫OnScanResult一個方法。當有裝置被掃描到就會觸發這個方法,然後就可以通過ScanResult的Device屬性來獲取裝置資訊。 我們在MAUI中列印除錯資訊可以使用System.Diagnostics.Debug.WriteLine真機除錯的資訊會被列印到vs的輸出控制檯。 我們新增一個屬性Devices用於彙總收集掃描到的裝置資訊。這裡使用了EventWaitHandle 用於在非同步操作時控制執行緒間的同步,執行緒在 EventWaitHandle 上將一直受阻,直到未受阻的執行緒呼叫 Set 方法,沒用過的可以自行檢視微軟文件。 繼續在MasaMauiBluetoothService新增欄位,並在建構函式初始化。
private readonly ScanSettings _settings;
private readonly DevicesCallback _callback;
public MasaMauiBluetoothService()
{
_bluetoothManager = (BluetoothManager)Android.App.Application.Context.GetSystemService(Android.App.Application.BluetoothService);
_bluetoothAdapter = _bluetoothManager?.Adapter;
_settings = new ScanSettings.Builder()
.SetScanMode(Android.Bluetooth.LE.ScanMode.Balanced)
?.Build();
_callback = new DevicesCallback();
}
這裡也很好理解,ScanSettings通過ScanSettings.Builder() 構造,用來配置藍芽的掃描模式,我們這裡使用平衡模式,具體式有如下三種:
ScanSettings.SCAN_MODE_LOW_POWER 低功耗模式(預設掃描模式,如果掃描應用程式不在前臺,則強制使用此模式。) ScanSettings.SCAN_MODE_BALANCED 平衡模式 ScanSettings.SCAN_MODE_LOW_LATENCY 高功耗模式(建議僅在應用程式在前臺執行時才使用此模式。)
最後新增ScanLeDeviceAsync方法
public async Task<IReadOnlyCollection<BluetoothDevice>> ScanLeDeviceAsync()
{
//第一個引數可以設定過濾條件-藍芽名稱,名稱字首,服務號等,這裡暫時不設定過濾條件
_bluetoothAdapter.BluetoothLeScanner.StartScan(null, _settings, _callback);
await Task.Run(() =>
{
_callback.WaitOne();
});
_bluetoothAdapter.BluetoothLeScanner.StopScan(_callback);
return _callback.Devices.AsReadOnly();
}
StartScan方法的第一個引數是過濾條件,可以根據名稱等進行過濾,我們暫不設定過濾。
測試
編譯Masa.Maui.Plugin.Bluetooth專案,然後在MauiBlueToothDemo專案中引用Masa.Maui.Plugin.Bluetooth.dll。 修改MauiBlueToothDemo的Index頁面,頁面使用了對MAUI支援良好的Masa Blazor元件: Masa Blazor
@page "/"
<MButton OnClick="ScanBLEDeviceAsync">掃描藍芽裝置</MButton>
<div class="text-center">
<MDialog @bind-Value="ShowProgress" Width="500">
<ChildContent>
<MCard>
<MCardTitle>
正在掃描藍芽裝置
</MCardTitle>
<MCardText>
<MProgressCircular Size="40" Indeterminate Color="primary"></MProgressCircular>
</MCardText>
</MCard>
</ChildContent>
</MDialog>
</div>
<MCard Class="mx-auto" MaxWidth="400" Tile>
@foreach (var item in BluetoothDeviceList)
{
<MListItem>
<MListItemContent>
<MListItemTitle>@item</MListItemTitle>
</MListItemContent>
</MListItem>
}
</MCard>
using Masa.Maui.Plugin.Bluetooth;
using Microsoft.AspNetCore.Components;
namespace MauiBlueToothDemo.Pages
{
public partial class Index
{
private bool ShowProgress { get; set; }
private List<string> BluetoothDeviceList { get; set; } = new();
[Inject]
private MasaMauiBluetoothService BluetoothService { get; set; }
private async Task ScanBLEDeviceAsync()
{
if (BluetoothService.IsEnabled())
{
if (await BluetoothService.CheckAndRequestBluetoothPermission())
{
ShowProgress = true;
var deviceList = await BluetoothService.ScanLeDeviceAsync();
BluetoothDeviceList = deviceList.Where(o => !string.IsNullOrEmpty(o.Name)).Select(o => o.Name).Distinct().ToList();
ShowProgress = false;
}
}
}
}
}
不要忘記在MauiProgram.cs注入寫好的MasaMauiBluetoothService
#if ANDROID
builder.Services.AddSingleton<MasaMauiBluetoothService>();
#endif
我們真機執行一下看看效果
同時在vs的輸出中可以看到列印的日誌
本文到此結束,下一篇我們實現具體的BLE的通訊。
如果你對我們MASA感興趣,無論是程式碼貢獻、使用、提 Issue,歡迎聯絡我們
WeChat:MasaStackTechOps QQ:7424099
- Blazor在IoT領域的前端實踐 @.NET開發者日
- MASA MAUI Plugin (十)iOS訊息推送(原生APNS方式)
- MASA MAUI Plugin (九)Android相簿多選照片(使用Android Jetpack套件庫)
- MASA MAUI Plugin (八)Android相簿多選照片(Intent 方式)
- MASA Stack 1.0 釋出會講稿——生態篇
- MASA Stack 1.0 釋出會講稿——實踐篇
- MASA Stack 1.0 釋出會講稿——產品篇
- MASA Stack 1.0 釋出會講稿——趨勢篇
- MASA MAUI Plugin (七)應用通知角標(小紅點)Android iOS
- .NET現代化應用開發 - CQRS&類目管理程式碼剖析
- MASA MAUI Plugin 安卓藍芽低功耗(二)藍芽通訊
- MASA MAUI Plugin 安卓藍芽低功耗(一)藍芽掃描
- MASA MAUI Plugin 安卓藍芽低功耗(二)藍芽通訊
- MASA MAUI Plugin 安卓藍芽低功耗(一)藍芽掃描
- MASA Framework的分散式鎖設計
- MAUI Masa Blazor 開發介面跟隨系統主題切換的App
- MAUI Masa Blazor 開發介面跟隨系統主題切換的App
- MAUI Masa Blazor 開發帶自動更新功能的安卓App
- 開篇-開啟全新的.NET現代應用開發體驗
- 怎麼樣的框架對於開發者是友好的?