.NET MAUI安卓端蓝牙LE运行时权限检查与设置实现
.NET MAUI 安卓平台蓝牙LE运行时权限实现方案
不要在页面构造函数中执行权限申请、蓝牙扫描这类异步逻辑,构造函数执行时页面还未完成挂载,此时弹权限弹窗会出现上下文异常,相关逻辑应该放到页面的OnAppearing生命周期方法中触发。
安卓不同版本的蓝牙权限要求差异很大:安卓6.0到11(API23-30)蓝牙扫描依赖定位权限,安卓12(API31)及以上版本替换成了独立的蓝牙扫描、连接权限,MAUI内置的Permissions API没有覆盖这套新权限,我们可以通过自定义权限类统一适配,不需要自己手写分支判断SDK版本。
1. 自定义蓝牙权限类
继承MAUI提供的Permissions.BasePlatformPermission基类,实现跨版本的权限声明配置,这个类可以放在共享项目的任意目录下:
public class BluetoothLEPermissions : Permissions.BasePlatformPermission { public override (string androidPermission, bool isRuntime)[] RequiredPermissions { get { var permissionList = new List<(string, bool)>(); if (OperatingSystem.IsAndroidVersionAtLeast(31)) { // 安卓12+ 专属蓝牙运行时权限 permissionList.Add(("android.permission.BLUETOOTH_SCAN", true)); permissionList.Add(("android.permission.BLUETOOTH_CONNECT", true)); // 需要蓝牙广播功能就放开下面这行 // permissionList.Add(("android.permission.BLUETOOTH_ADVERTISE", true)); } else { // 安卓6-11 蓝牙扫描需要定位权限 permissionList.Add(("android.permission.ACCESS_FINE_LOCATION", true)); } // 旧版本系统兼容的基础蓝牙权限,不需要动态申请 permissionList.Add(("android.permission.BLUETOOTH", false)); permissionList.Add(("android.permission.BLUETOOTH_ADMIN", false)); return permissionList.ToArray(); } } }
2. 权限检测与申请逻辑
自定义权限类可以直接复用MAUI原生的Permissions API,不需要自己调用安卓原生的权限检测方法:
- 权限检测:调用
Permissions.CheckStatusAsync<BluetoothLEPermissions>()就能直接拿到当前应用的蓝牙权限状态,自动适配不同安卓版本 - 权限申请:未授权时调用
Permissions.RequestAsync<BluetoothLEPermissions>(),系统会自动弹出原生权限申请弹窗,不需要自己写自定义Alert提前询问用户 - 永久拒绝处理:如果用户勾选了“不再询问”拒绝权限,后续调用申请方法不会再弹弹窗,需要引导用户跳转到系统设置页手动开启权限
3. 页面完整实现代码
把原来写在构造函数里的逻辑迁移到OnAppearing生命周期方法中,参考实现如下:
using Android.OS; namespace TSDZ2Monitor.Pages; public partial class BluetoothPage : ContentPage { public BluetoothPage() { InitializeComponent(); Console.WriteLine("Bluetooth here"); } protected override async void OnAppearing() { base.OnAppearing(); await CheckBluetoothPermissionAndStartScan(); } private async Task CheckBluetoothPermissionAndStartScan() { // 先检查现有权限状态 var permissionStatus = await Permissions.CheckStatusAsync<BluetoothLEPermissions>(); if (permissionStatus == PermissionStatus.Granted) { StartBleScan(); return; } // 发起权限申请 permissionStatus = await Permissions.RequestAsync<BluetoothLEPermissions>(); if (permissionStatus == PermissionStatus.Granted) { StartBleScan(); return; } // 处理权限拒绝场景 if (DeviceInfo.Platform == DevicePlatform.Android) { // 判断是否是永久拒绝(用户选了不再询问) bool needShowRationale = await Permissions.ShouldShowRationaleAsync<BluetoothLEPermissions>(); if (!needShowRationale) { bool goToSettings = await DisplayAlert("权限不足", "蓝牙权限已被永久拒绝,请前往应用设置页手动开启权限后使用", "去设置", "取消"); if (goToSettings) { // 跳转到应用系统设置页 AppInfo.ShowSettingsUI(); } return; } } // 普通拒绝场景,提示权限用途 await DisplayAlert("权限不足", "需要蓝牙权限才能扫描周边的心率带等外设设备,拒绝授权将无法使用对应功能", "确定"); } private void StartBleScan() { // 这里写Plugin.BLE的设备扫描逻辑即可 Console.WriteLine("启动蓝牙扫描"); } }
补充说明
你之前在AndroidManifest.xml里添加的权限声明已经覆盖了所有版本的需求,如果你的应用不需要通过蓝牙扫描获取物理位置,可以给BLUETOOTH_SCAN权限加上neverForLocation标记,避免应用商店审核时要求你提供位置权限的使用说明:
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
如果你采用MVVM架构,也可以把权限检查逻辑放到ViewModel中,只要通过页面的OnAppearing事件触发对应命令即可,不要在ViewModel构造函数中执行异步权限操作。
内容的提问来源于stack exchange,提问作者gfmoore
相关产品推荐
相关产品推荐

