乐趣区

关于.net:MASA-MAUI-Plugin-安卓蓝牙低功耗一蓝牙扫描

我的项目背景

MAUI 的呈现,赋予了宽广 Net 开发者开发多平台利用的能力,MAUI 是 Xamarin.Forms 演变而来,然而相比 Xamarin 性能更好,可扩展性更强,构造更简略。然而 MAUI 对于平台相干的实现并不残缺。所以 MASA 团队发展了一个实验性我的项目,意在对微软 MAUI 的补充和扩大,我的项目地址:https://github.com/BlazorComp…,每个性能都有独自的 demo 演示我的项目,思考到 app 安装文件体积(尽管 MAUI 曾经集成裁剪性能,然而该性能对于代码自身有影响),届时每一个性能都会以独自的 nuget 包的模式提供,不便测试,当初我的项目才刚刚开始,然而置信很快就会有能够交付的内容啦。

前言

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

介绍

微软的 MAUI 并没有提供蓝牙低功耗设施的相干性能,而物联网开发中蓝牙低功耗是非常常见的,所以咱们明天本人集成一个。
因为蓝牙功能设计的内容比拟多,篇幅无限,本文只集成一个最根本的蓝牙扫描性能,意在抛砖引玉。后续会陆续更新其余蓝牙通信性能的文章。本文蓝牙低功耗简称为 BLE
如果你对 BLE 的相干概念不理解,能够参考 开发者官网链接: 蓝牙低功耗 - 安卓
https://developer.android.goo…
本文 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

咱们真机运行一下看看成果

同时在 vs 的输入中能够看到打印的日志

本文到此结束,下一篇咱们实现具体的 BLE 的通信。


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

WeChat:MasaStackTechOps
QQ:7424099

退出移动版