Flutter媒体下载应用真机存储权限异常问题求助
Flutter媒体下载应用真机无法请求存储权限且下载失败问题解决(compileSdkVersion 33)
核心问题分析
- Android 13(API33)权限机制变更:原
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限在API33已被废弃,转而使用针对不同媒体类型的细分权限,当前使用的Permission.storage在API33真机上不会触发权限请求弹窗。 - 权限请求逻辑无等待:
download方法调用checkPermission()时未用await,导致下载逻辑在权限请求完成前就执行,直接触发存储访问失败。 - 硬编码存储路径兼容性差:直接使用
/storage/emulated/0/Download/路径在部分真机上可能因权限或路径结构差异导致写入失败。
具体修复步骤
1. 更新AndroidManifest权限配置
针对API33及以上版本添加媒体文件访问权限,保留低版本兼容配置:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 基础权限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- 低版本(<33)存储权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- API33+ 媒体权限(对应音频文件) --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- 仅需批量管理文件时保留,否则可移除 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" /> <application android:label="downloader" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true" android:requestLegacyExternalStorage="true"> <!-- 原有application内容不变 --> </application> </manifest>
2. 修正权限请求与下载逻辑
- 适配API33+权限,使用对应媒体权限而非
Permission.storage - 确保下载逻辑等待权限请求完成
- 处理权限拒绝边界情况,避免无限递归
import 'dart:io'; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:path_provider/path_provider.dart'; // 检查权限方法 Future<bool> checkPermission() async { bool hasPermission = false; if (Platform.isAndroid) { final androidInfo = await DeviceInfoPlugin().androidInfo; if (androidInfo.version.sdkInt >= 33) { // API33+ 请求音频读取权限 var audioStatus = await Permission.readMediaAudio.status; if (!audioStatus.isGranted) { audioStatus = await Permission.readMediaAudio.request(); } hasPermission = audioStatus.isGranted; } else { // 低版本请求存储权限 var storageStatus = await Permission.storage.status; if (!storageStatus.isGranted) { storageStatus = await Permission.storage.request(); } hasPermission = storageStatus.isGranted; } } else { // iOS等其他平台默认授权 hasPermission = true; } hasPermission ? print('权限已授予') : print('权限被拒绝'); return hasPermission; } // 下载方法修正 Future<void> download(String name, String link) async { bool permissionGranted = await checkPermission(); if (!permissionGranted) { print('权限未授予,无法下载'); // 可添加弹窗提示用户手动开启权限 return; } NotificationService().showNotification(id: 0, title: '下载已开始'); try { var response = await http.get(Uri.parse(link)); // 获取系统公共下载目录 Directory? downloadDir = await getExternalStoragePublicDirectory(DirectoryDownloads); if (downloadDir == null) { print('无法获取公共下载目录'); return; } final file = File('${downloadDir.path}/$name.m4a'); await file.writeAsBytes(response.bodyBytes); NotificationService().showNotification( id: 1, title: '下载完成', body: '$name 下载成功'); MediaScanner.loadMedia(path: file.path); print('下载完成'); } catch (e) { print('下载错误: $e'); NotificationService().showNotification( id: 2, title: '下载失败', body: '$name 下载出错'); } }
3. 补充必要依赖
在pubspec.yaml中添加对应依赖:
dependencies: flutter: sdk: flutter permission_handler: ^10.2.0 device_info_plus: ^9.0.0 path_provider: ^2.0.15 http: ^0.13.5
额外注意事项
- 若无需访问应用外媒体文件,可使用应用专属存储目录(无需权限),路径可通过
getApplicationDocumentsDirectory()获取。 MANAGE_EXTERNAL_STORAGE为特殊权限,仅当应用需批量管理所有文件时使用,提交Google Play需说明用途,否则可能被拒。
内容的提问来源于stack exchange,提问作者Raj Tripathi
相关产品推荐
相关产品推荐

