写点什么

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

  • 2022 年 9 月 21 日
    浙江
  • 本文字数:6281 字

    阅读完需:约 21 分钟

项目背景

MAUI 的出现,赋予了广大 Net 开发者开发多平台应用的能力,MAUI 是 Xamarin.Forms 演变而来,但是相比 Xamarin 性能更好,可扩展性更强,结构更简单。但是 MAUI 对于平台相关的实现并不完整。所以 MASA 团队开展了一个实验性项目,意在对微软 MAUI 的补充和扩展,项目地址:https://github.com/BlazorComponent/MASA.Blazor/tree/main/src/Masa.Blazor.Maui.Plugin

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

前言

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

介绍

微软的 MAUI 并没有提供蓝牙低功耗设备的相关功能,而物联网开发中蓝牙低功耗是十分常见的,所以我们今天自己集成一个。由于蓝牙功能设计的内容比较多,篇幅有限,本文只集成一个最基本的蓝牙扫描功能,意在抛砖引玉。后续会陆续更新其他蓝牙通讯功能的文章。本文蓝牙低功耗简称为 BLE 如果你对 BLE 的相关概念不了解,可以参考 开发者官网链接: 蓝牙低功耗-安卓https://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 获取 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 一个方法。当有设备被扫描到就会触发这个方法,然后就可以通过 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

发布于: 刚刚阅读数: 7
用户头像

还未添加个人签名 2021.10.26 加入

MASA技术团队官方账号,我们专注于.NET现代应用开发解决方案,Wechat:MasaStackTechOps ,Website:www.masastack.com

评论

发布
暂无评论
MASA MAUI Plugin 安卓蓝牙低功耗(一)蓝牙扫描_.net_MASA技术团队_InfoQ写作社区