Flutter中使用file_picker时如何获取文件的真实路径
Flutter中使用file_picker获取文件真实存储路径的实现方法

核心原因
你拿到缓存路径并非代码错误,是file_picker的默认设计逻辑:为保证跨平台一致性、规避各系统权限限制,插件默认会将用户选中的文件拷贝到应用自身临时缓存目录,返回的path字段就是这份缓存拷贝的路径,并非文件原始存储位置。
- iOS端受系统沙盒机制强制限制,不存在获取其他应用、系统目录下文件真实绝对路径的可能,所有第三方选择器都只能返回缓存路径或者授权访问的安全资源URL,无任何绕过方案。
- Android端从API 29(Android 10)开始推行分区存储规则,默认禁止应用直接访问公共存储目录的文件真实路径,需要通过存储访问框架(SAF)拿到授权Uri后转换得到,且部分非媒体类文件就算拿到路径也没有直接读写权限。
Android端可行实现
方案1:基于现有file_picker扩展,无需更换选择器
只需要将file_picker返回的content格式Uri转换为真实路径即可,步骤如下:
- 添加依赖
在pubspec.yaml中添加以下依赖:
dependencies: file_picker: ^6.1.1 uri_to_file: ^0.2.0 # 用于将content://格式Uri转换为真实文件路径 permission_handler: ^11.0.1 # 用于动态申请存储权限 device_info_plus: ^9.1.1 # 用于判断Android系统版本做权限适配
- 配置Android清单文件
打开android/app/src/main/AndroidManifest.xml做两处修改:
- 在
<application>标签上添加属性:android:requestLegacyExternalStorage="true",兼容Android 10版本的存储权限 - 在
<manifest>根节点下添加所需权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
- 替换原有文件选择逻辑
将你代码中的_openFileExplorer方法替换为以下实现,同时补充权限申请方法,其余原有逻辑(清除缓存、选择文件夹、UI构建代码等)不需要修改:
import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:uri_to_file/uri_to_file.dart'; // 存储权限动态申请 Future<bool> _requestStoragePermission() async { if (!Platform.isAndroid) return true; final androidInfo = await DeviceInfoPlugin().androidInfo; if (androidInfo.version.sdkInt <= 32) { final status = await Permission.storage.request(); return status.isGranted; } else { // Android 13+ 按文件类型申请对应媒体权限,非媒体文件走SAF临时授权 final imagePerm = await Permission.photos.request(); final videoPerm = await Permission.videos.request(); final audioPerm = await Permission.audio.request(); return imagePerm.isGranted || videoPerm.isGranted || audioPerm.isGranted; } } void _openFileExplorer() async { setState(() => _loadingPath = true); try { // 先申请权限 final hasPerm = await _requestStoragePermission(); if (!hasPerm) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("未获得存储访问权限,无法选择文件")) ); setState(() => _loadingPath = false); return; } _directoryPath = null; final result = await FilePicker.platform.pickFiles( type: _pickingType, allowMultiple: _multiPick, allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '').split(',') : null, ); if (result == null) { setState(() => _loadingPath = false); return; } // 遍历文件,转换content uri为真实路径 final List<PlatformFile> realFileList = []; for (final item in result.files) { // Android端identifier字段即为原始content:// 格式Uri if (item.identifier != null && item.identifier!.startsWith('content://')) { final realFile = await toFile(item.identifier!); realFileList.add(PlatformFile( name: item.name, size: item.size, path: realFile.path, // 此处即为文件真实存储路径 bytes: item.bytes, identifier: item.identifier, )); } else { realFileList.add(item); } } _paths = realFileList; } on PlatformException catch (e) { print("操作不支持: ${e.toString()}"); } catch (ex) { print("选择文件出错: $ex"); } if (!mounted) return; setState(() { _loadingPath = false; if (_paths != null && _paths!.isNotEmpty) { _fileName = _paths!.map((e) => e.name).toString(); } }); }
方案2:使用基于SAF实现的专属选择器
如果觉得Uri转换逻辑麻烦,可以直接替换为Android端专属的SAF框架选择器插件,这类插件选择文件后会直接返回真实路径和对应访问权限,但这类插件不支持iOS,跨平台项目需要自行做平台判断逻辑。
使用提醒
- 不要在iOS端浪费时间尝试获取真实路径,系统沙盒机制从底层禁止跨应用访问目录,所有相关方案返回的都是应用缓存目录下的拷贝文件路径。
- Android 11及以上版本,Download、Documents等非媒体公共目录中的文件,就算拿到真实路径,应用也没有直接读写权限,必须通过ContentResolver或者SAF提供的文件描述符操作,强行读写会直接报权限错误。
- 如果业务场景只是需要读取文件内容、上传文件,直接使用file_picker默认返回的缓存路径即可,缓存文件不会自动删除,只有主动调用
clearTemporaryFiles方法时才会被清理,完全可以满足常规业务需求,没必要强行获取真实路径。
内容的提问来源于stack exchange,提问作者user10264787
相关产品推荐
相关产品推荐

