权限管理
移动应用需要获取用户的授权才能使用某些设备功能(如相机、定位、存储等)。不同平台的权限机制不同,需要分别配置。
为什么需要权限
移动操作系统为了保护用户隐私,要求 App 在使用敏感功能前必须声明权限并获得用户授权。如果不在配置文件中声明权限,相关功能将无法使用甚至导致崩溃。
三大平台安全模型
| 平台 | 权限声明位置 | 运行时请求 | 说明 |
|---|---|---|---|
| Android | AndroidManifest.xml | 部分(危险权限需运行时请求) | 权限分普通和危险两类 |
| iOS | Info.plist | 是(必须) | 每种隐私访问都需描述用途 |
| 鸿蒙 | module.json5 | 是 | 类似 Android 但有独立权限体系 |
Android 权限配置
1. 在 AndroidManifest.xml 中声明权限
文件位置:android/app/src/main/AndroidManifest.xml
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 普通权限(安装时自动授予) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- 危险权限(需运行时请求) -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>2. 运行时请求危险权限
使用 permission_handler 插件:
bash
flutter pub add permission_handlerdart
import 'package:permission_handler/permission_handler.dart';
// 请求相机权限
Future<bool> requestCameraPermission() async {
// ─── ★ 运行时请求权限 ──────────────
final status = await Permission.camera.request();
// ─── ☆ 运行时请求权限 ──────────────
// ─── ★ 权限已授予 ──────────────
if (status.isGranted) {
return true;
} else if (status.isDenied) {
// 用户拒绝,可以再次请求
return false;
} else if (status.isPermanentlyDenied) {
// 用户永久拒绝,需要引导去设置页
await openAppSettings();
return false;
}
// ─── ☆ 权限已授予 ──────────────
return false;
}
// 检查权限状态
Future<bool> hasCameraPermission() async {
final status = await Permission.camera.status;
return status.isGranted;
}Android 危险权限分组
| 权限组 | 权限 |
|---|---|
| 位置 | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION |
| 相机 | CAMERA |
| 存储 | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE |
| 麦克风 | RECORD_AUDIO |
| 联系人 | READ_CONTACTS, WRITE_CONTACTS |
| 电话 | READ_PHONE_STATE, CALL_PHONE |
| 短信 | SEND_SMS, READ_SMS |
| 通知 | POST_NOTIFICATIONS (Android 13+) |
iOS 权限配置
在 Info.plist 中添加隐私描述
文件位置:ios/Runner/Info.plist
xml
<dict>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>需要访问相机以拍摄照片</string>
<!-- 相册 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以选择照片</string>
<!-- 定位(使用时) -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要获取位置信息以提供附近服务</string>
<!-- 定位(始终) -->
<key>NSLocationAlwaysUsageDescription</key>
<string>需要始终获取位置信息以提供导航服务</string>
<!-- 麦克风 -->
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风以录音</string>
<!-- 通讯录 -->
<key>NSContactsUsageDescription</key>
<string>需要访问通讯录以选择联系人</string>
<!-- 通知 -->
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
</dict>重要
iOS 审核要求每个隐私权限必须有清晰的用途描述。如果描述模糊或与功能无关,会被拒审。
鸿蒙权限配置
在 module.json5 中声明权限
文件位置:ohos/entry/src/main/module.json5
json
{
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{ "name": "ohos.permission.GET_WIFI_INFO" },
{ "name": "ohos.permission.APPROXIMATELY_LOCATION" },
{ "name": "ohos.permission.LOCATION" },
{ "name": "ohos.permission.CAMERA" }
]
}鸿蒙权限分类
| 类型 | 说明 | 示例 |
|---|---|---|
| normal | 安装时自动授予 | INTERNET, GET_WIFI_INFO |
| system_basic | 需用户授权 | LOCATION, CAMERA |
| system_core | 仅系统应用 | 极少使用 |
常见权限场景速查
| 功能 | Android 权限 | iOS Key | 鸿蒙权限 |
|---|---|---|---|
| 网络请求 | INTERNET | 无需 | ohos.permission.INTERNET |
| 相机 | CAMERA | NSCameraUsageDescription | ohos.permission.CAMERA |
| 相册读取 | READ_EXTERNAL_STORAGE | NSPhotoLibraryUsageDescription | - |
| 定位 | ACCESS_FINE_LOCATION | NSLocationWhenInUseUsageDescription | ohos.permission.LOCATION |
| 麦克风 | RECORD_AUDIO | NSMicrophoneUsageDescription | - |
| 通知 | POST_NOTIFICATIONS | Background Modes | - |
| 文件读写 | READ/WRITE_EXTERNAL_STORAGE | 无需(沙盒) | - |
| 生物识别 | USE_BIOMETRIC | NSFaceIDUsageDescription | - |
权限被拒绝后的处理策略
dart
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
class PermissionExamplePage extends StatelessWidget {
const PermissionExamplePage({super.key});
Future<void> requestWithFallback(BuildContext context, Permission permission) async {
// ─── ★ 通用权限请求封装 ──────────────
final status = await permission.request();
// ─── ☆ 通用权限请求封装 ──────────────
if (status.isGranted) {
// 权限已授予,执行功能
// ─── ★ 异步操作后检查 mounted ──────────────
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('权限已授予')),
);
}
// ─── ☆ 异步操作后检查 mounted ──────────────
} else if (status.isDenied) {
// 用户拒绝了,但可以再次请求
// 展示为什么需要此权限的说明,然后再次请求
} else if (status.isPermanentlyDenied) {
// 用户选择了"不再询问",需要引导去设置
if (context.mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('需要权限'),
content: const Text('请在设置中开启权限以使用此功能'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: const Text('去设置'),
),
],
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('权限管理')),
body: Center(
child: ElevatedButton(
onPressed: () => requestWithFallback(context, Permission.camera),
child: const Text('请求相机权限'),
),
),
);
}
}最佳实践
- 在使用功能前请求权限,不要一次性请求所有权限
- 被拒绝后解释为什么需要此权限
- 永久拒绝后引导用户去系统设置
- 权限被拒绝时提供降级功能(如无相机时选择相册)
下一步
- 插件开发与鸿蒙适配 — MethodChannel / EventChannel
