Android14下Microsoft Excel编辑保存后xlsx文件无法被ContentResolver访问
问题:Android 14下Microsoft Excel编辑保存后无法通过ContentResolver访问XLSX文件
环境配置
- .NET 8 MAUI项目
- minSdkVersion=29,compileSdkVersion=34,targetSdkVersion=34
- 已申请权限:
READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE、MANAGE_EXTERNAL_STORAGE
问题复现代码
// CREATE按钮点击事件 private async void OnCreateClicked(object sender, EventArgs e) { #if ANDROID29_0_OR_GREATER var activity = Platform.CurrentActivity!; var contentResolver = activity.ContentResolver!; // 删除已存在的文件 var cursor = contentResolver.Query(MediaStore.Downloads.ExternalContentUri, [IBaseColumns.Id, MediaStore.IMediaColumns.DisplayName, MediaStore.IMediaColumns.Data], MediaStore.IMediaColumns.DisplayName + " = ?", ["myexcel.xlsx"], null); if (cursor?.MoveToFirst() == true) { var id = cursor.GetLong(cursor.GetColumnIndex(IBaseColumns.Id)); var uri = ContentUris.WithAppendedId(MediaStore.Downloads.ExternalContentUri, id); contentResolver.Delete(uri, null, null); } // 从Assets读取模板文件 using var stream = await FileSystem.OpenAppPackageFileAsync("myexcel.xlsx"); using var memory = new MemoryStream(); stream.CopyTo(memory); var myexcel = memory.ToArray(); var extType = MimeTypeMap.Singleton!.GetMimeTypeFromExtension("xlsx"); var storedValue = new ContentValues(); storedValue.Put(MediaStore.IMediaColumns.DisplayName, "myexcel.xlsx"); storedValue.Put(MediaStore.IMediaColumns.MimeType, extType); storedValue.Put(MediaStore.IMediaColumns.RelativePath, Android.OS.Environment.DirectoryDownloads + "/myexcel/"); // 保存文件到MediaStore var fileUri = contentResolver.Insert(MediaStore.Downloads.ExternalContentUri, storedValue)!; using var writeStream = contentResolver.OpenOutputStream(fileUri, "rwt")!; writeStream.Write(myexcel); Toast.MakeText(activity, "File created.", ToastLength.Long)!.Show(); #endif } // OPEN按钮点击事件 private void OnOpenClicked(object sender, EventArgs e) { #if ANDROID29_0_OR_GREATER var activity = Platform.CurrentActivity!; var contentResolver = activity.ContentResolver!; #if ANDROID30_0_OR_GREATER // 检查MANAGE_EXTERNAL_STORAGE权限 if (!Android.OS.Environment.IsExternalStorageManager) { try { var packageUri = Android.Net.Uri.Parse($"package:{activity.PackageName}"); var intent = new Intent(Settings.ActionManageAppAllFilesAccessPermission, packageUri); activity.StartActivity(intent); } catch (Exception ex) { Toast.MakeText(activity, ex.Message, ToastLength.Long)!.Show(); } } #endif var cursor = contentResolver.Query(MediaStore.Downloads.ExternalContentUri, [IBaseColumns.Id, MediaStore.IMediaColumns.DisplayName, MediaStore.IMediaColumns.Data], MediaStore.IMediaColumns.DisplayName + " = ?", ["myexcel.xlsx"], null); if (cursor?.MoveToFirst() == true) { var id = cursor.GetLong(cursor.GetColumnIndex(IBaseColumns.Id)); var uri = ContentUris.WithAppendedId(MediaStore.Downloads.ExternalContentUri, id); var intent = new Intent(Intent.ActionView, uri); intent.AddFlags(ActivityFlags.NewTask) .AddFlags(ActivityFlags.ClearTop) .AddFlags(ActivityFlags.GrantReadUriPermission) .AddFlags(ActivityFlags.GrantWriteUriPermission); try { activity.StartActivity(intent); } catch (Exception ex) { Toast.MakeText(activity, ex.Message, ToastLength.Long)!.Show(); } } else { Toast.MakeText(activity, "File not found.", ToastLength.Long)!.Show(); } #endif }
操作流程
- 点击CREATE按钮,在Download文件夹下的
myexcel子目录创建myexcel.xlsx - 点击OPEN按钮用Microsoft Excel打开文件
- 未编辑直接返回:应用可再次正常打开文件
- 编辑内容后保存返回:应用无法通过MediaStore查询到该文件
异常现象
- 文件仍存在于Google Files中,可正常打开
- 编辑前执行
adb content query能查到文件记录,编辑后无查询结果 - 在Google Files中重命名文件再改回原名,可重新被
adb和应用识别 - 编辑前后文件权限无异常
- Android 11系统下无此问题
期望效果
- 文件稳定保存到外部存储的Download文件夹
- 编辑保存后应用可再次打开文件并显示修改内容
- 可通过ContentResolver或其他方式删除编辑后的文件
解决方案
原因分析
Android 14中Microsoft Excel采用原子保存机制:编辑时创建临时文件,保存时删除原文件,再将临时文件重命名为原文件名。但Android 14的MediaStore对第三方应用修改的文件索引更新不及时,且Excel未主动触发MediaStore扫描,导致原文件的MediaStore记录被删除后,新文件未被重新索引。
解决办法
1. 直接通过文件路径访问(依赖MANAGE_EXTERNAL_STORAGE权限)
既然已申请全文件访问权限,可绕过MediaStore,直接通过文件路径操作:
private void OnOpenClicked(object sender, EventArgs e) { #if ANDROID29_0_OR_GREATER var activity = Platform.CurrentActivity!; #if ANDROID30_0_OR_GREATER if (!Android.OS.Environment.IsExternalStorageManager) { try { var packageUri = Android.Net.Uri.Parse($"package:{activity.PackageName}"); var intent = new Intent(Settings.ActionManageAppAllFilesAccessPermission, packageUri); activity.StartActivity(intent); return; } catch (Exception ex) { Toast.MakeText(activity, ex.Message, ToastLength.Long)!.Show(); return; } } #endif // 直接构建文件路径 var filePath = Path.Combine( Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, Android.OS.Environment.DirectoryDownloads, "myexcel", "myexcel.xlsx" ); if (File.Exists(filePath)) { var fileUri = Android.Net.Uri.FromFile(new Java.IO.File(filePath)); var intent = new Intent(Intent.ActionView, fileUri); intent.AddFlags(ActivityFlags.NewTask) .AddFlags(ActivityFlags.GrantReadUriPermission) .AddFlags(ActivityFlags.GrantWriteUriPermission); try { activity.StartActivity(intent); } catch (Exception ex) { Toast.MakeText(activity, ex.Message, ToastLength.Long)!.Show(); } } else { Toast.MakeText(activity, "File not found.", ToastLength.Long)!.Show(); } #endif }
2. 触发MediaStore重新扫描文件
如果坚持使用MediaStore,可在查询前强制触发文件扫描,更新索引:
private void TriggerMediaScan(string filePath) { #if ANDROID29_0_OR_GREATER var scannerClient = new MediaScannerClient(filePath); var scannerConnection = new MediaScannerConnection(Platform.CurrentActivity!, scannerClient); scannerConnection.Connect(); #endif } private class MediaScannerClient : Java.Lang.Object, MediaScannerConnection.IMediaScannerConnectionClient { private readonly string _filePath; public MediaScannerClient(string filePath) => _filePath = filePath; public void OnMediaScannerConnected() { MediaScannerConnection.ScanFile(Platform.CurrentActivity!, new[] { _filePath }, null, null); } public void OnScanCompleted(string path, Android.Net.Uri uri) { MediaScannerConnection.Disconnect(); } }
在OnOpenClicked中,先调用TriggerMediaScan(filePath),再执行MediaStore查询。
3. 使用存储访问框架(SAF)获取持久化权限
若不想依赖MANAGE_EXTERNAL_STORAGE(Google Play审核可能受限),可通过SAF让用户选择文件,获取持久化URI权限:
private const int OpenDocumentRequestCode = 1001; private void OpenWithSAF() { var intent = new Intent(Intent.ActionOpenDocument); intent.AddCategory(Intent.CategoryOpenable); intent.SetType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // 申请持久化权限 intent.AddFlags(ActivityFlags.GrantPersistableUriPermission); intent.AddFlags(ActivityFlags.GrantReadUriPermission); intent.AddFlags(ActivityFlags.GrantWriteUriPermission); Platform.CurrentActivity!.StartActivityForResult(intent, OpenDocumentRequestCode); } // 在Activity中处理SAF返回结果 protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data) { base.OnActivityResult(requestCode, resultCode, data); if (requestCode == OpenDocumentRequestCode && resultCode == Result.Ok && data?.Data != null) { // 持久化URI权限 var takeFlags = ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission; Platform.CurrentActivity!.ContentResolver.TakePersistableUriPermission(data.Data, takeFlags); // 保存URI到本地,后续可直接使用 var prefs = GetSharedPreferences("FilePrefs", FileCreationMode.Private); prefs.Edit().PutString("ExcelFileUri", data.Data.ToString()).Apply(); // 打开文件 var intent = new Intent(Intent.ActionView, data.Data); intent.AddFlags(ActivityFlags.NewTask); StartActivity(intent); } }
这种方式不受MediaStore索引问题影响,符合Android 13+存储最佳实践。
内容的提问来源于stack exchange,提问作者WJC
相关产品推荐
相关产品推荐

