MAUI:Android模拟器中FileSaver存SD卡却返回内置存储路径
解决CommunityToolkit.Maui FileSaver返回SD卡文件路径错误的问题
当使用CommunityToolkit.Maui.Storage.FileSaver在Android API32上将文件保存到SD卡时,返回的FilePath始终指向内置存储,而非实际保存的SD卡路径,导致后续操作不符合预期。以下是针对该问题的解决方案:
核心原因
CommunityToolkit.Maui的FileSaver在处理Android存储访问框架(SAF)返回的Uri时,未能正确解析SD卡的真实路径,导致返回内置存储的模拟路径。
解决方案:自定义文件保存逻辑(平台特定实现)
绕过CommunityToolkit的FileSaver,直接调用Android原生的SAF来获取真实的文件路径,具体步骤如下:
1. 定义跨平台接口
在共享项目中创建一个接口,统一文件保存的调用方式:
public interface ICustomFileSaver { Task<string> SaveFileAsync(string initialFileName, Stream stream, CancellationToken cancellationToken); }
2. Android平台实现
在Android项目中实现上述接口,处理SAF的文件保存和路径解析:
[assembly: Dependency(typeof(CustomFileSaver))] namespace YourAppNamespace.Platforms.Android; using Android.Content; using Android.OS; using Android.Provider; using Microsoft.Maui.ApplicationModel; public class CustomFileSaver : ICustomFileSaver { public async Task<string> SaveFileAsync(string initialFileName, Stream stream, CancellationToken cancellationToken) { // 创建保存文件的Intent var saveIntent = new Intent(Intent.ActionCreateDocument); saveIntent.AddCategory(Intent.CategoryOpenable); saveIntent.SetType("*/*"); // 根据文件类型调整,例如"text/plain"或"image/png" saveIntent.PutExtra(Intent.ExtraTitle, initialFileName); // 启动系统文件选择器并等待结果 var activityResult = await Platform.CurrentActivity.StartActivityForResultAsync(saveIntent, 1001); if (activityResult.ResultCode == Result.Ok && activityResult.Data != null) { var fileUri = activityResult.Data.Data; // 将流写入选中的文件 using var outputStream = Platform.CurrentActivity.ContentResolver.OpenOutputStream(fileUri); await stream.CopyToAsync(outputStream, cancellationToken); // 解析Uri对应的真实SD卡路径 return ResolveRealPathFromUri(fileUri); } return null; } private string ResolveRealPathFromUri(Android.Net.Uri uri) { string realPath = null; var contentResolver = Platform.CurrentActivity.ContentResolver; // 尝试从MediaStore获取路径 using var cursor = contentResolver.Query(uri, null, null, null, null); if (cursor != null && cursor.MoveToFirst()) { var dataColumnIndex = cursor.GetColumnIndex(MediaStore.MediaColumns.Data); if (dataColumnIndex != -1) { realPath = cursor.GetString(dataColumnIndex); } } // 处理SD卡的Uri格式 if (string.IsNullOrEmpty(realPath) && uri.Scheme.Equals("content", StringComparison.OrdinalIgnoreCase)) { var docId = DocumentsContract.GetDocumentId(uri); var splitDocId = docId.Split(':'); var storageType = splitDocId[0]; if ("primary".Equals(storageType, StringComparison.OrdinalIgnoreCase)) { realPath = $"{Android.OS.Environment.ExternalStorageDirectory.AbsolutePath}/{splitDocId[1]}"; } else { // 遍历所有外部存储目录,匹配SD卡路径 var externalDirs = Android.OS.Environment.GetExternalStorageDirs(); foreach (var dir in externalDirs) { var dirPath = dir.AbsolutePath; if (dirPath.Contains(storageType)) { realPath = $"{dirPath}/{splitDocId[1]}"; break; } } } } return realPath; } }
3. 在共享项目中调用
替换原有的FileSaver调用,使用自定义的文件保存逻辑:
var customSaver = DependencyService.Get<ICustomFileSaver>(); var actualFilePath = await customSaver.SaveFileAsync(fileName, stream, ct); if (!string.IsNullOrEmpty(actualFilePath)) { filename = actualFilePath; // 后续基于真实路径的操作 }
注意事项
- 确保已在
AndroidManifest.xml中申请MANAGE_EXTERNAL_STORAGE权限,并完成用户授权流程。 - 根据实际文件类型调整
SetType参数,避免系统文件选择器过滤掉目标文件类型。 - 不同厂商的SD卡挂载路径可能存在差异,需在多设备上测试路径解析逻辑的兼容性。
内容的提问来源于stack exchange,提问作者user7399085
相关产品推荐
相关产品推荐

