Android 13及更高版本PDF保存权限被拒问题求助
解决Android 13 PDF保存权限被拒问题
问题概述
在Android 7设备上PDF保存功能正常,但切换到Android 13时出现权限错误:
Cannot open file, path = '/storage/emulated/0/Download/BitesVilla-invoice.pdf' (OS Error: Permission denied, errno = 13)
使用permission_handler插件请求存储权限始终返回Denied,修改AndroidManifest.xml也无法解决。
核心原因
- Android 10(API 29)启用分区存储,禁止直接通过硬编码路径写入公共外部存储目录;
- Android 13(API 33)进一步调整权限规则:保存文件到
Download等公共目录无需申请READ/WRITE_EXTERNAL_STORAGE,但必须通过MediaStoreAPI操作,不能直接写入文件路径; - 原代码中硬编码文件路径的方式在高版本系统中被拦截,导致权限拒绝。
解决方案
1. 替换文件保存逻辑为MediaStore API
修改_saveAsFile方法,使用MediaStore将PDF写入Download目录,无需申请存储权限:
import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:pdf/widgets.dart' as pw; Future<void> _saveAsFile( BuildContext context, pw.LayoutCallback build, pw.PdfPageFormat pageFormat, ) async { final bytes = await build(pageFormat); // 通过MethodChannel调用原生代码保存文件 const channel = MethodChannel('com.your.app/file_save'); try { await channel.invokeMethod('saveFileToDownload', { 'fileName': 'BitesVilla-invoice.pdf', 'bytes': bytes, }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('PDF已保存到Download目录'), duration: Duration(seconds: 2), ), ); await showNotification(context, File('/storage/emulated/0/Download/BitesVilla-invoice.pdf')); } on PlatformException catch (e) { logger.e('保存失败: ${e.message}'); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('保存失败: ${e.message}')), ); } }
在Android原生端添加MethodChannel实现(MainActivity.kt):
import android.content.ContentValues import android.os.Build import android.provider.MediaStore import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { private val CHANNEL = "com.your.app/file_save" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "saveFileToDownload") { val fileName = call.argument<String>("fileName") val bytes = call.argument<ByteArray>("bytes") if (fileName == null || bytes == null) { result.error("INVALID_ARGUMENTS", "文件名或字节数据为空", null) return@setMethodCallHandler } saveFileToDownload(fileName, bytes, result) } else { result.notImplemented() } } } private fun saveFileToDownload(fileName: String, bytes: ByteArray, result: MethodChannel.Result) { val contentValues = ContentValues().apply { put(MediaStore.Downloads.DISPLAY_NAME, fileName) put(MediaStore.Downloads.MIME_TYPE, "application/pdf") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.Downloads.RELATIVE_PATH, "Download") put(MediaStore.Downloads.IS_PENDING, 1) } } val resolver = contentResolver val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) if (uri == null) { result.error("URI_NULL", "无法获取文件URI", null) return } try { resolver.openOutputStream(uri).use { outputStream -> outputStream?.write(bytes) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { contentValues.put(MediaStore.Downloads.IS_PENDING, 0) resolver.update(uri, contentValues, null, null) } } result.success(true) } catch (e: Exception) { resolver.delete(uri, null, null) result.error("WRITE_FAILED", e.message, null) } } }
2. 调整AndroidManifest.xml权限配置
移除或限制旧存储权限的作用范围,避免高版本系统的权限冲突:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- 仅在Android 12及以下版本申请存储权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
3. 移除无效的权限请求代码
删除原代码中针对Android 13的存储权限请求逻辑,因为不需要这些权限:
// 以下代码可完全删除 // final value = await Permission.manageExternalStorage.request(); // logger.w("External Storage Value: $value"); // final status = await Permission.storage.status; // logger.w("Permission: $status"); // if (Platform.isAndroid && androidInfo.version.sdkInt > 29) { // final val = await Permission.manageExternalStorage.request(); // logger.w("Above 11: $val"); // } else { // final val = await Permission.storage.request(); // logger.w("Below 11: $val"); // }
验证说明
- Android 7及以下:兼容原逻辑或MediaStore方式,均可正常保存;
- Android 10-12:通过
maxSdkVersion限制的存储权限,配合MediaStore或原路径方式均可; - Android 13+:无需存储权限,通过MediaStore API直接写入Download目录,避免权限问题。
内容的提问来源于stack exchange,提问作者Arnab Ghosh
相关产品推荐
相关产品推荐

