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.location
,Permission.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 中需要NSLocationAlwaysUsageDescription
和NSLocationAlwaysAndWhenInUseUsageDescription
。 - 通知权限
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 平台分别处理权限请求逻辑,同时确保统一的调用入口,并且在处理各种细节问题(如权限永久拒绝和不同版本的权限管理)时更加灵活。