跳转到正文
YGJoon 技术博客

Flutter 权限处理详解

发布于:
·1.4k 字 · 约 3 分钟·作者:
permission_handler 一行 request() 申请相机定位存储;两端先声明权限,被拒后引导用户去设置页手动开
Flutter 权限处理详解文章封面
目录

简介

移动应用经常需要访问设备的各种功能(如相机、位置等),这就需要合理处理权限申请和检查。本文介绍 Flutter 中权限处理的方法。

基本配置

添加依赖

dependencies:
  permission_handler: ^10.4.3

配置平台权限

Android (AndroidManifest.xml)

<manifest xmlns: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"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
</manifest>

iOS (Info.plist)

<key>NSCameraUsageDescription</key>
<string>需要访问相机以拍摄照片</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以选择照片</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要访问位置信息以提供相关服务</string>

基本使用

检查权限

Future<bool> checkPermission(Permission permission) async {
  final status = await permission.status;
  return status.isGranted;
}

请求权限

Future<bool> requestPermission(Permission permission) async {
  final status = await permission.request();
  return status.isGranted;
}

打开设置

void openSettings() async {
  await openAppSettings();
}

完整示例

class PermissionManager {
  static final PermissionManager _instance = PermissionManager._internal();
  factory PermissionManager() => _instance;
  
  PermissionManager._internal();
  
  // 检查多个权限
  Future<Map<Permission, bool>> checkPermissions(
    List<Permission> permissions,
  ) async {
    Map<Permission, bool> statuses = {};
    for (var permission in permissions) {
      statuses[permission] = await checkPermission(permission);
    }
    return statuses;
  }
  
  // 请求多个权限
  Future<Map<Permission, bool>> requestPermissions(
    List<Permission> permissions,
  ) async {
    Map<Permission, bool> statuses = {};
    for (var permission in permissions) {
      statuses[permission] = await requestPermission(permission);
    }
    return statuses;
  }
  
  // 检查单个权限
  Future<bool> checkPermission(Permission permission) async {
    final status = await permission.status;
    return status.isGranted;
  }
  
  // 请求单个权限
  Future<bool> requestPermission(Permission permission) async {
    final status = await permission.request();
    return status.isGranted;
  }
  
  // 检查并请求权限
  Future<bool> checkAndRequestPermission(Permission permission) async {
    if (await checkPermission(permission)) {
      return true;
    }
    return await requestPermission(permission);
  }
}

// 使用示例
class PermissionDemo extends StatefulWidget {
  @override
  _PermissionDemoState createState() => _PermissionDemoState();
}

class _PermissionDemoState extends State<PermissionDemo> {
  final permissionManager = PermissionManager();
  Map<Permission, bool> _permissionStatuses = {};
  
  @override
  void initState() {
    super.initState();
    _checkPermissions();
  }
  
  Future<void> _checkPermissions() async {
    final statuses = await permissionManager.checkPermissions([
      Permission.camera,
      Permission.storage,
      Permission.location,
    ]);
    
    setState(() {
      _permissionStatuses = statuses;
    });
  }
  
  Future<void> _requestPermission(Permission permission) async {
    final granted = await permissionManager.requestPermission(permission);
    
    setState(() {
      _permissionStatuses[permission] = granted;
    });
    
    if (!granted) {
      _showPermissionDialog();
    }
  }
  
  void _showPermissionDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('需要权限'),
        content: Text('请在设置中开启相关权限'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('取消'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              openAppSettings();
            },
            child: Text('去设置'),
          ),
        ],
      ),
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('权限管理')),
      body: ListView(
        children: [
          _buildPermissionTile(
            '相机权限',
            Permission.camera,
            Icons.camera_alt,
          ),
          _buildPermissionTile(
            '存储权限',
            Permission.storage,
            Icons.storage,
          ),
          _buildPermissionTile(
            '位置权限',
            Permission.location,
            Icons.location_on,
          ),
        ],
      ),
    );
  }
  
  Widget _buildPermissionTile(
    String title,
    Permission permission,
    IconData icon,
  ) {
    final granted = _permissionStatuses[permission] ?? false;
    
    return ListTile(
      leading: Icon(icon),
      title: Text(title),
      subtitle: Text(granted ? '已授权' : '未授权'),
      trailing: Switch(
        value: granted,
        onChanged: (value) {
          if (value) {
            _requestPermission(permission);
          }
        },
      ),
    );
  }
}

最佳实践

  1. 先查 status 再 request:用户选了”永久拒绝”后,再调 request() 弹窗不会再出现,等于白调一次;先查 isPermanentlyDenied,是就直接弹二次引导去设置页。
  2. 权限按需分组申请,别在启动页一次性要相机+定位+存储:三个系统弹窗连击,用户第一反应是”这 App 要偷我数据”,拒绝率飙升;用到哪个申请哪个(申请时机的道理见文末实战案例)。
  3. iOS 的 UsageDescription 文案认真写:审核会看,写”需要您的权限”这种会被打回;写清具体用途——“拍摄身份证照片用于实名认证”,一句话讲清”为什么”。

注意事项

  1. iOS 端 Info.plist 里缺了对应的 UsageDescription key,申请时直接崩溃(不是返回 denied):上线前逐个权限检查 plist,别等用户 crash 上报才发现。
  2. Android 13+ 存储权限拆分:READ_MEDIA_IMAGES/VIDEO/AUDIO 替代了 READ_EXTERNAL_STORAGE;targetSdk 33+ 还在用旧权限申请,系统直接无视——按官方迁移文档逐个替换。
  3. 定位权限别上来就要 always:先申请 whenInUse,等用户信任后再引导 always;iOS 上 always 要走两步授权,审核还会问你为什么需要后台定位,答不上来就被拒。
  4. Android 上用户可以选”仅本次允许”:别把”这次授权了”缓存成永久状态,下次启动重新查 status——不然功能莫名其妙失效,你还以为是代码 bug。

实战案例:权限申请的时机比代码重要

某物流冷链项目 的身份证认证页用 image_picker 拍照(controller 里 final ImagePicker _picker = ImagePicker();),这个场景让我彻底理解了”申请时机”这件事。一开始的写法是进页面就申请相机权限,用户还没搞明白要干嘛,先被系统弹窗吓一跳,拒绝率很高。改成用户点了”拍摄身份证”按钮才申请,拒绝率直接降下来——因为这时候用户有明确预期,“我要拍照,当然要给相机权限”。被拒之后也别干瞪眼,弹个二次引导说明”去设置里打开”,比直接跳设置页的转化好。trade-off 在定位权限上也一样:别在启动页一次性申请相机+定位+存储三个权限,用户会觉得这 App 要窃取我全部数据。原则就一条:权限申请永远跟在用户动作后面,而不是跟在 App 启动后面。permission_handler 的 API 都是现成的,真正的功夫在产品交互上。

总结

合理的权限处理对于应用的用户体验至关重要。通过使用 permission_handler 插件,可以轻松实现权限的申请和管理。注意在实现过程中要考虑用户体验和平台差异。


分享这篇文章:


相关文章

上一篇
Flutter 国际化实现详解
下一篇
Flutter 推送通知实现详解