Flutter permission_handler 系统权限处理库理解

permission_handler 是 Flutter 中用于处理应用程序权限请求的库。通过这个库,你可以轻松管理 Android 和 iOS 系统中不同权限的请求状态、权限检查以及权限状态的处理。

在不同平台(iOS 和 Android)以及不同版本的 Android 系统中,权限的请求和处理方式会有所不同。接下来我将详细讲解这个库的使用、在 iOS 和 Android 系统中的区别,以及 Android 版本间的差异。

1. 添加依赖

首先,必须在 pubspec.yaml 文件中添加 permission_handler 依赖:

dependencies:
  permission_handler: ^10.2.0

然后执行 flutter pub get 获取依赖。

2. 常见权限类型

permission_handler 中,权限被抽象为 Permission 类,常见的权限有:

  • 相机Permission.camera
  • 位置Permission.locationPermission.locationAlways, Permission.locationWhenInUse
  • 麦克风Permission.microphone
  • 存储Permission.storage
  • 通知Permission.notification
  • 电话Permission.phone
  • 蓝牙Permission.bluetooth, Permission.bluetoothScan, Permission.bluetoothAdvertise, Permission.bluetoothConnect

3. 基本使用

获取权限状态、请求权限的基本流程如下:

import 'package:permission_handler/permission_handler.dart';

Future<void> checkAndRequestPermission() async {
  // 检查当前权限状态
  var status = await Permission.camera.status;

  if (status.isGranted) {
    // 权限已经授予
    print("Camera permission granted.");
  } else if (status.isDenied) {
    // 请求权限
    PermissionStatus result = await Permission.camera.request();
    if (result.isGranted) {
      print("Camera permission granted after request.");
    } else {
      print("Camera permission denied.");
    }
  }
}

4. iOS 和 Android 平台的差异

iOS 平台

  • 权限声明:在 iOS 中,你必须在 Info.plist 中声明每个你需要使用的权限,否则应用会崩溃。这里是几个常见权限的声明:
<key>NSCameraUsageDescription</key>
<string>We need access to the camera for scanning QR codes.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need access to your location while the app is in use.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to the microphone for voice recording.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library for selecting images.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>We need access to Bluetooth to connect to nearby devices.</string>
  • 权限的即时处理:在 iOS 中,权限通常是一次性的,如果用户选择拒绝,权限对话框不会再次出现,除非用户手动在系统设置中进行修改。你可以引导用户前往设置:
await openAppSettings();

iOS 权限请求注意事项

  • Permission.locationAlways 在 iOS 中需要 NSLocationAlwaysUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescription
  • 通知权限 Permission.notification 需要通过 request() 方法请求,它返回的是一个布尔值,表示是否成功注册通知。

Android 平台

Android 6.0(API 23)及以上

自 Android 6.0(API 23)起,权限被细分为正常权限危险权限

  • 正常权限:自动授予,无需用户同意,如 INTERNET
  • 危险权限:用户必须在运行时同意,如位置、摄像头等。permission_handler 会自动处理危险权限的请求和管理。
Android 10(API 29)
  • Android 10 引入了一些新的权限特性,比如位置权限。应用程序在后台请求位置权限时,需申请 Permission.locationAlways,否则应用在后台无法访问位置。
Android 11(API 30)
  • Android 11 对存储权限进行了重大更改,引入了Scoped Storage 概念。对于存储权限,使用 Permission.storage 可以处理,但对于访问应用沙盒之外的文件系统(如读取/写入外部存储器),你需要额外处理。
Android 12(API 31)
  • Android 12 新增了一些权限,尤其是蓝牙相关的权限,比如:
    • Permission.bluetoothScan
    • Permission.bluetoothConnect
    • Permission.bluetoothAdvertise

这些权限必须单独请求,否则应用将无法与蓝牙设备通信。

  • Android 12 还增加了更加严格的通知权限管理机制。如果你的应用需要通知权限,你需要在 Android 13 中显式请求 Permission.notification

5. Android 权限声明

在 Android 中,你需要在 AndroidManifest.xml 中声明所需的权限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

针对 Android 10+ 的位置权限变化:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

针对 Android 12+ 的蓝牙权限:

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" android:required="true"/>

6. 处理 Android 版本差异

permission_handler 中,你可以根据 Android 版本来动态调整权限的处理逻辑。例如,在请求权限时可以根据系统版本进行不同处理:

if (Platform.isAndroid && androidInfo.version.sdkInt >= 31) {
  // Android 12 (API 31) 新增权限
  await Permission.bluetoothScan.request();
  await Permission.bluetoothConnect.request();
} else {
  // 低版本 Android 处理
  await Permission.bluetooth.request();
}

7. 权限状态检查

可以通过 isGranted, isDenied, isPermanentlyDenied 等状态来检查权限是否授予或被拒绝:

if (await Permission.camera.isPermanentlyDenied) {
  // 用户永久拒绝,可能需要引导用户前往设置开启权限
  openAppSettings();
}

总结

  • iOS 平台:权限管理较为简单,但需要在 Info.plist 文件中声明对应权限。如果用户拒绝权限,你需要引导用户进入设置手动开启权限。

  • Android 平台:权限管理较为复杂,尤其是针对不同 Android 版本的权限处理。Android 6.0 之后,危险权限需要在运行时请求;而 Android 10、11 和 12 引入了新的权限规范和要求,开发者需要特别注意这些版本之间的区别。

下面是一个简单的Demo:

1. 创建统一入口:permission_service.dart

在这个文件中,我们创建一个统一的入口,用于调用各个平台的权限逻辑。

import 'dart:io';
import 'ios_per.dart';
import 'android_per.dart';

class PermissionService {
  static Future<void> requestPermissions() async {
    if (Platform.isIOS) {
      // 调用iOS权限处理逻辑
      await IosPermissionHandler().requestAllPermissions();
    } else if (Platform.isAndroid) {
      // 调用Android权限处理逻辑
      await AndroidPermissionHandler().requestAllPermissions();
    }
  }
}

2. iOS 处理逻辑:ios_per.dart

根据 iOS 平台的特性,比如要在 Info.plist 中添加权限声明,并处理权限被拒绝或永久拒绝的情况。

import 'package:permission_handler/permission_handler.dart';

class IosPermissionHandler {
  Future<void> requestAllPermissions() async {
    await _requestLocationPermission();
    await _requestBluetoothPermission();
    await _requestCameraPermission();
    await _requestStoragePermission();
    await _requestNotificationPermission();
    await _requestPhonePermission();
  }

  Future<void> _requestLocationPermission() async {
    PermissionStatus status = await Permission.location.request();
    if (status.isGranted) {
      print("iOS: 位置权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("iOS: 位置权限被永久拒绝,请前往设置开启");
    } else {
      print("iOS: 位置权限被拒绝");
    }
  }

  Future<void> _requestBluetoothPermission() async {
    PermissionStatus status = await Permission.bluetooth.request();
    if (status.isGranted) {
      print("iOS: 蓝牙权限已授予");
    } else {
      print("iOS: 蓝牙权限被拒绝");
    }
  }

  Future<void> _requestCameraPermission() async {
    PermissionStatus status = await Permission.camera.request();
    if (status.isGranted) {
      print("iOS: 相机权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("iOS: 相机权限被永久拒绝,请前往设置开启");
    } else {
      print("iOS: 相机权限被拒绝");
    }
  }

  Future<void> _requestStoragePermission() async {
    PermissionStatus status = await Permission.photos.request();
    if (status.isGranted) {
      print("iOS: 存储权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("iOS: 存储权限被永久拒绝,请前往设置开启");
    } else {
      print("iOS: 存储权限被拒绝");
    }
  }

  Future<void> _requestNotificationPermission() async {
    PermissionStatus status = await Permission.notification.request();
    if (status.isGranted) {
      print("iOS: 通知权限已授予");
    } else {
      print("iOS: 通知权限被拒绝");
    }
  }

  Future<void> _requestPhonePermission() async {
    PermissionStatus status = await Permission.phone.request();
    if (status.isGranted) {
      print("iOS: 电话权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("iOS: 电话权限被永久拒绝,请前往设置开启");
    } else {
      print("iOS: 电话权限被拒绝");
    }
  }
}

3. Android 处理逻辑:android_per.dart

Android 平台下,根据不同的 API 级别动态调整权限请求的处理,尤其是 Android 10、11、12 等版本的权限要求。

import 'package:permission_handler/permission_handler.dart';
import 'dart:io';

class AndroidPermissionHandler {
  Future<void> requestAllPermissions() async {
    await _requestLocationPermission();
    await _requestBluetoothPermission();
    await _requestCameraPermission();
    await _requestStoragePermission();
    await _requestNotificationPermission();
    await _requestPhonePermission();
  }

  Future<void> _requestLocationPermission() async {
    PermissionStatus status;
    if (Platform.isAndroid && Platform.version.sdkInt >= 29) {
      status = await Permission.locationAlways.request();
    } else {
      status = await Permission.locationWhenInUse.request();
    }

    if (status.isGranted) {
      print("Android: 位置权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("Android: 位置权限被永久拒绝,请前往设置开启");
    } else {
      print("Android: 位置权限被拒绝");
    }
  }

  Future<void> _requestBluetoothPermission() async {
    PermissionStatus status;
    if (Platform.isAndroid && Platform.version.sdkInt >= 31) {
      status = await [
        Permission.bluetoothScan,
        Permission.bluetoothConnect,
        Permission.bluetoothAdvertise,
      ].request();
    } else {
      status = await Permission.bluetooth.request();
    }

    if (status.isGranted) {
      print("Android: 蓝牙权限已授予");
    } else {
      print("Android: 蓝牙权限被拒绝");
    }
  }

  Future<void> _requestCameraPermission() async {
    PermissionStatus status = await Permission.camera.request();
    if (status.isGranted) {
      print("Android: 相机权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("Android: 相机权限被永久拒绝,请前往设置开启");
    } else {
      print("Android: 相机权限被拒绝");
    }
  }

  Future<void> _requestStoragePermission() async {
    PermissionStatus status = await Permission.storage.request();
    if (status.isGranted) {
      print("Android: 存储权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("Android: 存储权限被永久拒绝,请前往设置开启");
    } else {
      print("Android: 存储权限被拒绝");
    }
  }

  Future<void> _requestNotificationPermission() async {
    if (Platform.isAndroid && Platform.version.sdkInt >= 33) {
      PermissionStatus status = await Permission.notification.request();
      if (status.isGranted) {
        print("Android: 通知权限已授予");
      } else {
        print("Android: 通知权限被拒绝");
      }
    }
  }

  Future<void> _requestPhonePermission() async {
    PermissionStatus status = await Permission.phone.request();
    if (status.isGranted) {
      print("Android: 电话权限已授予");
    } else if (status.isPermanentlyDenied) {
      print("Android: 电话权限被永久拒绝,请前往设置开启");
    } else {
      print("Android: 电话权限被拒绝");
    }
  }
}

4. 如何调用

现在你可以通过 PermissionService 统一请求所有权限:

PermissionService.requestPermissions();

5. 重要处理细节

  • 权限被永久拒绝:当权限被永久拒绝时,应该引导用户进入系统设置手动开启权限,通过 openAppSettings() 函数可以实现。

  • 版本差异处理:Android 10、11、12 各个版本对存储、位置和蓝牙权限有不同的处理需求,代码里已经根据版本号做了动态处理。

通过这种方式,你可以在 iOS 和 Android 平台分别处理权限请求逻辑,同时确保统一的调用入口,并且在处理各种细节问题(如权限永久拒绝和不同版本的权限管理)时更加灵活。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容