项目背景

MAUI的出现,赋予了广大Net开发者开发多渠道运用的能力,MAUI 是Xamarin.Forms演变而来,可是比较Xamarin性能更好,可扩展性更强,结构更简单。可是MAUI关于渠道相关的完结并不完好。所以MASA团队展开了一个实验性项目,意在对微软MAUI的补充和扩展,项目地址:github.com/BlazorCompo…

每个功用都有独自的demo演示项目,考虑到app安装文件体积(虽然MAUI现已集成裁剪功用,可是该功用关于代码本身有影响),届时每一个功用都会以独自的nuget包的形式供给,方便测验,现在项目才刚刚开始,可是信任很快就会有能够交给的内容啦。

前言

本系列文章面向移动开发小白,从零开始进行渠道相关功用开发,演示怎样参考渠道的官方文档运用MAUI技能来开发相应功用。

介绍

微软的MAUI并没有供给蓝牙低功耗设备的相关功用,而物联网开发中蓝牙低功耗是非常常见的,所以咱们今天自己集成一个。 由于蓝牙功用设计的内容比较多,篇幅有限,本文只集成一个最基本的蓝牙扫描功用,意在抛砖引玉。后续会陆续更新其他蓝牙通讯功用的文章。本文蓝牙低功耗简称为BLE 假如你对BLE的相关概念不了解,能够参考 开发者官网链接: 蓝牙低功耗-安卓 developer.android.google.cn/guide/topic…

本文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 获取BluetoothAdapterBluetoothAdapter代表设备本身的蓝牙适配器,之后的蓝牙操作都需求经过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内部类DevicesCallbackScanCallback类 对应安卓的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一个办法。当有设备被扫描到就会触发这个办法,然后就能够经过ScanResultDevice特点来获取设备信息。 咱们在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。 修改MauiBlueToothDemoIndex页面,页面运用了对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

咱们真机运转一下看看作用

MASA MAUI Plugin 安卓蓝牙低功耗(一)蓝牙扫描

一起在vs的输出中能够看到打印的日志

MASA MAUI Plugin 安卓蓝牙低功耗(一)蓝牙扫描

本文到此结束,下一篇咱们完结具体的BLE的通讯。


假如你对咱们MASA感兴趣,无论是代码奉献、运用、提 Issue,欢迎联系咱们

  • WeChat:MasaStackTechOps
  • QQ:7424099