附录D - 权限处理
嗨,朋友!我是长安。
现代移动应用经常需要访问用户的敏感数据,比如相机、相册、位置等。为了保护用户隐私,iOS 和 Android 都有严格的权限管理机制。
这一章,我会教你如何在 Flutter 中正确请求和管理各种权限!
🔐 权限基础知识
为什么需要权限?
- 保护用户隐私:防止应用未经授权访问敏感数据
- 提升用户信任:明确告知用户应用需要什么权限
- 符合平台规范:iOS/Android 都强制要求权限管理
常见权限类型
| 权限 | 用途 | Android | iOS |
|---|---|---|---|
| 相机 | 拍照、扫码 | CAMERA | NSCameraUsageDescription |
| 相册 | 读取/保存图片 | READ_EXTERNAL_STORAGE | NSPhotoLibraryUsageDescription |
| 位置 | 定位、导航 | ACCESS_FINE_LOCATION | NSLocationWhenInUseUsageDescription |
| 麦克风 | 录音、语音 | RECORD_AUDIO | NSMicrophoneUsageDescription |
| 通知 | 推送消息 | POST_NOTIFICATIONS | - |
| 通讯录 | 联系人 | READ_CONTACTS | NSContactsUsageDescription |
| 日历 | 日程管理 | READ_CALENDAR | NSCalendarsUsageDescription |
| 存储 | 文件读写 | READ/WRITE_EXTERNAL_STORAGE | - |
📦 permission_handler 使用
permission_handler 是 Flutter 中最流行的权限管理包。
1. 添加依赖
# pubspec.yaml
dependencies:
permission_handler: ^11.3.0
2. 平台配置
Android 配置
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 相册/存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Android 13+ 图片权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<!-- 位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- 后台定位(可选) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<!-- 麦克风权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- 通知权限(Android 13+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- 通讯录权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<!-- 日历权限 -->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<application ...>
...
</application>
</manifest>
iOS 配置
<!-- ios/Runner/Info.plist -->
<dict>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>我们需要使用相机来拍照和扫描二维码</string>
<!-- 相册读取 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>我们需要访问相册来选择图片</string>
<!-- 相册写入 -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>我们需要保存图片到相册</string>
<!-- 位置(使用时) -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>我们需要获取您的位置来提供定位服务</string>
<!-- 位置(始终) -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>我们需要在后台获取您的位置来提供导航服务</string>
<!-- 麦克风 -->
<key>NSMicrophoneUsageDescription</key>
<string>我们需要使用麦克风来录制语音</string>
<!-- 通讯录 -->
<key>NSContactsUsageDescription</key>
<string>我们需要访问通讯录来添加好友</string>
<!-- 日历 -->
<key>NSCalendarsUsageDescription</key>
<string>我们需要访问日历来添加日程提醒</string>
<!-- 蓝牙 -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>我们需要使用蓝牙来连接设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>我们需要使用蓝牙来连接设备</string>
<!-- Face ID -->
<key>NSFaceIDUsageDescription</key>
<string>我们需要使用 Face ID 来验证身份</string>
</dict>
3. 基本使用
import 'package:permission_handler/permission_handler.dart';
// 检查权限状态
Future<void> checkCameraPermission() async {
final status = await Permission.camera.status;
if (status.isGranted) {
print('相机权限已授权');
} else if (status.isDenied) {
print('相机权限被拒绝');
} else if (status.isPermanentlyDenied) {
print('相机权限被永久拒绝,需要去设置中开启');
} else if (status.isRestricted) {
print('相机权限受限(iOS 家长控制)');
}
}
// 请求单个权限
Future<void> requestCameraPermission() async {
final status = await Permission.camera.request();
if (status.isGranted) {
// 权限已授权,执行相关操作
openCamera();
} else if (status.isPermanentlyDenied) {
// 引导用户去设置页面
openAppSettings();
} else {
// 权限被拒绝
showPermissionDeniedDialog();
}
}
// 请求多个权限
Future<void> requestMultiplePermissions() async {
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
Permission.microphone,
Permission.photos,
].request();
if (statuses[Permission.camera]!.isGranted &&
statuses[Permission.microphone]!.isGranted) {
// 相机和麦克风都已授权
startVideoRecording();
}
}
🎯 权限请求最佳实践
封装权限服务
// lib/services/permission_service.dart
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/material.dart';
class PermissionService {
/// 请求权限的通用方法
static Future<bool> requestPermission(
Permission permission, {
required BuildContext context,
required String permissionName,
required String description,
}) async {
// 1. 先检查当前状态
final status = await permission.status;
// 2. 如果已授权,直接返回
if (status.isGranted) {
return true;
}
// 3. 如果被永久拒绝,引导去设置
if (status.isPermanentlyDenied) {
final shouldOpenSettings = await _showSettingsDialog(
context,
permissionName,
description,
);
if (shouldOpenSettings) {
await openAppSettings();
}
return false;
}
// 4. 首次请求或之前拒绝过,显示说明后请求
if (status.isDenied) {
// 可选:先显示自定义说明对话框
final shouldRequest = await _showExplanationDialog(
context,
permissionName,
description,
);
if (!shouldRequest) {
return false;
}
// 请求权限
final result = await permission.request();
return result.isGranted;
}
return false;
}
/// 显示权限说明对话框
static Future<bool> _showExplanationDialog(
BuildContext context,
String permissionName,
String description,
) async {
return await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('需要$permissionName权限'),
content: Text(description),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定'),
),
],
),
) ?? false;
}
/// 显示去设置对话框
static Future<bool> _showSettingsDialog(
BuildContext context,
String permissionName,
String description,
) async {
return await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('$permissionName权限被禁用'),
content: Text('$description\n\n请前往系统设置中手动开启权限。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('去设置'),
),
],
),
) ?? false;
}
}
使用封装的服务
class CameraPage extends StatelessWidget {
const CameraPage({super.key});
Future<void> _openCamera(BuildContext context) async {
final hasPermission = await PermissionService.requestPermission(
Permission.camera,
context: context,
permissionName: '相机',
description: '我们需要使用相机来拍照和扫描二维码',
);
if (hasPermission) {
// 打开相机
_startCamera();
}
}
void _startCamera() {
// 相机逻辑
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('相机')),
body: Center(
child: ElevatedButton(
onPressed: () => _openCamera(context),
child: const Text('打开相机'),
),
),
);
}
}
📷 相机权限
完整相机权限处理
import 'package:permission_handler/permission_handler.dart';
import 'package:image_picker/image_picker.dart';
class CameraService {
final ImagePicker _picker = ImagePicker();
/// 拍照
Future<String?> takePhoto(BuildContext context) async {
// 请求相机权限
final hasPermission = await _requestCameraPermission(context);
if (!hasPermission) return null;
try {
final XFile? photo = await _picker.pickImage(
source: ImageSource.camera,
maxWidth: 1920,
maxHeight: 1080,
imageQuality: 85,
);
return photo?.path;
} catch (e) {
debugPrint('拍照失败: $e');
return null;
}
}
/// 录制视频
Future<String?> recordVideo(BuildContext context) async {
// 需要相机和麦克风权限
final cameraGranted = await _requestCameraPermission(context);
final micGranted = await _requestMicrophonePermission(context);
if (!cameraGranted || !micGranted) return null;
try {
final XFile? video = await _picker.pickVideo(
source: ImageSource.camera,
maxDuration: const Duration(minutes: 5),
);
return video?.path;
} catch (e) {
debugPrint('录制失败: $e');
return null;
}
}
Future<bool> _requestCameraPermission(BuildContext context) async {
final status = await Permission.camera.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
_showSettingsDialog(context, '相机');
return false;
}
final result = await Permission.camera.request();
return result.isGranted;
}
Future<bool> _requestMicrophonePermission(BuildContext context) async {
final status = await Permission.microphone.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
_showSettingsDialog(context, '麦克风');
return false;
}
final result = await Permission.microphone.request();
return result.isGranted;
}
void _showSettingsDialog(BuildContext context, String permissionName) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('需要$permissionName权限'),
content: Text('请在设置中开启$permissionName权限'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: const Text('去设置'),
),
],
),
);
}
}
🖼️ 相册权限
相册读取和保存
import 'package:permission_handler/permission_handler.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
class PhotoService {
final ImagePicker _picker = ImagePicker();
/// 从相册选择图片
Future<String?> pickImage(BuildContext context) async {
final hasPermission = await _requestPhotosPermission(context);
if (!hasPermission) return null;
try {
final XFile? image = await _picker.pickImage(
source: ImageSource.gallery,
maxWidth: 1920,
maxHeight: 1080,
imageQuality: 85,
);
return image?.path;
} catch (e) {
debugPrint('选择图片失败: $e');
return null;
}
}
/// 从相册选择多张图片
Future<List<String>> pickMultipleImages(BuildContext context) async {
final hasPermission = await _requestPhotosPermission(context);
if (!hasPermission) return [];
try {
final List<XFile> images = await _picker.pickMultiImage(
maxWidth: 1920,
maxHeight: 1080,
imageQuality: 85,
);
return images.map((e) => e.path).toList();
} catch (e) {
debugPrint('选择图片失败: $e');
return [];
}
}
/// 从相册选择视频
Future<String?> pickVideo(BuildContext context) async {
final hasPermission = await _requestPhotosPermission(context);
if (!hasPermission) return null;
try {
final XFile? video = await _picker.pickVideo(
source: ImageSource.gallery,
maxDuration: const Duration(minutes: 10),
);
return video?.path;
} catch (e) {
debugPrint('选择视频失败: $e');
return null;
}
}
/// 请求相册权限
Future<bool> _requestPhotosPermission(BuildContext context) async {
// Android 13+ 使用新的权限
Permission permission;
if (Platform.isAndroid) {
// Android 13+ (API 33+)
permission = Permission.photos;
} else {
permission = Permission.photos;
}
final status = await permission.status;
if (status.isGranted || status.isLimited) {
return true;
}
if (status.isPermanentlyDenied) {
_showSettingsDialog(context, '相册');
return false;
}
final result = await permission.request();
return result.isGranted || result.isLimited;
}
void _showSettingsDialog(BuildContext context, String permissionName) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('需要$permissionName权限'),
content: Text('请在设置中开启$permissionName权限'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: const Text('去设置'),
),
],
),
);
}
}
保存图片到相册
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:typed_data';
class ImageSaveService {
/// 保存网络图片到相册
Future<bool> saveNetworkImage(
BuildContext context,
String imageUrl,
) async {
// 请求存储权限
final hasPermission = await _requestStoragePermission(context);
if (!hasPermission) return false;
try {
// 下载图片
final response = await Dio().get(
imageUrl,
options: Options(responseType: ResponseType.bytes),
);
// 保存到相册
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 100,
name: 'image_${DateTime.now().millisecondsSinceEpoch}',
);
return result['isSuccess'] ?? false;
} catch (e) {
debugPrint('保存图片失败: $e');
return false;
}
}
/// 保存本地图片到相册
Future<bool> saveLocalImage(
BuildContext context,
String filePath,
) async {
final hasPermission = await _requestStoragePermission(context);
if (!hasPermission) return false;
try {
final result = await ImageGallerySaver.saveFile(filePath);
return result['isSuccess'] ?? false;
} catch (e) {
debugPrint('保存图片失败: $e');
return false;
}
}
Future<bool> _requestStoragePermission(BuildContext context) async {
// iOS 使用 photosAddOnly 权限
// Android 使用 storage 权限
final permission = Platform.isIOS
? Permission.photosAddOnly
: Permission.storage;
final status = await permission.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
_showSettingsDialog(context);
return false;
}
final result = await permission.request();
return result.isGranted;
}
void _showSettingsDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('需要存储权限'),
content: const Text('保存图片需要存储权限,请在设置中开启'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: const Text('去设置'),
),
],
),
);
}
}
📍 定位权限
位置服务
import 'package:permission_handler/permission_handler.dart';
import 'package:geolocator/geolocator.dart';
class LocationService {
/// 获取当前位置
Future<Position?> getCurrentLocation(BuildContext context) async {
// 1. 检查位置服务是否开启
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
_showLocationServiceDialog(context);
return null;
}
// 2. 请求位置权限
final hasPermission = await _requestLocationPermission(context);
if (!hasPermission) return null;
// 3. 获取位置
try {
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
timeLimit: const Duration(seconds: 10),
);
return position;
} catch (e) {
debugPrint('获取位置失败: $e');
return null;
}
}
/// 监听位置变化
Stream<Position>? watchPosition(BuildContext context) {
return Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10, // 移动 10 米才更新
),
);
}
/// 计算两点距离(米)
double calculateDistance(
double startLat,
double startLng,
double endLat,
double endLng,
) {
return Geolocator.distanceBetween(startLat, startLng, endLat, endLng);
}
/// 请求位置权限
Future<bool> _requestLocationPermission(BuildContext context) async {
final status = await Permission.locationWhenInUse.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
_showSettingsDialog(context);
return false;
}
final result = await Permission.locationWhenInUse.request();
return result.isGranted;
}
/// 请求后台定位权限
Future<bool> requestBackgroundLocation(BuildContext context) async {
// 先确保有前台定位权限
final foregroundGranted = await _requestLocationPermission(context);
if (!foregroundGranted) return false;
// 再请求后台定位权限
final status = await Permission.locationAlways.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
_showSettingsDialog(context);
return false;
}
final result = await Permission.locationAlways.request();
return result.isGranted;
}
void _showLocationServiceDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('位置服务未开启'),
content: const Text('请开启设备的位置服务'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
Geolocator.openLocationSettings();
},
child: const Text('去设置'),
),
],
),
);
}
void _showSettingsDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('需要位置权限'),
content: const Text('获取位置需要定位权限,请在设置中开启'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: const Text('去设置'),
),
],
),
);
}
}
使用位置服务
class LocationPage extends StatefulWidget {
const LocationPage({super.key});
State<LocationPage> createState() => _LocationPageState();
}
class _LocationPageState extends State<LocationPage> {
final _locationService = LocationService();
Position? _currentPosition;
bool _isLoading = false;
Future<void> _getLocation() async {
setState(() => _isLoading = true);
final position = await _locationService.getCurrentLocation(context);
setState(() {
_currentPosition = position;
_isLoading = false;
});
if (position != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'位置:${position.latitude.toStringAsFixed(6)}, '
'${position.longitude.toStringAsFixed(6)}',
),
),
);
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('位置')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_currentPosition != null) ...[
Text('纬度:${_currentPosition!.latitude}'),
Text('经度:${_currentPosition!.longitude}'),
Text('精度:${_currentPosition!.accuracy} 米'),
const SizedBox(height: 20),
],
ElevatedButton(
onPressed: _isLoading ? null : _getLocation,
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('获取位置'),
),
],
),
),
);
}
}
🔔 通知权限
请求通知权限
import 'package:permission_handler/permission_handler.dart';
class NotificationService {
/// 请求通知权限
Future<bool> requestNotificationPermission(BuildContext context) async {
// Android 13+ 需要请求通知权限
// iOS 需要请求通知权限
final status = await Permission.notification.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
_showSettingsDialog(context);
return false;
}
// 显示说明对话框
final shouldRequest = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('开启通知'),
content: const Text('开启通知后,您将及时收到消息提醒、活动通知等重要信息'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('暂不开启'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('立即开启'),
),
],
),
) ?? false;
if (!shouldRequest) return false;
final result = await Permission.notification.request();
return result.isGranted;
}
/// 检查通知权限状态
Future<bool> isNotificationEnabled() async {
final status = await Permission.notification.status;
return status.isGranted;
}
void _showSettingsDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('通知权限被禁用'),
content: const Text('请在设置中开启通知权限,以便接收重要消息'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: const Text('去设置'),
),
],
),
);
}
}
🛡️ 权限状态组件
权限检查 Widget
import 'package:permission_handler/permission_handler.dart';
/// 权限状态检查组件
class PermissionGuard extends StatefulWidget {
final Permission permission;
final Widget child;
final Widget Function(BuildContext context)? onDenied;
final String permissionName;
final String description;
const PermissionGuard({
super.key,
required this.permission,
required this.child,
this.onDenied,
required this.permissionName,
required this.description,
});
State<PermissionGuard> createState() => _PermissionGuardState();
}
class _PermissionGuardState extends State<PermissionGuard>
with WidgetsBindingObserver {
PermissionStatus? _status;
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_checkPermission();
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void didChangeAppLifecycleState(AppLifecycleState state) {
// 从设置返回时重新检查权限
if (state == AppLifecycleState.resumed) {
_checkPermission();
}
}
Future<void> _checkPermission() async {
final status = await widget.permission.status;
if (mounted) {
setState(() => _status = status);
}
}
Future<void> _requestPermission() async {
final status = await widget.permission.request();
if (mounted) {
setState(() => _status = status);
}
}
Widget build(BuildContext context) {
if (_status == null) {
return const Center(child: CircularProgressIndicator());
}
if (_status!.isGranted) {
return widget.child;
}
// 权限被拒绝时显示的内容
return widget.onDenied?.call(context) ??
_buildDefaultDeniedWidget(context);
}
Widget _buildDefaultDeniedWidget(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.lock_outline,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
'需要${widget.permissionName}权限',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
widget.description,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey[600]),
),
const SizedBox(height: 24),
if (_status!.isPermanentlyDenied)
ElevatedButton(
onPressed: openAppSettings,
child: const Text('去设置'),
)
else
ElevatedButton(
onPressed: _requestPermission,
child: const Text('授权'),
),
],
),
),
);
}
}
// 使用示例
class CameraPage extends StatelessWidget {
const CameraPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('相机')),
body: PermissionGuard(
permission: Permission.camera,
permissionName: '相机',
description: '我们需要使用相机来拍照和扫描二维码',
child: const CameraPreview(), // 权限授权后显示的内容
),
);
}
}
📋 权限状态一览表
PermissionStatus 枚举
| 状态 | 说明 | 处理方式 |
|---|---|---|
granted | 已授权 | 直接使用功能 |
denied | 被拒绝(可再次请求) | 显示说明后请求 |
permanentlyDenied | 被永久拒绝 | 引导去设置 |
restricted | 受限(iOS 家长控制) | 提示用户 |
limited | 有限授权(iOS 14+ 相册) | 可以使用 |
provisional | 临时授权(iOS 通知) | 可以发静默通知 |
判断方法
final status = await Permission.camera.status;
// 常用判断
status.isGranted // 已授权
status.isDenied // 被拒绝
status.isPermanentlyDenied // 永久拒绝
status.isRestricted // 受限
status.isLimited // 有限授权(iOS 相册)
📝 最佳实践总结
权限请求原则
- 适时请求:在需要使用功能时才请求,不要一启动就请求所有权限
- 说明用途:请求前先告知用户为什么需要这个权限
- 优雅降级:权限被拒绝时提供替代方案或明确提示
- 尊重选择:不要反复弹窗骚扰用户
- 引导设置:永久拒绝后引导用户去设置页面
请求时机建议
// ✅ 好的做法:用户点击相关功能时请求
ElevatedButton(
onPressed: () async {
final granted = await requestCameraPermission();
if (granted) openCamera();
},
child: const Text('拍照'),
)
// ❌ 不好的做法:App 启动时批量请求所有权限
void main() async {
await [
Permission.camera,
Permission.location,
Permission.contacts,
Permission.microphone,
].request(); // 用户还不知道这些权限用来干嘛
runApp(const MyApp());
}
💪 练习题
- 实现一个完整的相机功能,包含权限请求和错误处理
- 实现一个位置打卡功能,获取并显示当前位置
- 创建一个权限管理页面,显示各权限状态并支持跳转设置
- 实现从相册选择多张图片并上传的功能
🎉 教程完结
恭喜你完成了 Flutter 基础教程的全部内容!现在你已经掌握了:
- Flutter 开发环境搭建
- Dart 语言基础
- Widget 和布局系统
- 状态管理(setState、Provider、GetX)
- 页面导航和路由
- 网络请求和本地存储
- 动画和主题定制
- 常用第三方包
- 调试和性能优化
- 打包和发布
- 项目结构最佳实践
- 国际化和权限处理
现在,是时候开始你自己的 Flutter 项目了!加油!💪
由 编程指南 提供
