使用File Picker与Permission Handler保存文件时Flutter应用崩溃
Flutter保存文本文件崩溃,仅生成0字节空文件问题解决
问题描述
开发Flutter应用时,尝试将文本文件保存到设备存储,使用file_picker选择保存位置、permission_handler申请权限,但保存时应用崩溃,仅生成0字节空文件。
现有代码实现
Flutter保存函数
Future<void> saveTextFile() async { var storageStatus = await Permission.storage.status; var manageStorageStatus = await Permission.manageExternalStorage.status; if (kDebugMode) { print('::::::::::::: Storage status: $storageStatus'); print('::::::::::::: Manage storage status: $manageStorageStatus'); } if (!storageStatus.isGranted) { storageStatus = await Permission.storage.request(); } if (storageStatus.isDenied || storageStatus.isPermanentlyDenied) { manageStorageStatus = await Permission.manageExternalStorage.request(); } if (storageStatus.isGranted || manageStorageStatus.isGranted) { try { // Create some lorem ipsum text String loremText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; // Convert the text to bytes final bytes = utf8.encode(loremText); String fileName = 'LoremIpsum.txt'; // Open the file picker for the user to choose the save location String? outputFile = await FilePicker.platform.saveFile( dialogTitle: 'Save your text file', fileName: fileName, type: FileType.custom, allowedExtensions: ['txt'], ); if (outputFile != null) { final file = File(outputFile); await file.writeAsBytes(bytes, flush: true); // Show a message indicating the file has been saved. Get.snackbar('Success', 'Lorem ipsum text has been saved as a text file.'); } else { // User canceled the file save dialog Get.snackbar('Cancelled', 'File save operation was cancelled.'); } } catch (e) { // Catch any errors during the file writing process Get.snackbar('Error', 'An error occurred while saving the file: $e'); debugPrint('Error saving file: $e'); } } else { // Handle permission denial Get.snackbar('Permission Denied', 'Storage permission is required to save the file.'); }
AndroidManifest.xml配置
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <application android:label="SJIC Admin" android:name="${applicationName}" android:icon="@mipmap/launcher_icon" android:requestLegacyExternalStorage="true"> <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
问题现象
- 通过
permission_handler申请存储权限 - 生成文本内容并转为字节
- 用
file_picker选择保存位置 - 保存时应用崩溃,仅生成0字节空文件
已尝试操作
- build.gradle配置:minSdkVersion 30
- 依赖版本:Flutter SDK 3.19.3,path_provider: ^2.1.3,permission_handler: ^11.3.1,file_picker: ^8.0.3
解决方案
1. 修正权限逻辑(适配Android 11+存储机制)
Android 11(API 30)及以上,WRITE_EXTERNAL_STORAGE权限已被废弃,MANAGE_EXTERNAL_STORAGE仅适用于文件管理器类应用,普通应用无需申请。file_picker的saveFile方法通过系统存储访问框架(SAF)实现,用户选择保存位置后自动获得该文件的写入权限,无需额外申请全局存储权限。
仅在Android 10及以下版本,需要申请传统存储权限。
2. 优化后的Flutter代码
import 'package:device_info_plus/device_info_plus.dart'; Future<void> saveTextFile() async { // 获取Android SDK版本 final androidInfo = await DeviceInfoPlugin().androidInfo; final int sdkVersion = androidInfo.version.sdkInt; if (kDebugMode) { print('当前SDK版本: $sdkVersion'); } // 仅在Android 10及以下申请存储权限 if (sdkVersion < 30) { var storageStatus = await Permission.storage.status; if (!storageStatus.isGranted) { storageStatus = await Permission.storage.request(); if (storageStatus.isDenied || storageStatus.isPermanentlyDenied) { Get.snackbar('权限拒绝', '需要存储权限才能保存文件'); return; } } } try { const String loremText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; const String fileName = 'LoremIpsum.txt'; String? outputFile = await FilePicker.platform.saveFile( dialogTitle: '保存文本文件', fileName: fileName, type: FileType.custom, allowedExtensions: ['txt'], ); if (outputFile != null) { final file = File(outputFile); // 直接写入字符串更高效,无需手动转字节 await file.writeAsString(loremText, flush: true); Get.snackbar('成功', '文本文件已保存'); } else { Get.snackbar('已取消', '文件保存操作已取消'); } } catch (e, stackTrace) { Get.snackbar('错误', '保存文件时出错: $e'); // 打印堆栈信息便于排查 debugPrint('保存文件错误详情: $e\n$stackTrace'); } }
3. 修正AndroidManifest.xml配置
移除不必要的权限,仅保留低版本所需的读取权限:
<!-- 仅在Android 10及以下需要读取权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" /> <application android:label="SJIC Admin" android:name="${applicationName}" android:icon="@mipmap/launcher_icon"> <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
4. 添加依赖
需要添加device_info_plus来获取Android SDK版本,在pubspec.yaml中添加:
dependencies: device_info_plus: ^10.1.0
问题原因说明
- 权限逻辑错误:在Android 11+下错误申请
MANAGE_EXTERNAL_STORAGE权限,该权限与SAF机制冲突,导致写入文件时权限异常。 - 传统存储权限失效:Android 11+不再认可
WRITE_EXTERNAL_STORAGE权限,直接使用会导致写入失败。 - 异常捕获不完整:原代码未捕获堆栈信息,难以定位崩溃具体原因。
内容的提问来源于stack exchange,提问作者abishek perera
相关产品推荐
相关产品推荐

