Flutter应用启动时集成permission_handler权限处理及相关报错解决
Flutter启动存储权限申请问题修复方案
根因分析&修复思路
对应遇到的4个问题,处理逻辑如下:
- 重复申请权限报错:将权限校验的Future预先存在State变量中,避免FutureBuilder重绘时重复触发校验逻辑
- 从设置页返回不识别权限:监听应用生命周期
resume事件,用户从设置页返回应用时自动重新校验权限状态 - 不再询问后无限循环:权限申请前判断
isPermanentlyDenied状态,该状态下直接弹出引导去设置的弹窗,不再调用系统权限申请接口 - 启动加载页体验差:将闪屏页作为默认展示的UI,异步做权限校验,校验过程中用户看到的是正常闪屏,不会出现加载圈或者黑屏
- 高版本Android权限适配:Android13及以上存储权限规则变更,需要在Manifest中补充对应权限声明,解决
no permissions defined报错
代码修改
1. AndroidManifest.xml权限适配
补充Android13及以上的媒体权限声明,兼容全版本系统:
<!-- Android 12及以下 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13及以上 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
2. permission_handler.dart完整修改代码
import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:status_saver/utils/splash_screen.dart'; class PermissionHandler extends StatefulWidget { const PermissionHandler({Key? key}) : super(key: key); @override _PermissionHandlerState createState() => _PermissionHandlerState(); } class _PermissionHandlerState extends State<PermissionHandler> with WidgetsBindingObserver { // 预先存储权限校验Future,避免重复触发 late Future<void> _permissionCheckFuture; // 权限状态标记 bool _isPermissionGranted = false; // 是否需要展示设置引导弹窗 bool _showSettingDialog = false; @override void initState() { super.initState(); // 注册生命周期监听 WidgetsBinding.instance.addObserver(this); _permissionCheckFuture = _checkPermission(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); // 应用从后台回到前台时重新校验权限 if (state == AppLifecycleState.resumed && !_isPermissionGranted) { _checkPermission(); } } Future<void> _checkPermission() async { PermissionStatus status; // 根据系统版本判断要校验的权限 if (Theme.of(context).platform == TargetPlatform.android) { final androidInfo = await WidgetsBinding.instance.platformDispatcher.androidInfo; if (androidInfo.version.sdkInt >= 33) { status = await Permission.photos.status; } else { status = await Permission.storage.status; } } else { // iOS权限按需补充,这里默认过 status = PermissionStatus.granted; } if (status.isGranted) { setState(() { _isPermissionGranted = true; _showSettingDialog = false; }); return; } // 永久拒绝状态,直接弹设置引导 if (status.isPermanentlyDenied) { setState(() { _showSettingDialog = true; }); return; } // 其他未授权状态,申请权限 PermissionStatus requestStatus; if (Theme.of(context).platform == TargetPlatform.android) { final androidInfo = await WidgetsBinding.instance.platformDispatcher.androidInfo; if (androidInfo.version.sdkInt >= 33) { requestStatus = await Permission.photos.request(); } else { requestStatus = await Permission.storage.request(); } } else { requestStatus = PermissionStatus.granted; } if (requestStatus.isGranted) { setState(() { _isPermissionGranted = true; _showSettingDialog = false; }); } else if (requestStatus.isPermanentlyDenied) { setState(() { _showSettingDialog = true; }); } else { // 普通拒绝,重新触发校验 _checkPermission(); } } @override void dispose() { // 移除生命周期监听 WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { // 已授权直接返回闪屏页 if (_isPermissionGranted) { return const SplashScreen(); } return FutureBuilder( future: _permissionCheckFuture, builder: (ctx, snapshot) { // 校验过程中直接展示闪屏页UI,避免加载圈 if (snapshot.connectionState != ConnectionState.done || !_showSettingDialog) { return const SplashScreen(); } // 需要展示引导弹窗的情况 return Scaffold( body: Stack( children: [ const SplashScreen(), AlertDialog( title: const Text('权限申请'), content: const Text('需要存储权限才能正常使用应用功能,请前往设置开启'), actions: [ TextButton( onPressed: () { openAppSettings(); setState(() { _showSettingDialog = false; }); }, child: const Text('去开启'), ), TextButton( onPressed: () { setState(() { _showSettingDialog = false; }); // 拒绝的话重新校验,持续申请 _checkPermission(); }, child: const Text('取消'), ), ], ), ], ), ); }, ); } }
优化点说明
- 全程校验过程中默认展示闪屏页,不会出现加载圈或者黑屏,符合体验要求
- 权限申请失败后如果不是永久拒绝,会自动重新申请,直到用户授权或者点不再询问
- 监听应用生命周期,用户从设置页返回后自动校验权限,授权后直接进入后续流程
- 兼容Android 13及以上系统的权限规则,解决Manifest权限不识别的报错
内容的提问来源于stack exchange,提问作者user12680761
相关产品推荐
相关产品推荐

